JavaScript设计模式笔记 - 享元模式 代理模式

发布于 2012-11-21 | 更新于 2020-09-20

1、享元模式

享元模式最适合于解决因创建大量类似对象而累及性能的问题。这种模式在Javascript中尤其有用,因为复杂的Javascript代码可能很快就会用光浏览器的所有可用内存。通过把大量独立对象转化为少量共享对象,可以降低运行Web应用程序所需的资源数量。对于那些可能一连用上几天也不会重新加载的大型应用系统,任何减少内存用量的技术都有非常显著的效果。而对于那些不会在浏览器中打开那么长时间的小型网页,内存的节约就没那么重要。

1.1、示例:汽车登记

内在状态和外在状态,用工厂进行实例化内在状态,封装在管理器中的外在状态。

example

用单例来做封装这些数据的管理器优化方法是以复杂性为代价的。原有的只是一个类,而现在却变成了一个类和两个单例对象。

1.1.1、管理外在状态:

使用管理器对象进行管理。这种对象有一个集中管理的数据库,用于存放外在状态及其所属的享元对象。汽车登记那个实力就采用了这种方案。其优点在于简单、容易维护。

使用组合模式进行管理。可以使用对象自身的层次体系来保存信息,而不需要另外使用一个集中管理的数据库。组合对象的叶节点全都可以是享元对象,这样一来这些享元对象可以在组合对象层次体系中的多个地方被共享。

1.2、示例:Web日历

创建一个Web日历,演示用组合对象保存外在状态的具体做法。

没有把日期对象转换为享元的问题:你不得不为每一年创建365个CalendarDay对象。如果对象数目太多了,会给浏览器带来资源压力。更有效的做法是无论日历要显示多少年,都只用了一个CalendarDay对象来代表所有日期。所有把日期对象转换为享元进行优化。example

1.3、外在数据保存在哪里

组合对象的结构本身就已经包含了所有的外在数据。由于月份对象中的日期对象依次存放在一个数组中,所以它知道每一个日期对象的状态,从CalendarDay构造函数中剔除的两种数据都已经存在于CalendarMonth对象中。

这就是组合模式与享元模式配合得如此完美的原因。组合对象通常拥有大量叶对象,它还保存着许多可作为外在数据处理的数据。叶对象通常只包含极少的内在数据,所以很容易被转化为共享资源。

1.4、示例:工具提示对象

在Javascript对象需要创建HTML内容这种情况下,享元模式特别有用。那种会生成DOM元素的对象如果数目众多的话,会占用过多内存,使网页陷入泥沼。采用享元模式后,只需创建少许这种对象即可,所有需要这种对象的地方都可以共享它们。工具提示就是一个典型的例子。

1.5、存实例供以后重用

模式对话框是享元模式的另一个适用场合。与工具提示一样,对话框对象也封装着数据和HTML内容。

因为运行期间需要用到的实例数目无法确定,所以不能对实例的个数加以限制,只能要用多少就创建多少,然后把它们保存起来供以后使用。

DialogBox的例子

1.6、享元模式的适用场合:

网页中必须使用了大量资源密集型对象。如果只会用到少许这类对象,这种优化并不划算。

对象中所保存的数据至少有一部分能被转化为外在数据。此外,将这些数据存储在对象外部所占用的资源应该相对较少,否则这种做法对于性能的提示实际上毫无意义。那种大量包含基础性代码和HTML内容的对象可能比较适合这种优化。

将外在数据分离出去后,独一无二的对象的数目相对较少。

1.7、实现享元模式的一般步骤:

将所有外在数据从目标类剥离。具体做法是尽可能多地删除该类的属性,所删除的应该是那种因实例而异的属性。构造参数也要这样处理。这些参数应该被添加到该类的各个方法。

创建一个用来控制该类的实例化的工厂。用一个对象字面量保存每一个这类对象的引用,并以用来生成这些对象的参数的唯一性组合作为它们的索引。另一种方法是对象池技术。

创建一个用来保存外在数据的管理器。外在数据被保存在管理器内的一个数据结构中。管理器随后会根据需要将这些数据提供给共享对象的方法,其效果就如同该类有许多实例一样。

1.8、享元模式之利:

可以把网页的资源符合降低几个数量级。即使享元模式的应用无法将实例的个数削减到一个,你仍能够从中获益不少。

这种节省不需要大量修改原有代码。在创建了管理器、工厂和享元之后,就需要对代码进行的修改只不过是从直接实例化目标类改为调用管理器对象的某个方法。

1.9、享元模式之弊:

如果把它用在不必要的地方,其结果反而有损代码的运行效率。这种模式在优化代码的同时,也提高了其复杂程度,这会给调试和维护造成困难。

它之所以会妨碍调试,是因为现在可能出错的地方变成了三个:管理器、工厂和享元。

