Hope is a good thing, and maybe the best thing of all

编程不止是一份工作,还是一种乐趣!!!

设计模式

结构型

代理模式


WHAT

代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it. 为其他对象提供一种代理以控制对这个对象的访问。

WHY
  • 职责清晰

    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

  • 高扩展性

    具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

WHEN

代理模式的使用场景非常多,大家可以看看Spring AOP,这是一个非常典型的动态代理。

HOW
Examples


适配器模式


WHAT

适配器模式(Adapter Pattern)的定义如下:

Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

WHY
  • 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。

  • 增加了类的透明性

  • 提高了类的复用度

  • 灵活性非常好

WHEN

适配器应用的场景只要记住一点就足够了:你有动机修改一个已经投产中的接口时,适配器模式可能是最适合你的模式。比如系统扩展了,需要使用一个已有或新建立的类,但这个类又不符合系统的接口,怎么办?使用适配器模式。

HOW
Examples


装饰者模式


WHAT

装饰模式(Decorator Pattern)是一种比较常见的模式,其定义如下:

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality. 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。

WHY
  • 优点:

    • 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无须知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。

    • 装饰模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component,实现的还是is-a的关系。

    • 装饰模式可以动态地扩展一个实现类的功能,这不需要多说,装饰模式的定义就是如此。

  • 缺点:

    对于装饰模式记住一点就足够了:多层的装饰是比较复杂的。为什么会复杂呢?你想想看,就像剥洋葱一样,你剥到了最后才发现是最里层的装饰出现了问题,想象一下工作量吧,因此,尽量减少装饰类的数量,以便降低系统的复杂度。

WHEN
  • 需要扩展一个类的功能,或给一个类增加附加功能。

  • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

HOW
Examples


桥接模式


WHAT

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

WHY
  • 优点:

    • 抽象和实现的分离。

    • 优秀的扩展能力。

    • 实现细节对客户透明。

  • 缺点:

    • 增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。

    • 要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。

WHEN
  • 不希望或不适用使用继承的场景

  • 接口或抽象类不稳定的场景

  • 重用性要求较高的场景

HOW

桥接模式有以下几种角色:

  • 抽象角色(Abstraction): 抽象的定义,并保存一个Implementor对象的引用。

  • 扩展抽象角色(RefineAbstraction): 拓展Abstraction。

  • 抽象实现角色(Implementor): 定义实现类的接口,提供基本操作,其实现交给子类实现。

  • 具体实现角色(ConcreteImplementor): 实现Implementor接口,在程序运行时,子类对象将替换其父类对象,提供给Abstraction具体的业务操作方法。

Examples

不同颜色的画笔来不同的形状。


外观模式


WHAT

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用

WHY
  • 优点:

    • 实现了子系统与客户端之间的松耦合关系,减少系统间的相互依赖、提升灵活性与安全性

    • 客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易

  • 缺点:

    • 不符合开闭原则,如果要修改某一个子系统的功能,通常外观类也要一起修改
WHEN
  • 预防低水平人员带来的风险扩散

  • 设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式

  • 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口

  • 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互

HOW
  • Facade:负责子系统的的封装调用

  • Subsystem Classes:具体的子系统,实现由外观模式Facade对象来调用的具体任务

Examples


享元模式


WHAT

享元模式是池技术的重要实现方式,其定义如下:使用共享对象可有效地支持大量的细粒度的对象。

享元模式的定义为我们提出了两个要求:细粒度的对象和共享对象。要求细粒度对象,那么不可避免地使得对象数量多且性质相近,那我们就将这些对象的信息分为两个部分:内部状态(intrinsic)与外部状态(extrinsic)

  • 内部状态:是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变。

  • 外部状态:是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,它是一批对象的统一标识,是唯一的一个索引值。

WHY

享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存的占用,增强程序的性能,但它同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。

WHEN
  • 系统中存在大量的相似对象

  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。

  • 需要缓冲池的场景

HOW
Examples


行为型

模板方法模式


WHAT

模板方法模式(Template Method Pattern)是如此简单,以致让你感觉你已经能够掌握其精髓了。其定义如下:

Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure. 定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

