1 - CH01-创建型-简单工厂

创建型模式概述

创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道他们的共同接口,而不清楚其具体细节,使整个系统的设计更加符合单一职责原则。

该模式在创建什么(what)、由谁创建(who)、何时创建(when)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。

简单工厂(Simple Factory Pattern)

模式动机

比如一个软件系统可以提供多个外观不同的按钮(圆形、矩形、菱形等),这些按钮都源自同一个基类,在继承基类后不同的子类修改了部分属性从而使得他们可以呈现不同的外观,如我们在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把参数传入该方法即可返回一个相应的按钮对象,这时就可以使用该模式。

模式定义

又称为静态工厂方法(Static Factory Method),它属于类创建型模式。该模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,这些被创建的实例通常拥有共同的父类。

模式结构

简单工厂模式包含如下角色:

  • Factory:工厂角色

    负责实现创建所有实例的内部逻辑。

  • Produce:抽象产品角色

    是创建的所有对象的父类,负责描述所有实例所共有的公共接口

  • ConcreteProduce:具体产品角色

    是创建目标,所有创建的对象多充当这个角色某个具体类的实例

类图

NAME

时序图

NAME

代码实例

C++

#include "Factory.h"
#include "ConcreteProductA.h"
#include "ConcreteProductB.h"
Product* Factory::createProduct(string proname){
	if ( "A" == proname )
	{
		return new ConcreteProductA();
	}
	else if("B" == proname)
	{
		return new ConcreteProductB();
	}
	return  NULL;
}

模式分析

  • 对象的创建对象的业务处理分离可以降低系统的耦合度,使得两者修改起来更简便。
  • 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用方便,可通过类名直接调用,而且只需要传入一个简单的参数即可。在实际开发中,还可以在调用时将所传入的参数保存在 XML 等配置文件中,修改参数时无需修改任何源代码。
  • 该模式最大的问题在于工厂类的职责过重,增加新的产品需要修改工厂类的逻辑判断,这一点与开闭原则是相违背的。
  • 该模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获得你需要的对象,而无需知道其创建细节。

优点

  • 工厂类含有必要的逻辑判断,可以决定什么时候(条件)创建什么类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”(使用)产品。通过这种方式实现对责任的分割,提供专门的工厂类用于创建对象。
  • 客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类对应的参数即可,对于一些复杂的类名,通过该模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换个增加新的产品类,在一定程度上提高了系统的灵活性。

缺点

  • 由于工厂类中集中了所有产品类的创建逻辑,一旦不能正常工作,将影响整个系统。
  • 该模式将会增加系统中类的个数,在一定程度上增加了系统的复杂度和立即难度。
  • 系统扩展困难,一旦添加新的产品类则必须修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑较为复杂,不利于系统的扩展和维护。
  • 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

适用场景

  • 工厂类负责创建的对象比较少:由于创建的对象类别少,不会造成工厂方法中的业务逻辑太过复杂。
  • 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,也不需要知道类名,只需要知道该类型需要的参数。

模式应用

  1. JDK 类库中广泛使用了该模式,如工具类java.text.DateFormat,用于格式化一个本地日期:

    public final static DateFormat getDateInstance();
    public final static DateFormat getDateInstance(int style);
    public final static DateFormat getDateInstance(int style, Local local);
    
  2. Java 加密技术:

    1. 获取不同加密算法的密钥生成器:

      KeyGenerator keyGen = KeyGenerator.getInstance("DESede");
      
    2. 创建密码器:

      Cipher cp = Cipher.getInstance("DESede");
      

总结

  • 创建型模式对类的实例化过程进行了抽象,能够将对象的创建与对象的使用分离。
  • 简单工厂模式又称为静态工厂方法模式,属于类创建型模式。在该模式中,可以根据参数的不同返回不同类的实例。同时专门定义了一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
  • 包含三个角色:工厂角色负责实现创建所有实例的内部逻辑;抽象产品角色是需要创建的所有类的父类,负责描述所有实例所共有的公共接口;具体产品角色是创建目标,所有创建的对象都充当这个角色某个具体类的实例。
  • 要点在于:当你需要什么,只需要传入对应正确的参数,就可以获得对应的对象,而无需知道创建细节。
  • 有点在于:将对象的创建与使用分离,将对象的创建交给工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品则需要修改工厂的判断逻辑,当产品较多时,逻辑将会复杂。
  • 适用场景:工厂类需要创建的对象比较少;客户端只知道传入工厂类的参数,对于创建过程不关心。

2 - CH02-创建型-工厂方法

模式动机

针对“1-简单工厂”中提到的系统进行修改,不再使用一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成。首先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形、矩形、菱形按钮等,这些具体的工厂类会实现抽象工厂类中定义的方法。

这种抽象化的结构可以在不修改具体工厂类的情况下引入新的产品,如果出现新的按钮类型,只需要为这种新的按钮类型创建对应的工厂类即可获得该新按钮的实例。因此,使得工厂方法模式具有超越简单工厂的优越性,更加符合开闭原则

模式定义

**工厂方法模式(Factory Method Pattern)**又称工厂模式,或虚拟构造器(Virtual Constructor)模式、多态工厂(Polymorphic Factory),属于类创建型模式。该模式中,工厂父类负责负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪个具体产品类。

模式结构

工厂方法包含的角色:

  1. Product:抽象产品
  2. ConcreteProduct:具体产品
  3. Factory:抽象工厂
  4. ConcreteFactory:具体工厂

类图

NAME

时序图

NAME

代码示例

#include "ConcreteFactory.h"
#include "ConcreteProduct.h"

Product* ConcreteFactory::factoryMethod(){

	return  new ConcreteProduct();
}
#include "Factory.h"
#include "ConcreteFactory.h"
#include "Product.h"
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
	Factory * fc = new ConcreteFactory();
	Product * prod = fc->factoryMethod();
	prod->use();
	
	delete fc;
	delete prod;
	
	return 0;
}

模式分析

由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。在该模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这个细节,使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

实例

日志记录器

