关于设计模式:改善应用程序性能和代码质量通过代理模式组合HTTP请求

40次阅读

共计 3621 个字符,预计需要花费 10 分钟才能阅读完成。

原文发表于我的博客:blog.zhangbing.site

在前端我的项目中,咱们的网页通常须要向服务器发送多个 HTTP 申请。

假如咱们的产品具备一项性能,即每当用户单击 li 标记时,客户端都会向服务器发送一个 HTTP 申请。

这是一个简略的 Demo:

<html>

<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
    </ul>

    <script>
        // Suppose this function is used to make HTTP requests to the server
        var sendHTTPRequest = function(message) {console.log('Start sending HTTP message to the server:', message)
            console.log('1000ms passed')
            console.log('HTTP Request is completed')
        }

        var ul = document.getElementsByTagName('ul')[0];

        ul.onclick = function(event) {if (event.target.nodeName === "LI") {

                // Executes this function every time the <li> tag is clicked.
                sendHTTPRequest(event.target.innerText)
            }
        }
    </script>
</body>

</html>

在下面的代码中,咱们间接应用简略的 sendHTTPRequest 函数来 模仿 发送 HTTP 申请。这样做是为了更好地专一于外围指标,因而我简化了一些代码。

而后,咱们将 click 事件绑定到 ul 元素。每次用户单击诸如 <li> 5 </li> 之类的标记时,客户端将执行 sendHTTPRequest 函数以向服务器收回 HTTP 申请。

下面的程序是这样的:

为了使你们更容易尝试,我制作了一个 Codepen 演示:https://codepen.io/bitfishxyz…

当然,在实在的我的项目中,咱们可能会向服务器发送一个文件,推送告诉,或者发送一些日志。但为了演示的常规,咱们将跳过这些细节。

好了,这是一个很简略的演示,那么下面的代码有没有什么毛病呢?


如果您的我的项目非常简单,那么编写这样的代码应该没有问题。然而,如果您的我的项目很简单,并且客户端须要频繁向服务器发送 HTTP 申请,则此代码效率很低。

在下面的示例中,如果任何用户重复疾速单击 li 元素会产生什么?这时,咱们的客户端须要向服务器收回频繁的 HTTP 申请,并且每个申请都会耗费大量工夫和服务器资源。

客户端每次与服务器建设新的 HTTP 连贯时,都会耗费一些工夫和服务器资源。因而,在 HTTP 传输机制中,一次传输所有文件比屡次传输大量文件更为无效。

例如,您可能须要发送五个 HTTP 申请,每个 HTTP 申请的 HTTP 数据包大小为 1MB。当初,您一次发送一个 HTTP 申请,数据包大小为 5MB。通常预期后者的性能要比前一个更好。

网页上的大量 HTTP 申请可能会减慢网页的加载工夫,最终侵害用户体验。如果加载速度不够快,这可能会导致访问者更快地来到该页面。

因而,在这种状况下,咱们能够思考合并 HTTP 申请。

在咱们目前的我的项目中,我的思路是这样的:咱们能够在本地设置一个缓存,而后在肯定范畴内收集所有须要发送给服务器的音讯,而后一起发送。

你能够暂停一下,本人试着想方法。

提醒:您须要创立一个本地缓存对象来收集须要发送的音讯。而后,您须要应用定时器定时发送收集到的音讯。

这是一个实现。

var messages = [];
var timer;
var sendHTTPRequest = function (message) {messages.push(message);
  if (timer) {return;}
  timer = setTimeout(function () {console.log("Start sending messages:", messages.join(","));
    console.log("1000ms passed");
    console.log("HTTP Request is completed.");

    clearTimeout(timer);
    timer = null;
    messages = [];}, 2000);
};

每当客户端须要发送音讯,也就是触发一个 onclick 事件的时候,sendHTTPRequest 并不会立刻向服务器发送音讯,而是先将音讯缓存在音讯中。而后,咱们有一个计时器,该计时器在 2 秒钟后执行,并且在 2 秒钟后,该计时器会将所有先前缓存的音讯发送到服务器。此更改达到了组合 HTTP 申请的目标。

