《设计模式的艺术:软件开发人员内功修炼之道》简记

7种常用的面向对象设计原则

1:单一职责原则 一个类只负责一个功能领域中的相应职责
2:开闭原则 软件实体应对扩展开放,而对修改关闭
3:里氏代换原则 所有引用基类对象的地方能够透明地使用其子类的对象
4:依赖倒转原则 抽象不应该依赖于细节,细节应该依赖于抽象
5:接口隔离原则 使用多个专门的接口,而不使用单一的总接口
6:合成复用原则 尽量使用对象组合,而不是继承来达到复用的目的
7:迪米特法则 一个软件实体应当尽可能少地与其他实体发生相互作用

1:单一职责原则(Single Responsibility Rrinciple, SRP):一个类只负责一个功能领域中的相应职责。或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
2:开闭原则(Open-Closed Principle,OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
3:里氏代换原则(Liskov Subsititution Principle,LSP):所有引用基类(父类)的地方必须能透明地使用其子类对象。
4:依赖倒转原则(Dependency Inversion Principle,DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。
常用的注入方式有3种:构造注入,设值注入和接口注入。
5:接口隔离原则(Interface Segregation Principle,ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
6:合成复用原则(Composite Reuse Rrinciple,CRP):尽量使用对象组合,而不是继承来达到复用的目的。
7:迪米特法则(Law of Demeter,LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

一个类创建型模式使用继承改变被实例化的类。而一个对象创建型模式将实例化委托给另一个对象。

创建的艺术–创建型模式

模式名称和定义:
单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
简单工厂模式:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
工厂方法模式:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
原型模式:使用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

单例模式

动机:确保对象的唯一性。(例如:Windows任务管理器、负载均衡器)
定义:单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式
单例模式有3个要点:(1)某个类只能有一个实例。(2)它必须自行创建这个实例。(3)它必须自行向整个系统提供这个实例。
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的GetInstance()方法,让客户可以访问它的唯一实例;为了防止在外部对单例类实例化,它的构造函数可见性为private;在单例类内部定义了一个Singleton类型的静态对象,作为供外部共享访问的唯一实例。
饿汉式单例类:

1
2
3
4
5
class EagerSingleton{
   private static final EagerSingleton instance=new EagerSingleton();
   private EagerSingleton(){}
   public static EagerSingleton getInstance(){return instance};
}

当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,实例类的唯一实例将被创建。
懒汉式单例类:

1
2
3
4
5
6
7
8
class LazySingleton{
   private static LazySingleton instance=null;
   private LazySingleton(){}
   synchronized public static LazySingleton getInstance(){
      if(instance==null){instance=new LazySingleton();}
      return instance;
   }
}

懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载技术,即需要的时候再加载实例,为了避免多个线程同时调用getInstance()方法,可以使用synchronized。
使用双重检查锁定优化懒汉式单例类:

1
2
3
4
5
6
7
8
9
10
11
12
class LazySingleton{
   private volatile static LazySingleton instance=null;//增加volatile关键字确保多个线程都能够正常处理
   private LazySingleton(){}
   public static LazySingleton getInstance(){
      if(instance==null){//第一重判断
         synchronized(LazySingleton.class){//锁定代码块
            if(instance==null){instance=new LazySingleton();}//第二重判断及创建单例实例
         }
      }
      return instance;
   }
}

一种更好的单例实现方法Initialization on Demand Holder(IoDH)

1
2
3
4
5
6
7
class Singleton{
   private Singleton(){}
   private static class HolderClass{
      private final static Singleton instance=new Singleton();
   }
   public static Singleton getInstance(){return HolderClass.instance;}
}

由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不能造成任何影响。其缺点是与编程语言本身的特性相关。
总结(P47)

简单工厂模式

集中式工厂的实现
定义:简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态方法,因此简单工厂模式又被称为静态工厂方法模式,它属于类创建型模式
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
在简单工厂模式结构图中包含以下3个角色:
(1)Factory(工厂角色):即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。
(2)Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提供系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
(3)ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的角色都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。
在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象。
典型的抽象产品类代码如下:

1
2
3
4
5
6
7
8
abstract class Product{
   // 所有产品类的公共业务方法
   public void methodSame(){
        //公共方法的实现
   }
   // 声明抽象业务方法
   public abstract void methodDiff();
}

典型的工厂类代码如下:

1
2
3
4
5
6
7
8
9
10
class Factory{
   // 静态工厂方法
   public static Product getProduct(String arg){
      Product product=null;
      if(arg.equalsIgnoreCase('A')){product=new ConcreteProductA();}
      else  if(arg.equalsIgnoreCase('B')){product=new ConcreteProductB();}
      ......
      return product;
   }
}

创建对象与适用对象
与一个对象相关的职责通常有3类:对象本身所具有的职责、创建对象的职责和使用对象的职责。
在Java语言中,通常有以下几种创建对象的方法:
(1)使用new关键字直接创建对象。
(2)通过反射机制创建对象。
(3)通过clone()方法创建对象。
(4)通过工厂类创建对象。
所有的工厂模式都强调一点:两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。
总结(P59)

工厂方法模式

多态工厂的实现
动机:
(1)工厂类过于庞大,包含了大量的if…else…代码,导致维护和测试难度增大。
(2)系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了开闭原则。
定义:工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式,又可称为虚拟构造器模式或多态工厂模式。工厂方法模式是一种类创建型模式
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。
在工厂方法模式结构图中包含以下4个角色:
(1)Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
(2)ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
(3)Factory(抽象工厂):在抽象工厂类中,声明了工厂方法,用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
(4)ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。
Java反射(Java Reflection)是指在程序运行时获取已知名称的类或已有对象的相关信息的一种机制,包括类的方法、属性、父类等信息,还包括实例的创建和实例类型的判断等。在反射中使用最多的类是Class。Class类的实例表示正在运行的Java应用程序中的类和接口,其forName(String className)方法可以返回与带有给定字符串名的类或接口相关联的Class对象,再通过Class对象的newInstance()方法创建此对象所表示的类的一个新实例,即通过一个类名字符串得到类的实例。
java.lang.reflect包封装了其他与反射相关的类。
总结(P72)

抽象工厂模式

产品族的创建
概念:
(1)产品等级结构。产品等级结构即产品的继承结构。
(2)产品族。在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。
抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品的等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率。
定义:抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式
在抽象工厂模式结构图中包含以下4个角色:
(1)AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
(2)ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品额方法,生成一组具体产品,这些产品构成一个产品族,每一个产品都位于某个产品等级结构中。
(3)Product(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
(4)ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现在抽象产品接口中声明的业务方法。
在抽象工厂中声明了多个工厂方法,用于创建不同类型的产品,抽象工厂可以是接口,也可以是抽象类或者具体类。
总结(P85)

原型模式

对象的克隆
在使用原型模式时,需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。
定义:原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。原型模式是一种对象创建型模式
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象克隆自己来实现创建过程。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
需要注意的是,通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址。
在原型模式结构图中包含以下3个角色:
(1)Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
(2)ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
(3)Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
常用的克隆实现方法:
(1)通用实现方法
通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员变量相同。

1
2
3
4
5
6
7
8
9
10
class ConcretePrototype implements Prototype{
   private String attr;//成员变量
   public void setAttr(String attr){this.attr=attr;}
   public String getAttr(){return this.attr;}
   public Prototype clone(){
      Prototype prototype=new ConcretePrototype();//创建新对象
      prototype.set(this.attr);
      return prototype;
   }
}

(2)Java语言提供的clone()方法
能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。

1
2
3
4
5
6
7
8
9
10
class ConcretePrototype implements Cloneable{
   ......
   public Prototype clone(){
      Object object=null;
      try{object=super.clone();}
      catch(CloneNotSupportedException exception){
      }
      return (Prototype)object;
   } 
}

两种不同的克隆方法:
1.浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有被复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
2.深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将被复制。
在Java语言中,如果需要实现深克隆,可以通过序列化等方式来实现。
原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。
总结(P103)

建造者模式

复杂对象的组装与创建
动机:如何一步一步地创建一个包含多个组成部分的复杂对象。
定义:建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表现。建造这模式是一种对象创建型模式
在建造者模式结构图中包含以下4个角色:
(1)Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法:一类方法是buildPartX(),用于创建复杂对象的各个部件;另一类方法是getResult(),用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
(2)ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
(3)Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义其装配过程。
(4)Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造和装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
建造者模式与抽象工厂模式有点相似,但是建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而在建造者模式中,客户端通过指定具体建造者类型并指导Director类如何去生成对象,侧重于一步一步地构造一个复杂对象,然后将结果返回。
关于Director的进一步讨论
1.省略Director
2.钩子方法的引入
总结(P119)

组合的艺术–结构型模式

模式名称和定义
适配器模式:将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作。
桥接模式:将抽象部分与其实现部分分离,使它们都可以独立地变化。
组合模式:组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构。
装饰模式:动态地给一个对象增加一些额外的职责。
外观模式:外部与一个子系统的通信通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的入口。
享元模式:运用共享技术有效地支持大量细粒度对象的复用。
代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

适配器模式

不兼容结构的协调
适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标接口。
定义:适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装类(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。
在对象适配器模式结构中包含以下3个角色:
(1)Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
(2)Adapter(适配器类):适配器可以调用另一个接口,作为一个适配器,对Adaptee和Target进行适配。适配器类是适配器模式的核心,在对象适配器模式中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
(3)Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
典型的对象适配器模式代码如下:

1
2
3
4
5
class Adapter extends Target{
   private Adaptee adaptee;
   public Adapter(Adaptee adaptee){this.adaptee=adaptee;}
   public void request(){adaptee.specificRequest();}
}

类适配器模式与对象适配器模式最大的区别在于其适配器和适配者之间的关系是继承关系
在对象适配器模式的使用过程中,如果在适配器中同时包含对目标类和适配类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配类中的方法,那么该适配器就是一个双向适配器

交互的艺术–行为型模式

职责链模式:避免将请求发送者与接收者耦合在一起,让多个对象都有机会接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
命令模式:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
解释器模式:定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
迭代器模式:提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示。
中介者模式:用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
备忘录模式:在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
观察者模式:定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
策略模式:定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,使得算法的变化可独立于使用它的客户。
模板方法模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
访问者模式:提供一个作用于某对象结构中的各元素的操作表示,使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。