某系统日志记录器要求支持多种日志记录方式,如文件、数据库等。且用户可以根据要求动态选择日志记录方式,使用工厂方法进行设计:

类图:

NAME

时序图:

NAME

优点

  • 该模式中,工厂方法用来创建用户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。
  • 在加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,只需要添加一个具体工厂和具体产品就可以了。增加了扩展性,符合开闭原则。

缺点

  • 在添加新产品时需要编写新的产品类,还要提供于此对应的具体工厂类,类的个数会成对增加,一定程度上增加了复杂性。
  • 引入了抽象层,客户端的代码均使用抽象层进行定义,增加了系统的抽象性和理解难度。

适用场景

  • 无需知道需要创建对象的类:该模式中,客户端不需要知道具体产品类的类名,只需要知道他对应的工厂即可,具体的产品对象由具体工厂类创建。
  • 通过子类来指定创建哪个对象:该模式中,对于抽象工厂类只需要提供一个创建产品的接口,由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏替换原则,在程序运行时,子类对象将覆盖父类对象,从而使系统更易扩展。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时无需关心是哪个工厂子类来创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

模式应用

JDBC 中的工厂方法:

Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");

模式扩展

  • 使用多个工厂方法:才抽象工厂中可以定义多个工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以包含不同的业务逻辑,以满足不同的产品对象的需求。
  • 产品对象的重复使用:工厂对象将已创建过的产品保存到一个集合,然后根据客户端对产品的请求,对集合进行查询。
  • 多态性的丧失和模式的退化:如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,此时就不再是工厂方法模式了。工厂对象应该有一个抽象的父类型,如果工厂等级结构中只有一个工厂类的话,抽象工厂可以省略,则发生退化;当只有一个具体工厂,在具体工厂中创建所有产品对象,并且工厂方法设计为静态方法时,该模式就退化成了简单工厂模式。

总结

  • 工厂方法模式又称为工厂模式,属于类创建型模式。该模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,以将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
  • 工厂方法模式包含四个角色:
    1. 抽象产品是定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;
    2. 具体产品实现了抽象产品接口,某种类型的具体具体产品由专门的具体工厂创建,他们之间往往一一对应
    3. 抽象工厂中声明了工厂方法,用于返回一个产品,它是该模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;
    4. 具体工厂是抽象工厂的子类,实现了抽象工厂中定义的方法,并可由客户调用,返回一个具体产品类的实例。
  • 该模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,同时克服了它的缺点。核心的工厂类不再负责创建所有类型的产品,而是将其创建交给具体子类去做。该核心类仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得该模式可以允许系统在不修改工厂角色的情况下引进新产品。
  • 主要优点是增加新产品而无需修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;缺点在于增加新产品的同时需要增加对应的具体工厂,导致类的个数增长,一定程度上增加了系统的复杂性。
  • 该模式适合的情况包括:一个类不知道他所需要的对象的类;一个类通过其子类来指定创建哪个对象;将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态绑定。

3 - CH03-创建型-抽象工厂

模式动机

  • 在工厂方法模式中,具体工厂负责生成具体的产品,每个具体工厂对应一个具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或一组重载的工厂方法。但是有些时候我们需要一个工厂能够提供多个产品对象,而不是一个单一的产品对象。

    为了清晰理解工厂方法模式,需要知道两个概念:

    1. 产品等级结构:产品等级结构即产品的继承结构(如一个抽象类是电视机,其子类有:海尔、海信、TCL等,抽象电视机与对应品牌的电视机即构成了产品等级结构)。
    2. 产品族:在抽象工厂模式中,产品族是指由一个工厂生产的,位于不同产品等级结构中的一组产品。如海尔电器工厂生产的电视机、电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
  • 当系统提供的工厂需要生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中、属于不同类型的具体产品时,需要使用抽象工厂模式。

  • 该模式是所有形式的工厂模式中最为抽象和最具一般性的形态。

  • 抽象工厂模式与工厂方法模式最大的区别在于:工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂等级结构,可以负责多个不同产品等级结构中的产品对象的创建。当一个工厂等级结构(海尔电器工厂),可以创建出分属不同产品等级结构的 一个产品族中的所有对象(电视机、电冰箱)时,抽象工厂模式比工厂方法模式更有效率。

模式定义

抽象工厂模式(Abstract Factory Pattern):提供一个 创建一系列相关或相互依赖对象 的接口,而无需指定他们的具体类。又称为 Kit 模式。

模式结构

包含如下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

类图

NAME

解释:

  • AbstractFactory 可以理解为 电器工厂;
  • ConcreteFactory1 可以理解为 海尔电器工厂;
  • ConcreteFactory2 可以理解为 格力电器工厂;
  • AbstractProductA 理解为产品 电视机;
  • AbstractProductB 理解为产品 电冰箱;
  • ProductA1 则为 海尔牌电视机;
  • ProductA2 则为 格力牌电视机;
  • ProductB1 则为 海尔牌电冰箱;
  • ProductB2 则为 格力牌电视机;

因此,海尔电器工厂 可以生产 海尔牌的电视机和电冰箱,同理格力电器工厂。这样,产品等级结构(电视机、电冰箱)和产品族(海尔牌、格力牌)通过两种维度实现对产品的建模。

时序图

NAME

优点

  • 抽象工厂模式隔离了具体类的生产,使得客户端并不知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以从某种程度上改变整个系统的行为。同时该模式可以实现高内聚低耦合的实际目的,因此该模式被广泛应用。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
  • 增加新的具体工厂和产品族非常方便,无需修改已有系统,符合开闭原则

缺点

  • 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就要对该接口进行扩展,这会涉及到对抽象工厂角色及其所有子类进行修改,会带来较大不便。
  • 开闭原则的倾斜性,增加新的工厂和产品族容易,增加新的产品等级结构麻烦。

适用场景

  • 当一个系统不需要依赖于产品类实例如何被创建、组合、表达的细节,这对于所有形式的工厂模式都是重要的。
  • 系统中有多于一个的产品族,而每次只适用其中一个产品族。
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

模式应用

