结构型模式(下)

124 阅读13分钟

结构型模式(上)

装饰模式

装饰模式,动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更加灵活。装饰模式是一种对象结构型模式

image.png

装饰模式结构图包含 4 个角色

  • 抽象构件(Component),它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法
  • 具体构件(ConcreteComponent),它是抽象构件类的子类,用于定义具体的构件对象,它实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责
  • 抽象装饰类(Decorator),它是抽象构件类的子类,用于给具体构件类增加职责,但是具体的职责在其子类中实现
  • 具体装饰类(ConcreteDecorator),它是抽象装饰类的子类,负责向构建添加新的职责

透明装饰模式,要求客户端完全针对抽象编程。装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型

透明装饰模式可以让客户端透明的使用装饰之前的对象和装饰之后的对象,无需关心它们的区别


透明装饰模式的设计难度较大,而且有时用户需要单独调用新增的业务方法。为了能够调用到新增方法,不得不用具体装饰类型来定义装饰之后的对象,而具体构件类型还是可以使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式

装饰模式的注意事项

  • 尽量保持装饰类的接口和被装饰类的接口相同。也就是,在可能得情况下,应该尽量使用透明装饰模式
  • 尽量保持具体构建类是一个「轻」类,不要把太多的行为放在具体构件类中,可以通过装饰类对其进行扩展
  • 如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类

装饰模式的优点

  • 对于扩展一个对象的功能,装饰模式比继承模式更加灵活性,不会导致类的个数急剧增加
  • 可以通过一种动态的方式来扩展一个对象的功能
  • 可以对一个对象进行多次装饰
  • 具体构件类和具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无需改变,符合开闭原则

装饰模式的缺点

  • 使用装饰模式进行系统设计时将产生很多小对象。这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同
  • 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承增加易于出错,排查错误更加困难

装饰模式的适用场景

  • 在不影响其它对象的情况下,以动态、透明的方式给单个对象添加职责
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时,可以使用装饰模式

外观模式

根据单一职责原则,在软件中将一个系统划分为若干个子系统,有利于降低整个系统的复杂性。一个常见的设计目标是使客户类与子系统之间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观角色,它为子系统的访问提供了一个简单而单一的入口

外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观角色进行

外观模式,外部与一个子系统的通信通过一个统一的外观角色进行,为子系统中的一组接口提供一个一致的入口。外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式

image.png

image.png

外观模式结构图包含两个角色

  • 外观角色(Facade),在客户端可以调用这个角色的方法,在外观角色中可以知道相关的子系统的功能和责任
  • 子系统角色(SubSystem),在软件系统中可以有一个或者多个子系统角色。

外观模式的主要目的在于降低系统的复杂程度。在外观模式下,增加新的子系统或者移除子系统都非常方便,客户端无需进行修改,只需要在外观类中增加或者移除对子系统的引用即可

外观模式的优点

  • 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目并使得子系统使用起来更加容易
  • 实现了子系统和客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
  • 一个子系统的修改对其它子系统没有任何影响,而且子系统内部变化也不会影响到外观对象
  • 只是只是提供了一个访问子系统的统一入口,并不会影响客户端直接使用子系统类

外观模式的缺点

  • 不能很好的限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,这违背了开闭原则

外观模式的适用场景

  • 当要为访问一系列复杂的子系统提供一个简单入口时,可以使用外观模式
  • 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间 不直接产生联系,而通过外观类建立联系,降低层之间的耦合度

享元模式

享元模式通过共享技术实现相同或相似对象的重用。在逻辑上每个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象。这些对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例。在享元模式中,存储这些共享实例对象的地方称为「享元池」

享元模式以共享的方式高效的支持大量细粒度对象的重用。享元对象能做到共享的关键是区分了内部状态和外部状态

  • 内部状态,存在在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享
  • 外部状态,是随环境改变而改变的、不可以共享的状态

享元模式,运用共享技术有效的支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又被称为轻量级模式,是一种对象结构型模式

image.png

享元模式结构图包含 4 个角色

  • 抽象享元类(Flyweight),通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据。同时也可以通过这些方法来设置外部数据
  • 具体享元类(ConcreteFlyweight),它实现了抽象享元类,其实例称为享元对象。在具体享元类中为内部状态提供了存储空间。通常,可以结合单例模式来设计具体享元类,为每个具体享元类提供唯一的享元对象
  • 非共享具体享元类(UnsharedConcreteFlyweight),并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类
  • 享元工厂类(FlyweightFactory),用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中

标准的享元模式结构图中既包含可以共享的具体享元类,也可以包含不可以共享的非共享具体享元类。

  • 单纯享元模式:所有的具体享元类都是可以共享的,不存在非共享具体享元类

image.png

  • 复合享元模式,将一些单纯享元模式对象适用组合模式加以组合,形成复合享元对象,这些复合享元对象本身不能共享,但是它们可以包含单纯享元对象,而后者则可以共享

image.png


享元模式通常需要和其它模式一起联用

  • 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象
  • 在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计
  • 享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态

享元模式的优点

  • 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享

享元模式的缺点

  • 享元模式需要分离出内部状态和外部状态,从而使得系统变得复杂,这使得程序的逻辑复杂化
  • 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长

享元模式的适用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源。因此,在需要多次重复使用同一个享元对象时,才值得使用享元模式

代理模式

代理模式,给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式是一种对象结构型模式

代理模式的核心是代理类

image.png

代理模式结构图包含 3 个角色

  • 抽象主题角色(Subject),它声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程
  • 代理主题角色(Proxy),代理主题角色内部包含了对真实主题的引用,从而可以在任何时候操作真实主题对象
  • 真实主题角色(RealSubject),它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作

代理模式根据其目的和实现方式不同可分为很多种类,其中常用的几种代理模式

  • 远程代理,为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一个主机中,也可以在另一台主机中
  • 虚拟代理,如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来标识,真实对象只在需要时才会被真正创建
  • 保护代理,控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
  • 缓冲代理,为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
  • 智能引用代理,当一个对象被引用时,提供一些额外的操作

代理模式的优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度,满足迪米特法则
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无需修改源代码,符合开闭原则
  • 远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提供系统的整体运行效率
  • 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
  • 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限

代理模式的缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂

代理模式的适用场景

  • 当客户端的对象需要访问远程主机中的对象时,可以使用远程代理
  • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销,缩短运行时间时,可以使用虚拟代理
  • 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时,可以使用保护代理
  • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时,可以使用缓冲代理
  • 当需要为一个对象的访问提供一些额外的操作时,可以使用智能引用代理

结构型模式(上)