这种优化也会使维护变得更加困难。现在你面对的不是由封装着数据的对象构成的清晰架构,而是一堆又碎又乱的东西。其中的数据至少分两处保存。最好注释标明内在数据和外在数据。

只有在必要的时候才应该进行这种优化。必须在运行效率和可维护性之间进行权衡。如果拿不准是否需要使用享元模式,那么你很可能并不需要它。享元模式适合的是系统资源已经用得差不多而且明显需要进行某种优化这样一类场合。

这种模式对Javascript程序员特别有用,因为它可以用来减少网页上所要使用的DOM元素的数量,要知道这些元素需要耗费许多内存。结合使用这种模式与组合模式等组织型可以开发出功能丰富的复杂Web应用系统,它们可以平稳的运行在任何现代Javascript环境中。

2、代理模式

代理是一个对象,可以用来控制对另一个对象的访问。与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象。另外那个对象成为本体。代理可以代替其本体本实例化,并使其可被远程访问。它还可以把本体的实例化推迟到真正需要的时候。

2.1、代理的结构:

代理最基本的形式是对访问进行控制。代理对象和另一个对象实现的是同样的接口。实际上工作的还是本体,它才是负责执行所分派的任务的那个对象或类。代理对象并不会想装饰者模式那样在另一个对象的基础上添加或修改方法,也不会像外观模式那样简化那个对象的接口。它实现的接口与本体完全相同,所有对它进行的方法调用都会被传递给本体。

2.1.1、代理如何控制对本体的访问

那种根本不实现任何访问控制的代理最简答,它所做的只是把所有方法调用传递到本体。这种代理毫无用处,但它也可提供一个进一步发展的基础。

示例:创建一个代表图书馆的类PublicLibrary,该类封装一个Book对象

一个没有实现任何访问控制的PublicLibrary类的代理:example

这种类型的代理没有什么用处。在各种类型的代理中,虚拟代理是最有用的类型之一。

虚拟代理用于控制对那种创建开销很大的本体的访问。它会把本体的实例化推迟到有方法被调用的时候,有时候还会提供关于实例化状态的反馈。

假设PublicLibrary的实例化很慢,不能在网页加载的时候立即完成。我们可以为其创建一个虚拟代理。让它把PublicLibrary的实例化推迟到必要的时候:example

PublicLibraryProxy和PublicLibraryVirtualProxy之间的关键区别在于后者不会立即创建PublicLibrary的实例。PublicLibraryVirtualProxy会把构造函数的参数保存起来,知道有方法被调用时才真正执行本体的实例化。

2.1.2、虚拟代理、远程代理和保护代理

对Javascript程序员来说,虚拟代理可能是最有用的代理类型。

远程代理:用于访问位于另一个环境中的对象。在Java中,这意味着另一个虚拟机中的对象。这种类型的代理很难照搬到Javascript中。首先,Javascript运行时环境不可能长期存在。第二,在Javascript中无法建立到另一个运行时环境的套接字连接以访问其变量空间。与此最接近的一种做法只是用JSON对方法调用进行序列化,然后用Ajax技术将结果发送给某个资源。

远程代理的一种更有可能的用途是控制对其他语言中的本体的访问,这种本体可能是一个Web服务资源,也可能是一个PHP对象。

保护代理也不容易照搬到Javascript中,在Javascript中,你无法判断调用方法的客户类型,因此也就不可能实现这种模式。

出于上述原因,本章集中讨论的是虚拟代理和远程代理。

2.1.3、代理模式与装饰者模式的比较

代理在许多方法都很想装饰者。装饰者和虚拟代理都要对其他对象进行包装,都要实现与被包装对象相同的接口,而且都要把方法调用传递给包装对象。

区别:

装饰者会对被包装对象的功能进行修改或扩充,而代理只不过是控制对它的访问。代理可能会添加一些控制代码,而并不会对传递给本体的方法调用进行修改。而装饰者就是为修改方法而生的。

装饰者中被包装对象的实例化过程是完全独立的,可以随意裹上一个或多个装饰者。而在代理模式中,被包装对象的实例化是代理的实例化过程的一部分。代理不会像装饰者那样互相包装,它们一次只使用一个。

2.2、代理模式的适用场合

虚拟代理是一个对象,用于控制对一个创建开销昂贵的资源的访问。虚拟代理是一种优化模式。如果有些类或对象需要使用大量内存保存期数据,而你并不需要在实例化完成之后立即访问这些数据,或者其构造函数需要进行大量计算那就应该使用虚拟代理将设置开销的产生推迟到真正需要使用数据的时候。

远程代理:如果需要访问某种远程资源的话,最好是用一个类或对象来包装它,而不是一遍又一遍的手工设置XMLHttpRequest对象。如果包装独享实现了远程资源的所有方法,那它就是一个远程代理。如果它会在运行期间增添一些方法,那它就是一个装饰者如果它简化了该远程资源的接口,那它就是一个门面。远程代理是一种结构型模式,它提供给了一个访问位于其他环境中的资源的原生Javascript API(native Javascript API)。