比如一些软件系统中需要更换系统主题,要求界面中的按钮、文本框、背景等一起发生改变时,就可以使用该模式。比如:按钮元素的不同形式构成一个产品等级结构,不同元素的同一主题形式构成一个产品族。

模式扩展

开闭原则的倾斜性:

  • 开闭原则要求对系统扩展开发,对修改关闭,通过扩展达到增强其功能的目的。对于涉及到多个产品族与多个产品等级结构的系统,其功能增强则包括两方面:
    1. 增加产品族:对于增加新的产品族,本模式很好的支持了开闭原则,只需要增加一个新的具体工厂即可,对已有代码无需做任何修改;
    2. 增加产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生成新产品的方法,不能很好的支持开闭原则。
  • 抽象工厂模式的这种性质称为开闭原则的倾斜性,该模式以一种倾斜的方式来支持增加新的产品,为新产品族的增加提供方便,但不能为新的产品等级结构增加提供方便。

工厂模式退化:

  • 当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,该模式也就退化成了工厂方法模式;
  • 当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建产品的方法设计为静态方法时,工厂方法模式退化成简单工厂模式。

总结

  • 该模式提供了一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。
  • 包含四种角色。
  • 是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
  • 主要优点是隔离了具体类的生成,使得客户端不知道什么被创建。
  • 。。。

4 - CH04-创建型-建造者

模式动机

一些复杂的对象,拥有多个组成部分,比如汽车,包括车轮、方向盘、发动机等。对于大多数用户而言,无需知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车。可以通过建造者模式对其进行设计与描述,建造者模式可以将其部件和其组装过程分开,逐步创建一个对象。用户只需要指定复杂对象的类型就可以得到该对象,而无需知道其内部的具体构造细节。

软件系统中也存在大量类似汽车的复杂对象,拥有一系列成员和属性,这些成员属性中有些是引用类型的成员对象。而且这些复杂对象中,还可能存在一些限制条件,如某些属性赋值则复杂对象不能作为一个完整的产品使用;有些属性的赋值必须按照某个顺序,一个属性没有赋值之前,另一个属性可能无法赋值等。

复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程。由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称为建造者的对象里,建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无需关心该对象所包含的属性以及他们的组装方式,这就是建造者模式的模式动机。

模式定义

建造者模式(Builder Pattern),将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。

该模式是逐步创建一个复杂对象,允许用户只通过指定复杂对象的类型和内容就可以构建他们,而不需要知道他们内部的具体构建细节。又称为生成器模式

模式结构

  • Builder:抽象建造者
  • ConcreteBuilder:具体建造者
  • Director:指挥者
  • Product:产品角色

类图

NAME

时序图

NAME

代码示例

抽象建造者:

public interface PersonBuilder{
  void buildHead();
  void buildBody();
  void buildFoot();
  Persion buildPerson();
}

具体建造者 1:

public class ManBuilder implements PersonBuilder{
  Person person;
  public ManBuilder() {
    person = new Man();
  }
  public void buildBody() {
    person.setBody("set man's body");
  }
  public void buildFoot() {
    person.setFoot("set man's foot");
  }
  public void buildHead() {
    person.setHead("set man's head");
  }
  public Person buildPerson() {
    return person;
  }
}

具体建造者 2:

public class WomanBuilder implements PersonBuilder{
  Person person;
  public WomanBuilder() {
    person = new Woman();
  }
  public void buildBody() {
    person.setBody("set woman's body");
  }
  public void buildFoot() {
    person.setFoot("set woman's foot");
  }
  public void buildHead() {
    person.setHead("set woman's head");
  }
  public Person buildPerson() {
    return person;
  }
}

指挥者:

public class PersonDirector {
  public Person constructPerson(PersonBuilder pb){
    pb.buildHead();
    pb.buildBody();
    pb.buildFoot();
    return pb.buildPerson();
  }
}

产品:

public class Person {
  private String head;
  private String body;
  private String foot;
  
  public void setHead(String head){
    this.head = head;
  }
  public void setBody(String body){
    this.body = body;
  }
  public void setFoot(String foot){
    this.foot = foot;
  }
}

public class Man extends Person {
  public Man() {
    System.out.println("Start building Man");
  }
}

public class Woman extends Person {
  public Woman() {
    System.out.println("Start building Woman");
  }
}

用例:

public class Usage{
  public static void main(String[] args){
    PersonDirector pd = new PersonDirector();
    Person manPerson = pd.constructPerson(new ManBuilder());
    Person womanPerson = pd.constructPerson(new WomanBuilder());
  }
}

模式分析

抽象建造者类中定义了产品的创建方法和返回方法。

建造者模式的结构中还引入了一个指挥者类 Director,该类作用主要有两个:一方面将客户端与产品生产过程;另一方面负责整个产品的生产过程。

指挥者针对抽象建造者编程,客户端只需要提供具体建造者的类型,即可通过指挥者来调用具体建造者的方法,返回一个完整的产品对象。

在客户端代码中,则不需要关心产品具体的生产过程,只需要提供具体建造者类型来调用指挥者的创建方法。建造者模式将复杂对象的构建与对象的表现分离开来,这使得同样的构建过程可以创建出不同的表现。

实例

KFC 套餐

套餐看做是一个复杂对象,一般包括主食(汉堡、鸡肉卷等)和饮料(果汁、可乐等)等组成部分,服务员可以根据要求,逐步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。

类图:

NAME

优点

  • 客户端不必知道产品内部的组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程(由父类创建者约束了创建该过程)可以创建不同的产品对象。
  • 每一个具体建造者都相对独立,与其他的具体建造者无关,因此可以很方便的替换具体建造者或者增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
  • **可以更加精细的控制产品的创建过程。**将复杂产品的的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
  • **增加新的具体创建者无需修改原有代码,指挥者针对抽象建造者编程。**系统扩展方便,符合开闭原则。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。
  • 如果产品的内部变化复杂,可能需要定义很多具体建造者来实现这些变化,导致系统庞大。