测试后果如下:

如你所见,只管咱们屡次触发点击事件,但在两秒钟内,咱们只发送了一个 HTTP 申请。

当然,为了不便演示,我将等待时间设置为 2 秒。如果你感觉这个等待时间太长,你能够缩短这个等待时间。

对于不须要太多实时交互的我的项目,2 秒的提早并不是一个微小的副作用,但它能够加重服务器的很多压力。在适当的状况下,这是十分值得的。


下面的代码的确为我的项目提供了一些性能改良。然而就代码设计而言,下面的代码并不好。

第一,违反了繁多责任准则。sendHTTPRequest 函数不仅向服务器发送 HTTP 申请,而且还组合 HTTP 申请。该函数执行过多操作,使代码看起来非常复杂。

如果某个性能(或对象)承当了过多的责任,那么当咱们的需要发生变化时,该性能通常将不得不产生重大变动。这样的设计不能无效地应答可能的更改,这是一个蹩脚的设计。

咱们现实的代码如下所示:

咱们没有对 sendHTTPRequest 进行任何更改,而是抉择为其提供代理。这个代理函数执行合并 HTTP 申请的工作,并将合并后的消息传递给 sendHTTPRequest 发送。而后咱们当前就能够间接应用 proxySendHTTPRequest 办法了。

您能够暂停片刻,而后尝试本人解决。

这是一个实现:

var proxySendHTTPRequest = (function() {var messages = [],
      timer;
  return function(message) {messages.push(message);
    if (timer) {return;}
    timer = setTimeout(function() {sendHTTPRequest(messages.join(","));
      clearTimeout(timer);
      timer = null;
      messages = [];}, 2000);
  };
})();

其根本思维与后面的代码相似,该代码应用 messages 变量在肯定工夫内缓存所有音讯,而后通过计时器对立地发送它们。此外,这段代码应用了闭包技巧,将 messagestimer 变量放在部分作用域中,以防止净化全局名称空间。

这段代码与后面的代码最大的区别是它没有更改 sendHTTPRequest 函数,而是将其暗藏在 proxySendHTTPRequest 前面。咱们不再须要间接拜访 sendHTTPRequest,而是应用代理 proxySendHTTPRequest 来拜访它。proxySendHTTPRequestsendHTTPRequest 具备雷同的参数列表和雷同的返回值。

这样的设计有什么益处?

  • 发送 HTTP 申请和合并 HTTP 申请的工作交给了两个不同的函数,每个函数专一于一个职责。它听从繁多责任准则,并使代码更容易了解。
  • 因为两个函数的参数是雷同的,咱们能够简略地用 proxySendHTTPRequest 替换 sendHTTPRequest 的地位,而不须要做任何重大更改。

设想一下,如果未来网络性能有所提高,或者因为某些其余起因,咱们不再须要合并 HTTP 申请。在这一点上,如果咱们应用以前的设计,咱们将不得不再次大规模地更改代码。在以后的代码设计中,咱们能够简略地替换函数名。

事实上,这个编码技巧通常被称为设计模式中的 代理模式

所谓的代理模式,其实在现实生活中很好了解。

  • 比方说,你想拜访一个网站,但你不想泄露你的 IP 地址。那么你能够应用 VPN,先拜访你的代理服务器,而后通过代理服务器拜访指标网站。这样指标网站就无奈晓得你的 IP 地址了。
  • 有时候,你会把你的实在服务器暗藏在 Nginx 服务器前面,让 Nginx 服务器为你的实在服务器解决一些琐碎的操作。

这些都是现实生活中代理模式的例子。

咱们不须要为代理模式(或任何其余设计模式)的正式定义而懊恼,咱们只须要晓得,当客户端没有间接拜访它的便当(或能力)时,咱们能够提供代理性能(或对象)来管制对指标性能(或对象)的拜访即可。客户机实际上拜访代理函数(或对象),代理函数对申请进行一些解决,而后将申请传递给指标。

正文完
 0