总之,如果有些类或对象的创建开销较大,而且不需要在实例化完成后立即访问其数据,那么应该使用虚拟代理。如果你有某种远程资源,并且要为该资源提供的所有功能实现对应的方法,那么应该使用远程代理。

2.3、示例:网页统计

本例将创建一个远程代理,包装了一个用来提供网页统计数据的Web服务。这个Web服务由一系列URL组成,它们各相当于一个拥有可选参数的方法。它在服务器端用什么语言实现并不重要。数据将以JSON格式返回。下面是这个Web服务实现的5个方法:

http://mydomain.com/stats/getPageviews/ http://mydomain.com/stats/getUniques/ http://mydomain.com/stats/getBrowserShare/ http://mydomain.com/stats/getTopSearchTerms/ http://mydomain.com/stats/getMostVisitedPages/

这几个方法都有用来限制搜集统计数据的时间范围的可选参数(startDate和endDate),对于前面4个方法还可以要求只要特定网页的统计数据。

你希望在整个网站中都显示这些统计数据,但只在用户需要的时候才显示。目前的做法是为每个网页进行手工XHR调用:example

这段代码使用了单例模式的两种较高级的形式,这样可以创建私用属性和方法,接口所需要的那些方法被定义为公用方法。而助理方法则被定义为私用方法。所有公用方法都调用了fetchData这个辅助方法,前面的手工实现版本中那些重复性的代码都被集中到这个方法中。

本例使用远程代理的好处:实现代码与Web服务的耦合变松散,重复代码大大减少。对待StatsProxy对象与对待别的Javascript对象没什么两样,你可以随意用它进行查询。弊端:掩盖了数据来源,实际上还是要对服务器进行访问,需要耗费一定时间。在设计远程代理时需要注明一下这种性能问题。本例中通过借助回调函数进行异步调用稍加缓解,不会因为要等待调用结果而被阻塞。

2.4、包装Web服务的通用模式

我们可以从上面的例子中提炼出一个更加通用的Web服务包装模式。由于Javascript的同源性限制,Web服务代理所包装的服务必须部署在使用代理的网页所在的域中。这里使用的不是一个单例,而是一个拥有构造函数的普通类,以便以后进行扩展。example

2.5、示例:目录查找

为公司网站的主页添加一个可搜索的员工目录。它应该模仿实际的员工花名册中的页面。由于这个网页的访问量很大,所以这个解决方案必须尽量节约带宽,我们不希望这个小小的特性拖累整个网页。

只为那些需要查看员工资料的用户加载这种数据(要知道其数据量相当大)。这是虚拟代理可以大显身手的地方,因为它能够把需要占用大量带宽的资源的加载推迟到必要的时候。同时在加载员工目录的过程中提供一些提示信息。example

要设计一个更复杂的版本,那个初始化检查还可以设计得再健壮一些,实例化过程的触发器也可以设计的更精巧一些。

2.6、创建虚拟代理的通用模式

Javascript是一种非常灵活的语言。得益于此,你可以创建一个动态虚拟代理,它会检查提供给他的类的接口,创建自己的对应方法,并且将该类的实例化推迟到某些预定条件得到满足的时候。

这个动态代理会把本体的实例化推迟到你认为必要的时候。在实例化完成之前,代理的所有公用方法什么事都不会做。这个类可以用来包装那些需要大量计算或较长时间才能实例化的类。example

2.7、代理模式之利

远程代理:可以把远程资源当做本地Javascript对象使用,其益处显而易见。减少了为访问远程资源而不得编写的粘合性代码的数量并且为此提供了单一接口。

虚拟代理:有着截然不同的作用,并不会减少重复性的代码和提高对象的模块性,这与本书所讲的大多数模式都不一样。实际上还会在网页中增加一些代码,而这些代码并非必不可少。在速度比较重要的网页中,虚拟代理可以用来把大对象的实例化推迟到其他元素加载完毕之后。虚拟代理的主要好处就在于:你可以用它代替其本体,而不用操心实例化开销的问题。

2.8、代理模式之弊

不同的代理具有不同的好处,但它们的弊端却是想通的。代理可以掩盖了大量复杂行为。远程代理请求方法的时间比访问本地资源多出几个数量级。此外,远程代理只有在能够与远程资源通信的条件下才能工作。

对于虚拟代理来说,它掩盖了推迟本体的实例化逻辑,使用这种代理的程序员并不清楚有哪些从左会出发对象的实例化。

代理任何时候都可以被替换为本体,它会增加项目的复杂性。除非它能降低你的代码的冗余程度、提高其模块化程度或运行效率,否则不要使用它。如果运用得当,那么代理能够大大简化对资源的访问,这是其他方法难以办到的。

本文作者: arthinking

本文链接: https://www.itzhai.comjavascript-design-patterns-notes-flyweight-proxy-mode.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。