适用场景

  • 需要生产的产品有复杂的内部结构,产品通常包含多个成员属性。
  • 需要生产的额产品,其属性相互依赖,需要制定生成顺序。
  • 对象的创建过程独立于创建该对象的类。在该模式中引入了指挥者类,将创建过程封装在指挥者类中,而不再建造者中。
  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

模式扩展

建造者模式的简化:

  • 省略掉抽象建造角色:如果系统中只需要一个具体建造者的话。
  • 省略掉指挥者:在具体建造者只有一个的情况下,如果抽象建造者以及被省略掉,那么还可以省略掉指挥者,让建造者充当指挥者与建造者角色。

建造者模式与抽象工厂模式的比较:

  • 建造者返回一个组装好的完整产品,抽象工厂返回一系列相关的产品,这些产品位于不同的产品等级结构,构成一个产品族。
  • 抽象工厂中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象;建造者中,客户端通过指挥类而不是调用建造者的相关方法,侧重于逐步构造一个复杂对象。
  • 如果将抽象工厂看做是“汽车配件生产工厂”,生产一个产品族的产品;建造者模式就是一个“汽车组装工厂”,通过对部件的组装返回一辆完整的汽车。

5 - CH05-创建型-单例

模式动机

对于系统中的某些类来说,只有一个实例很重要。比如,系统中可以有多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器和文件系统;一个系统只能有一个计时器或 ID 生成器。

方式是让类自身负责保存它的唯一实例,保证没有其他实例被创建,并且提供一个访问该实例的方法。

模式定义

单例模式(Singleton Pattern),确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:类只能有一个实例;必须自行创建这个实例;必须向整个系统提供这个实例。

模式结构

单例模式仅有一个角色:Singleton

类图

NAME

时序图

NAME

代码实例

模式分析

单例模式的目的是保证一个类仅有一个实例,并提供一个访问该实例的全局访问点。单例模式的角色只有一个,就是单例类-Singleton。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。该模式中包含一个静态私有成员变量与静态公共工厂方法,工厂方法负责检验实例是否存在并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

在单例模式的实现过程中,需要注意一下要点:

  • 单例类的构造函数为私有;
  • 提供一个自身的静态私有成员变量;
  • 提供一个共有的静态工厂方法。

优点

  • 提供了对唯一实例的受控访问。因为单例类封装了它唯一的实例,所以他可以严格控制客户端怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,该模式可以提高系统性能。
  • 允许可变数目的实例。可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。

缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了单一职责原则。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当产品角色,包含一些业务方法,将产品的创建和产品本身的功能融合到了一起。
  • 滥用单例将带来一些负面问题,比如,为了节省资源将数据库连接池对象设计成单例类,可能会导致共享该连接池对象的程序过多而出现连接池溢出;如果该对象长期不使用将被 GC 回收,导致对象状态丢失。

适用场景

  • 系统中只需要一个实例对象,如提供一个序列号生成器,或资源消耗太大而只能创建一个实例。
  • 客户端调用类的单个实例只允许适用一个公共访问点,不能通过其他途径访问该实例。
  • 系统中要求一个类只能有一个实例时才应当使用单例模式。

6 - CH06-结构型-适配器

模式动机

  • 在软件开发中采用类似于电源适配器的设计和编码技巧被称为适配器模式。
  • 通常,客户端可以通过目标类的接口访问他所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是现有类中方法名与目标类中定义的方法名不一致等原因导致的。
  • 这时,现有的接口要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类提供的功能,适配器用于完成这些转化。
  • 在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类即为适配器(Adapter),他所包装的对象就是适配者(Adaptee),即被适配的类。
  • 适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口调用。也就是说:当客户类调用适配器的方法时,在适配器的内部将调用适配者的方法,而这个过程对客户端是透明的,客户端并不直接访问适配者类。因此,适配器可以使因为接口不兼容而不能交互的类完成兼容。即该模式的动机。

模式定义

适配器模式(Adapter Pattern),将一个接口转换为客户端期望的另一个接口,使接口不兼容的类可以在一起工作,别名为包装器(Wrapper)。该模式即可以作为类接口型模式,也可以作为对象结构型模式。

模式结构

包含如下角色:

  • Target:目标抽象类
  • Adapter:适配器类
  • Adaptee:适配者类
  • Client:客户类

类图

分为对象适配器类适配器两种实现:

对象适配器

NAME

类适配器

NAME

时序图

NAME

代码分析

对象适配器:

int main(int argc, char *argv[]){
  Adaptee * adaptee = new Adaptee();
  Target * target = new Adapter(adaptee);
  target->request();
  return 0;
}

优点

  • 将目标类与适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无需修改原有代码;
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性很好,通过使用配置文件,可以方便的更换适配器,也可以在不修改原有代码的基础上增加的的适配器类,完全符合“开闭原则”。

类适配器模式的独特优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式的独特优点:

​ 一个对象适配器可以把多个不同的适配者适配到同一目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

  • 类适配器的缺点:对于 Java、C# 等不支持多重继承的语言,一次只能适配一个适配者类,而且目标类只能为抽象类,不能为具体类,有一定局限性,不能将一个适配者类和其子类都适配到目标接口。
  • 对象适配器的缺点:对类适配器相比,要想置换适配者类的方法不方便。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后把这个子类当做真正的适配者进行适配,实现复杂。

适用场景

  • 需要使用现有类,而这些类的接口不符合系统的需要。
  • 想要建立一个可重复使用的类,用于与一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作。

模式扩展

默认适配器模式(Default Adapter Pattern),或称缺省适配器模式。当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类则可以有选择的覆盖父类的某些方法来实现需求。适用于一个接口不想使用其所有的方法的情况,因此也称为单接口适配器模式。

7 - CH07-结构型-桥接

模式动机

设想如果要绘制矩形、圆形、椭圆、正方形,至少需要四种形状类,但是如果绘制的图形具有不同的颜色,此时至少有以下两种设计方案:

  1. 为每一种形状都提供一套各种颜色的版本
  2. 根据实际需要对形状和颜色进行组合