WHY
  • 优点:

    • 封装不变部分,扩展可变部分

      把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。

    • 提取公共部分代码,便于维护

    • 行为由父类控制,子类实现

      基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

  • 缺点:

    按照设计原则,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响。

WHEN
  • 多个子类有公有的方法,并且逻辑基本相同时。

  • 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。

  • 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

HOW
Examples

泡茶、煮咖啡


观察者模式


WHAT

观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe),它是一个在项目中经常使用的模式,其定义如下:

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically. 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

WHY
  • 优点:

    • 观察者和被观察者之间是抽象耦合

      如此设计,则不管是增加观察者还是被观察者都非常容易扩展,而且在Java中都已经实现的抽象层级的定义,在系统扩展方面更是得心应手。

    • 建立一套触发机制

      根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢?比如,我们去打猎,打死了一只母鹿,母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘……这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。

  • 缺点:

    观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。多级触发时的效率更是让人担忧,大家在设计时注意考虑。

WHEN
  • 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。

  • 事件多级触发场景。

  • 跨系统的消息交换场景,如消息队列的处理机制。

HOW
  • Subject被观察者

    定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。

  • Observer观察者

    观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。

  • ConcreteSubject具体的被观察者

    定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。

  • ConcreteObserver具体的观察者

    每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。

Examples

libevent,基于zookeeper的事件驱动框架


访问者模式


WHAT

访问者模式是一个相对简单的模式,其定义如下:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)

WHY
  • 优点:

    • 符合开闭,单一职责,对象结构比较稳定

    • 优秀的扩展性与灵活性

  • 缺点:

    • 具体元素对访问者公布细节

    • 违背了依赖倒置转原则,Visitor依赖具体的元素了。

WHEN
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,需要避免这些操作“污染”这些对象的类。

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。

HOW
  • Visitor

    接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。

  • ConcreteVisitor

    具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。

  • Element

    元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。

  • ElementA、ElementB

    具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

  • ObjectStructure

    定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。

Examples

Pracbiz B2B系统,对PO、INV、RN等类型的文档,进行加密、签名、格式转换等操作。


中介者模式


WHAT

用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

WHY
  • 优点:

中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。

  • 缺点:

中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

WHEN

中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单。

HOW
  • Mediator:抽象中介者角色定义统一的接口,用于各同事角色之间的通信。

  • Concrete Mediator:具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色。

  • Colleague:每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。每个同事类的行为分为两种:一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖;第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)。

Examples


备忘录模式


WHAT

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

WHY

由于备忘录模式有太多的变形和处理方式,每种方式都有它自己的优点和缺点,标准的备忘录模式很难在项目中遇到,基本上都有一些变换处理方式。因此,我们在使用备忘录模式时主要了解如何应用以及需要注意哪些事项就成了。

WHEN
  • 需要保存和恢复数据的相关状态场景。

  • 提供一个可回滚(rollback)的操作;比如Word中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的backspace键等。

  • 需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般的做法是备份一个主线程中的对象,然后由分析程序来分析。

  • 数据库连接的事务管理就是用的备忘录模式,想想看,如果你要实现一个JDBC驱动,你怎么来实现事务?还不是用备忘录模式嘛!

HOW
Examples


命令模式


WHAT

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

WHY
  • 优点:

    • 类间解耦

      调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。

    • 可扩展性

      Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合。

    • 命令模式结合其他模式会更优秀

      命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题

  • 缺点:

    命令模式也是有缺点的,请看Command的子类:如果有N个命令,问题就出来了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项目中慎重考虑使用。

WHEN

只要你认为是命令的地方就可以采用命令模式,例如,在GUI开发中,一个按钮的点击是一个命令,可以采用命令模式。

HOW
  • Receive接收者角色

    该角色就是干活的角色,命令传递到这里是应该被执行的,具体到我们上面的例子中就是Group的三个实现类。

  • Command命令角色

    需要执行的所有命令都在这里声明。

  • Invoker调用者角色

    接收到命令,并执行命令。

Examples


创建型