对于有两个变化维度的系统,采用第二种方案进行设计则类的个数更少,系统扩展也更方便。方案二即为桥接模式,该模式将继承关系转换为关联关系,减少耦合和编码量。

模式定义

桥接模式(Bridge Pattern),将抽象部分与它的实现部分分离,使他们都可以独立变化。又称为柄体模式(Handle and Body)接口模式(Interface)

模式结构

包含四种角色:

  • Abstraction:抽象类
  • RefinedAbstraction:扩充抽象类
  • Implementor:实现类接口
  • ConcreteImplementor:具体实现类

类图

NAME

时序图

NAME

代码分析

int main(int argc, char argv[]){
  Implementor * pImp = new ConcreteImplementorA();
  Abstraction * pa = new RefinedAbstraction();
  pa->operation();
  
  Abstraction * pb = new RefinedAbstraction(new ConcreteImplementorB());
  pb->operation();
  ...
}

模式分析

理解桥接模式,重点需要理解如何将抽象化(Abstraction)和实现化(Implementation)解耦,使得二者可以独立的变化。

  1. 抽象化:抽象化就是忽略一些信息,把不同的实体当做同样的实体对待,将对象的共同性质抽取出来形成类的过程即为抽象化;
  2. 解耦:解耦就是将抽象化和实现化之间的耦合解脱开,或者将他们之间的强关联转换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的解耦,是指在一个软件系统的抽象化和实现化之间使用关联关系(组合、聚合),而不是继承关系,从而使两者可以相互独立的变化,也就是桥接模式的动机。

实例

如果需要开发一个跨平台的视频播放器,可以在不同操作系统平台上播放多种格式的视频文件,常见的视频格式包括 MPEG、RMVB、AVI、WMV 等。

优点

  • 分离抽象接口及其实现部分。
  • 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(一个类只有一个变化的原因),复用性较差,而且多继承中类的个数非常庞大,桥接则是更好的方法。
  • 提高了系统的可扩充性,在两个变化维度任意扩展一个维度,都不需要修改原有的系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。

缺点

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  • 要求正确识别出系统中两个独立变化的维度,使用范围有一定局限性。

适用场景

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多灵活性,避免在两个层次之间建立静态继承关系,通过桥接模式可以使他们在抽象层建立一个关联关系。
  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需要独立扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和实现化角色需要独立变化,设计要求独立管理这两者。
  • 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

模式扩展

适配器与桥接模式联用:

​ 桥接模式和适配器模式用于设计的不同阶段。桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使他们可以进行分别变化;在初步设计完成后,当发现系统与已有类无法协同工作时,可以使用适配器模式。有时也需要在设计初期使用适配器模式,尤其是在那些涉及大量第三方应用接口的时候。

8 - CH08-结构型-装饰器

模式动机

一般有两种方式可以实现给一个类或对象增加行为:

  1. 继承机制:通过继承一个类,使子类在拥有自身方法的同时拥有父类的方法。但是这种方式是静态的,用户不能控制增加行为的方式和时机。
  2. 关联机制:即将一个类的对象嵌入到另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,这个嵌入的被称为装饰器。

装饰器模式以客户透明的方式动态给一个对象附加上更多的责任,客户端并不会觉得对象在装饰前后有什么不同。可以在不创建更多子类的情况下扩展对象的功能。即该模式的动机。

模式定义

装饰器模式(Decorator Pattern),动态给对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比创建子类的实现更为灵活。或称为“包装器”,与适配器别名相同但应用场景不同,或称“油漆工模式”。

模式结构

包含四种角色:

  • Component:抽象构件
  • ConcreteComponent:具体构件
  • Decorator:抽象装饰类
  • ConcreteDecorator:具体装饰类

类图

NAME

时序图

NAME

代码分析

模式分析

  • 与继承关系相比,关联关系的主要优势在于不会破坏的类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展。在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了维护阶段,由于关联关系具有较好的松耦合性,因此系统会更易维护。而关联关系的缺点则是会比继承关系要多创建更多对象。
  • 使用装饰器模式来实现扩展比继承更加灵活,它以对客户透明的方式动态的给一个对象附加更多的责任。该模式可以在不创建更多子类的情况下,扩展对象功能。

实例

变形金刚

变形金刚在变型之前是一辆汽车,它可以在陆地上移动。变型之后除了能够移动外还可以说话;或者变成飞机飞行。

类图:

NAME

时序图:

NAME

优点

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰器比继承更为灵活。
  • 可以通过动态的方式来扩展一个对象的功能,通过配置文件在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类已经这些装饰类的排列组合,可以创造出很多不同行为的组合。或者使用多个不同的具体装饰类装饰同一个对象,得到功能更对强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构建类和具体装饰类,在使用时再对其进行组合,原有代码无需改变,符合开闭原则。

缺点

  • 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于他们之间的相互连接方式有所不同,而不是他们的类或属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂性,加大学习与理解难度。
  • 这种比继承机制更加灵活的特性,同时也意味着装饰模式比继承模式更易出错,排查也更加困难,对于多次装饰的对象,调试时寻找试错可能需要逐级排查,较为繁琐。

适用场景

  • 在不影响其它对象的情况下,以动态、透明的方式给单个对象增加职责。
  • 需要动态给一个对象增加功能,这些功能也可以动态的被撤销。
  • 当不能采用继承的方式对系统中进行扩从或采用继承不利于系统扩展与维护时。有两种情况不适合使用继承:系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类;活类的定义为 final,不能继承。

模式扩展

装饰器模式简化需要注意的问题:

  • 一个装饰类的接口必须与被装饰类的接口保持相同,对于客户端来说,对象在被装饰前后都可以一致对待。
  • 尽量保持具体构件类 Component 作为一个“轻”类,不要把太多的逻辑和状态放在具体构件类中,可以通过装饰类来完成这些工作

对该模式进行扩展时:如果只有一个具体构件类而没有抽象构件类,那么抽象装饰类可以作为具体构件类的直接子类。

9 - CH09-结构型-外观

模式动机

模式定义

外观模式(Facade Pattern),外部与子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。又称门面模式

模式结构

包含两种角色:

  • Facade:外观角色
  • SubSystem:子系统角色

类图

NAME

时序图

NAME

代码分析

模式分析

  • 根据单一职责原则,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个最常见的目标是使子系统之间的通信和相互依赖关系达到最小,达到该目标的方式之一就是引入一个外观对象,来为子系统提供一个简单而单一的入口。
  • 外观模式也是迪米特法则的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
  • 该模式要求,一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂度分隔开,客户端则只需要与外观类交互,而不需要和子系统内部的诸多对象交互。
  • 该模式的目的在于降低系统的复杂度。
  • 该模式很大程度上提高了客户端使用上的便捷性,使得客户端无需关心子系统的内部细节。

优点

  • 对客户端屏蔽子系统组件,减少客户处理的对象数目,并使得子系统用起来更容易。
  • 实现客户端与子系统的松耦合,使子系统的组件变化不会影响到客户类,只需要调整外观类即可。
  • 降低大型系统中的编译依赖性,并简化了系统在不同平台之间的迁移过程,可以以一个更小粒度(子系统)修改、编译软件。
  • 只是提供了一个统一访问子系统的统一入口,并不影响用户直接使用子系统类。

缺点

  • 不能很好的限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
  • 在不引入抽象外观类的情况下,增加新的子系统可能要修改外观类或客户端的代码,违背了开闭原则。

适用场景

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

模式扩展

  • 一个系统有多个外观类

    通常只有一个外观类,同时只有一个实例,即它是一个单例类。同时也可以定义多个外观类,分别于不同的特定子系统交互。

  • 不要通过外观类为子系统增加新的行为

  • 外观模式与迪米特法则

    外观类充当了客户与子系统之间的第三者,降低客户与系统的耦合度。

  • 引入抽象外观类

    该模式最大的缺点在于违背了开闭原则。当增减子系统时可以通过引入抽象外观类来解决该问题,客户端则针对抽象外观类编程。以增减具体外观类的方式来支持子系统的变更。

10 - CH10-结构型-享元

模式动机

OOP 可以很好的解决一些灵活性和扩展性问题,但同时要增加对象的个数。当对象数过多则会导致运行代价过高,带来性能问题。

  • 享元模式正式为了解决这类问题。该模式通过共享技术实现相同或相似对象的重用。
  • 在该模式中可以共享的相同内容称为内部状态(Intrinsic State),而那些需要通过外部环境来配置的不能共享的内容称为外部状态(Extrinsic State)。因为区分了内外状态,可以通过设置不同的外部状态使得相同对象可以具有一些不同的特征,而相同的内部状态则可以共享。
  • 该模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个**享元池(Flyweight Pool)**用于存储具有相同内部状态的享元对象。
  • 该模式中共享的享元对象的内部状态、外部状态需要通过环境来配置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,所包含的内部状态较少,也称为细粒度对象。该模式的目的就是使用共享技术来实现大量细粒度对象的复用。

模式定义

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

模式结构

该模式包含四种角色:

  • Flyweight:抽象享元类
  • ConcreteFlyweight:具体享元类
  • UnsharedConcreteFlyweight:非共享具体享元类
  • FlyweightFactory:享元工厂类

类图

NAME

时序图

NAME

代码分析

模式分析

该模式是一种考虑系统性能的设计模式,使用该模式以节约内存空间,提供系统性能。

享元模式的核心在于享元工厂类,它的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中取,如果不存在则创建一个该具体享元类的实例,存入享元池并返回给客户。

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

  • 内部状态:是存储在享元对象内部并且不会随着环境改变而改变的状态。
  • 外部状态:是随环境而改变的、不可共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候在传入到享元对象内部。外部状态相互之间是独立的。

优点

  • 可以极大减少内存中对象的数量,使得相同对象或者相似对象在内存中只保存一份。
  • 外部状态相互独立,而且不会影响其内部状体,从而使得享元对象可以在不同的环境中被共享。

缺点

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

适用场景

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

模式应用

该模式大量应用于编辑器软件,如在一个文档中多次使用相同的图片等。

模式扩展

单纯享元模式和复合享元模式:

  • 单纯享元模式:所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
  • 复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是可以分解成单纯享元对象,然后进行共享。

与其他模式联用:

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

11 - CH11-结构型-代理

模式动机

某些情况下,一个客户不想或者不能直接引用一个对象,此时可以使用一个称为代理的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介作用,并且可以通过代理对象来去掉客户不能看到的内容、服务或资源,或添加额外的功能。

通过引入一个新的对象(小图片、远程代理 对象)来实现对真实对象的操作,或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象

模式定义

代理模式(Proxy Pattern),给某一个对象提供一个代理,并由代理对象控制对源对象的引用。或称为 Surrogate

模式结构

共包含三种角色:

  • Subject:抽象主题角色
  • Proxy:代理主题角色
  • RealSubject:真实主题角色

类图

NAME

时序图

NAME

优点

  • 能够协调调用者和被调用者,一定程度上降低了系统耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,优化系统以提升速度。
  • 保护代理可以控制真是对象的使用权限。

缺点

  • 由于在客户端和真实对象之间增加了代理,因此,有些类型的代理模式(如远程)可能会造成请求的处理速度过慢。
  • 实现代理模式需要额外的工作,有些实现则比较复杂。

适用场景

  • 远程代理(Remote):为一个处于不同地址空间的对象提供一个本地代理对象。
  • 虚拟代理(Virtual):如果需要创建一个资源消耗过大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时在会被真正创建。
  • Copy-To-Write 代理:是虚拟代理的一种,把复制(克隆)操作 延迟到只有在客户端真正需要时才执行。对象的深克隆是一个开销较大的工作,此方式可以使该操作延迟,需要时才执行。
  • 保护代理(Protect or Access):控制一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲代理(Cache):为某一个目标操作的结果提供临时的存储空间,以使多个客户端可以共享这些结果。
  • 防火墙代理(Firewall):保护目标不让恶意用户接触。
  • 同步化代理(Synchronizaiton):使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用代理(Smart Reference):当一个对象被引用时,提供一些额外操作,比如记录对象的调用次数。

模式应用

EJB、Web Service等分布式技术都是代理模式的应用。

12 - CH12-行为型-命令

行为型模式概述

行为型模式(Behavioral Pattern)是对在不同的对象之间划分职责和算法的抽象。

该模式不仅仅关注类和对象的结构,而且关注他们之间的相互作用

通过该模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不孤立,他们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

该类模式又分为类行为模式对象行为模式

  • 类行为模式:使用继承关系在几个子类之间分配行为,通过多态方式分配父类与子类的职责。
  • 对象行为模式:使用对象的聚合关联关系来分分配行为,主要是通过对象关联等方式类分配两个或多个类的职责。根据合成复用原则,系统中要尽量通过关联关系来取代继承关系,因此绝大多数的行为设计模式都属于对象行为型模式。

命令模式

模式动机

软件设计中,经常要向对象发送请求,但是并不知道对象的接收者是谁,也不知道被请求的操作是哪个,只需要在运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者和接收者之间接触耦合,增加对象调用之间的灵活性。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

模式定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。属于对象行为型模式,别名为动作模式(Action)事务模式(Transaction)

模式结构

包含五种角色:

  • Command:抽象命令类
  • ConcreteCommand:具体命令类
  • Invoker:调用者
  • Reciver:接收者
  • Client:客户端

类图

NAME

时序图

NAME

代码示例

模式分析

命令模式的本质是对命令进行封装,将发送命令的责任和执行命令的责任分隔开。

  • 每一个命令都是一个操作:请求方发出请求,要求执行一个操作;接收方收到请求,并执行操作。
  • 命令模式运行请求方和接收方独立开来,使得请求方不必知道接收方的接口,更不必知道请求是如何被接收的,以及操作是否被执行、合适被执行,以及如何执行。
  • 命令模式使请求本身称为一个对象,这个对象和其他对象一样可以被存储和传递。
  • 命令模式的关键在于引入了抽象命令接口,并且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相互关联。

优点

  • 降低系统的耦合度
  • 新的命令可以很容易地加入到系统
  • 可以比较容易的设计一个命令队列和宏命令(组合命令)
  • 可以方便的实现对请求的 Undo 和 Redo 操作

缺点

  • 可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能会需要大量命令类,这将影响命令模式的使用。

适用场景

  • 系统需要将请求调用者和接收者解耦,使得调用者和接收者不直接交互
  • 需要在不同的时间指定请求、将请求排队、执行
  • 系统需要支持命令的撤销和恢复操作
  • 系统需要将一组操作组合在一起,即自持宏命令

模式扩展

  • 宏命令又称组合命令,它是命令模式和组合模式联用的产物。
  • 宏命令也是一个具体命令,不过他包含了对其他命令对象的引用。在调用宏命令的execute()方法时,将会递归调用它所包含的每个成员命令的execute()方法。
  • 一个宏命令的成员对象可以是一个简单命令也可以是另外一个宏命令。
  • 执行一个宏命令将执行多个具体命令,从而实现对命令的批处理。

13 - CH13-行为型-中介者

模式动机

  • 在用户与用户直接聊天的设计方案中,用户对象之间存在很强的关联性,将导致系统出现如下问题:
    • 系统结构复杂:对象之间存在大量的相互关联和调用,如有一个对象发生变化,则需要跟踪那些与该对象关联的其他所有对象,并进行适当处理。
    • 对象可用性差:由于一个对象和其他对象具有很强的关联,如没有其他对象的支持,一个对象很难被另一个系统或模块复用,这些对象表现出来更像一个不可分割的整体,职责较为混乱。
    • 系统扩展性低:增加一个新的对象需要在原有对象上增加引用,增加新的引用关系也需要调整原有对象,系统耦合度高,对象操作不灵活,扩展性差。
  • 在面向对象的软件设计与开发过程中,根据“单一职责原则”,应该尽量将对象细化,使其只负责或呈现大一的职责。
  • 对于一个模块,可能有很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,这就是该模式的动机。

模式定义

中介者模式(Mediator Pattern):用一个中介对象来封装一系列对象交互,中介者使各个对象不需要直接的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。中介者模式又称为调停者模式,是一种对象行为型模式。

模式结构

包含四种角色:

  • Mediator:抽象中介者
  • ConcreteMediator:具体中介者
  • Colleague:抽象同事类
  • ConcreteColleague:具体同事类

类图

NAME

时序图

NAME

模式分析

中介者模式可以使对象之间的关系数量急剧减少。

中介者承担两方面的职责:

  • 中转作用(结构性):通过中介者提供的中转作用,各个同事对象之间就不需要再进行显式引用,当需要和其他同事进行通信时,通过中介者即可。该中转作用属于中介者在结构上的支持。
  • 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。

优点

  • 简化了对象之间的交互
  • 将各同事解耦
  • 减少子类生成
  • 可以简化各同事类的设计和实现

缺点

在具体中介类中包含类同事之间的交互细节,可能会导致具体中介类非常复杂,使系统难以维护。

适用场景

  • 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
  • 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
  • 想通过一个中间类来疯转多个类中的行为,而又不想生成太多的子类。
  • 交互的公共行为,如果需要改变行为可以增加新的中介者类。

模式应用

MVC 架构中的控制器:Controller 作为一个中介者,它负责控制试图对象 View 和模型对象 Model 之间的交互。

14 - CH14-行为型-观察者

模式动机

建立一种对象与对象之间的依赖关系,一个对象改变时将自动通知其他对象,其他对象将作出相应反应。在此,发生改变的对象成为观察目标,被通知的对象成为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加或删除观察者,使得系统更易于扩展。这便是该模式的动机。

模式定义

观察者模式(Observer Pattern):定义对象的一种一对多关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。或称为发布-订阅(Publish/Subscribe)模式、模型-试图(Model/View)模式、源-监听(Source/Listener)模式、从属(Dependents)模式

模式结构

包含 4 中角色:

  • Subject:目标
  • ConcreteSubject:具体目标
  • Observer:观察者
  • ConcreteObserver:具体观察者

类图

NAME

时序图

NAME

模式分析

  • 管擦者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
  • 该模式中的关键对象是观察目标和管擦者,一个目标可以有任意数目的、与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
  • 作为多这个通知的响应,每个观察者都将更新自己的状态,以与目标状态同步,这种交互也被称为发布-订阅。目标是通知的发布者,它发出通知时并不知道谁是它的观察者,可以有任意数量的观察者订阅它并接收通知。

优点

  • 该模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种不同的表示层作为具体观察者角色。
  • 该模式在观察目标和观察者之间建立了一个抽象的耦合。
  • 观察者模式支持广播通信。
  • 观察者模式符合“开闭原则”。

缺点

  • 如果一个目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到将会花费很多时间。
  • 如果在观察者和目标之间有循环依赖的话,目标会触发他们之间进行循环调用,导致系统崩溃。
  • 该模式没有相应的机制让观察者知道相应的观察目标是怎样发生变化的,而仅仅只是知道目标发生了变化。

适用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中,可以使他们各自独立的改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少个对象发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A 对象的行为将影响 B 对象, B 对象的行为将会影响 C 对象…等等,可以使用该模式创建一种链式触发机制。

15 - CH15-行为型-状态

模式动机

  • 很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性称为状态,这样的对象称为有状态(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统行为也随之改变。
  • 在 UML 中可以使用状态图来描述对象状态的变化。

模式定义

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它自身的行为,对象开起来似乎修改了它的类。其别名为状态对象(Objects for states)。

模式结构

包含 3 种角色:

  • Context:环境类
  • State:抽象状态类
  • ConcreteState:具体状态类

类图

NAME

时序图

NAME

模式分析

  • 状态模式描述了对象的状态变化以及对象如何在每一种状态下表现出不同的行为。
  • 状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类称为抽象状态类,而对象的每一种具体状态类都继承自该类,并在不同具体状态类中实现了不同状态的行为,以及不同状态间的转换。

需要理解环境类与抽象状态类的作用:

  • 环境类实际上就是拥有状态的对象,有时候可以充当状态管理器(State Manager)的角色,可以在环境类中对状态进行切换操作。

  • 抽象状态类可以是抽象类,也可以是接口,不同状态类就是继承这个父类的不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件:

    • 这些状态需要经常切换
    • 不同状态下,对象的行为不同

    因此,可以将不同对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变它的行为,对象看起来似乎修改了它的类,实际上是由于切换到不同的状态类实现的。

  • 由于环境类可以设置为任一具体状态类,因此它针对抽象状态类进行编程,在程序运行时可以将任一具体状态类的对象设置到环境类中,从而使得环境类可以改变内部状态,并且改变行为。

优点

  • 封装了转换规则
  • 枚举可能的状态,在枚举状态之前需要确定状态种类
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便的增加新的状态,只需要改变对象状态即可改变对象行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是一个巨大的条件语句块。
  • 可以让多个环境对象共现一个状态对象,从而减少系统中对象的个数。

缺点

  • 必然增加系统类和对象的个数
  • 结构与实现比较复杂,使用不当将导致结构与代码混乱
  • 对“开闭原则”的支持并不是很好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态装换的源代码,否则无法切换到新增状态;修改某个状态类的行为也需要修改对应类的源代码。

适用场景

  • 对象的行为依赖于其状态(属性),并且需要根据它的状态改变而改变相关行为。
  • 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便的增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。

16 - CH16-行为型-策略

模式动机

  • 完成一项任务,往往可以有多种不同的方式,每种方式称为一个策略,我们可以根据环境、条件的不同选择不同的策略来完成该项任务。
  • 在软件开发中也常常遇到类似的情况,实现某一功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活的选择解决途径,也能够方便的增加新的解决途径。
  • 在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码在一个类中,如需要提供多种查询算法,可以将这些算法写到一个类中,在该类中提供多个方法,每个方法对应一种算法;或者将所有的算法封装在一个方法中,通过if-else语句来进行选择。这些方法都可以称为硬编码,如果需要增加一种新的算法,则需要修改封装算法类的源代码;修改、更换算法时,仍然需要修改客户端调用代码。这种方式使封装算法的类过于庞大、逻辑复杂,难以维护。
  • 除了提供专门的查找算法外,还可以在客户端程序中直接包含算法代码,这种做法更不可取,将导致客户端程序庞大而难以维护,如果存在大量可供选择的代码则将家中问题的严重性。
  • 为了解决这个问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会使用一个抽象的策略类来做算法的定义,而具体每种算法则应对应于一个具体的策略类。

模式定义

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让他们可以互相替换。该模式实现算法能够独立于使用它的客户端而独立变化,或称政策模式(Policy)

模式结构

包含 3 种角色:

  • Context:环境类
  • Strategy:抽象策略类
  • ConcreteStrategy:具体策略类

类图

NAME

时序图

NAME

模式分析

  • 策略模式是一个比较容易理解和使用的模式,就是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。即:准备一组算法,并将每一个算法封装起来,使得他们可以互换。
  • 在该模式中,应当有客户端自己决定何时、选择哪个具体策略。
  • 该模式仅仅是封装算法,提供新算法插入到已有系统中,以及老算法从系统中移除。策略模式并不觉得在何时使用哪种算法,这些由客户端类决定。在一定程度上提升了灵活性,但是客户端需要理解所有具体策略之间的区别,以便选择合适的算法,这也是该模式的缺点,一定程度上增加了客户端的使用难度。

优点

  • 提供了对开闭原则的支持,用户可以在不修改原有系统的基础上选择、增减算法或行为。
  • 提供了管理相关算法族的办法。
  • 提供了可以替换继承关系的办法。
  • 可以避免使用多重条件语句。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪个策略。
  • 将会产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

适用场景

  • 如果一个系统中有很多类,他们之间的区别仅在于他们的行为,这时使用策略模式就可以动态的让一个对象在许多行为中选择一个。
  • 系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句实现。
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。