目录
1 简单工厂模式 Simple Factory Pattern
2 工厂方法模式 Factory Method Pattern
3 抽象工厂模式 Abstract Factory Pattern
12 模板方法模式 Template Method Pattern
1 简单工厂模式 Simple Factory Pattern
模式定义:定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式
简单工厂模式中包含如下几个角色:
- 抽象产品:工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,所有创建的具体产品对象都是其子类对象
- 具体产品:简单工厂模式的创建目标,每一个具体产品角色都继承了抽象产品角色
- 工厂:即工厂类,负责实现创建所有产品实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象
典型的抽象产品类代码:
abstract class Product {
//所有产品类的公共业务方法
public void methodSame() {
//公共方法的实现
}
//声明抽象业务方法
public abstract void methodDiff();
}
典型的具体产品类代码:
class ConcreteProduct extends Product {
//实现业务方法
public void methodDiff() {
//业务方法的实现
}
}
典型的工厂类代码:
class Factory {
//静态工厂方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
//初始化设置product
}
else if (arg.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
//初始化设置product
}
return product;
}
}
有时候,为了简化简单工厂模式,我们可以将抽象产品类和工厂类合并,将静态工厂方法移至抽象产品类中
简单工厂模式优点:
- 实现了对象创建和使用的分离
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
简单工厂模式缺点:
- 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
- 会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护
2 工厂方法模式 Factory Method Pattern
工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂
模式定义:工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象
工厂方法模式中包含如下几个角色:
- 抽象产品:定义产品的接口,是工厂方法模式所创建的产品对象的公共父类
- 具体产品:实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应
- 抽象工厂:声明了工厂方法,用于返回一个产品,所有创建对象的工厂类都必须实现该接口
- 具体工厂:抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例
与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类
典型抽象工厂代码:
interface Factory {
public Product factoryMethod();
}
具体工厂典型代码:
class ConcreteFactory implements Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
客户端代码中,只需关心工厂类即可,不同的具体工厂可以创建不同的产品,典型的客户端代码:
Factory factory;
factory = new ConcreteFactory(); //可通过配置文件实现
Product product;
product = factory.factoryMethod();
有时候,为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时,在工厂类中将直接调用产品类的业务方法,客户端无须调用工厂方法创建产品,直接通过工厂即可使用所创建的对象中的业务方法
工厂方法模式优点:
- 实现了对象创建和使用的分离,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名
- 加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,只要添加一个具体工厂和具体产品就可以了,可扩展性非常好,符合开闭原则,克服了简单工厂的问题
- 基于工厂角色和产品角色的多态性设计
工厂方法模式缺点:
- 添加新产品需要编写新的具体产品类和对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销
- 考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度
3 抽象工厂模式 Abstract Factory Pattern
抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品(一种抽象产品的多种子类),它负责创建一族产品(多种抽象产品的多种子类)
模式定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的产品,这些产品构成了一个产品族
抽象工厂模式中包含如下几个角色:
- 抽象产品:为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法
- 具体产品:定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法
- 抽象工厂:声明了一组用于创建一族产品的方法,每一个方法对应一种产品
- 具体工厂:实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中
抽象工厂典型代码:
abstract class AbstractFactory { //抽象工厂,比如电器工厂,既生产电视机,又生产电冰箱等电器
public abstract AbstractProductA createProductA(); //工厂方法一,创建产品A,比如电视机
public abstract AbstractProductB createProductB(); //工厂方法二,创建产品B,比如电冰箱
……
}
具体工厂典型代码:
class ConcreteFactory1 extends AbstractFactory {//具体工厂,比如,海尔电器工厂
//工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1(); //创建抽象产品A的一个子类A1,比如海尔电视机
}
//工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1(); //创建抽象产品B的一个子类B1,比如海尔电冰箱
}
……
}
- 产品等级结构:即产品的继承结构,如抽象产品是电视机,则子类有海尔电视机、海信电视机、TCL电视机,则该抽象产品与所有子类构成一个产品等级结构。
- 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。,如海尔电器工厂生产的海尔电视机、海尔电冰箱、海尔洗衣机等。海尔电视机,海尔电冰箱,海尔洗衣机构成一个产品族。
- 抽象工厂中模式中的具体工厂:即生成不同产品族的工厂,如海尔电器工厂,海信电器工厂,美的电器工厂等,且每个具体工厂生产同一组抽象产品的不同子类实现。
抽象工厂模式优点:
- 隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象
- 增加新的产品族很方便,无须修改已有系统,符合开闭原则
抽象工厂模式缺点:
- 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则
4 建造者模式 Builder Pattern
又称为生成器模式,建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品
模式定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节
在建造者模式中包含如下几个角色:
- 产品:它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程
- 抽象建造者:为创建一个产品对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口
- 具体建造者:实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象
- 指挥者:又称为导演类,负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中
复杂对象:包含多个成员属性的对象,这些成员属性也称为部件或零件,如汽车包括方向盘、发动机、轮胎等部件,电子邮件包括发件人、收件人、主题、内容、附件等部件
典型的复杂对象类代码:
class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
抽象建造者典型代码:
abstract class Builder {
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
指挥者主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程。典型代码:
class Director {
private Builder builder;//关联一个builder
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
//产品构建与组装方法
public Product construct() {
//指挥者针对抽象建造者编程包括某个buildPartX()方法是否被调用以及多个buildPartX()方法调用的先后次序等
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
对于客户端而言,只需关心具体的建造者,典型代码:
……
Builder builder = new ConcreteBuilder(); //可通过配置文件实现
Director director = new Director(builder);
Product product = director.construct();
……
建造者模式与抽象工厂模式比较:
- 建造者模式返回一个完整的复杂产品,而抽象工厂模式返回一系列相关的产品
- 在建造者模式中,客户端通过指定具体建造者类型并指导Director类如何去生成对象,侧重于一步步构造一个复杂对象,然后将结果返回;在抽象工厂模式中,客户端通过选择具体工厂来生成所需对象,而
如果将抽象工厂模式看成一个汽车配件生产厂,生成不同类型的汽车配件;
那么建造者模式就是一个汽车组装厂,通过对配件进行组装返回一辆完整的汽车,不同的具体建造者组装不同的车,如货车建造者,轿车建造者,在实现抽象建造者的buildePartX方法时,给X赋不同值,比如假设产品有一个属性是轮子,则货车可以设置为6,轿车设置为4
5 单例模式 Singleton Pattern
模式定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类
几个要点:
- 在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例
- 为了防止在外部对其实例化,将其构造函数设计为私有
- 在单例类内部定义了一个静态对象,作为外部共享的唯一实例
单例类有两种不同实现方式:饿汉式与懒汉式
饿汉式:在定义静态变量的时候实例化单例类,在类加载的时候就已经创建了单例对象
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式:懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,即延迟加载
class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
但是饿汉式在多线程中可能出现多个线程同时调用getInstance(),造成返回的不是同一个实例,所以应该使用双重检查
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;
}
}
饿汉式单例类与懒汉式单例类比较
饿汉式
- 类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性
- 调用速度更快,反应时间更少,因为单例对象一开始就得以创建
- 类加载时该对象就需要创建,比较消耗资源,且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长
懒汉式
- 第一次使用时创建,无须一直占用系统资源,实现了延迟加载
- 必须处理好多个线程同时访问的问题,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响
双重检查锁的问题
双重检查锁在多线程并发时,可能会因为JVM指令重排出问题
代码中 s2 = new SingleTon2();在JVM中可能会分解为:
memory = allocate(); //1:分配对象的内存空间
initInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址如果因为某些原因导致指令重排,指令的执行顺序可能为:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
initInstance(memory); //2:初始化对象此时,如果在instance=memory执行,但未完成对象初始化时,另一个线程执行第一个if(s2==null)会返回false直接返回未初始化的
Singleton实例,导致错误。
为了解决饿汉式的占用资源和懒汉式的多线程影响性能问题,有一种更好的单例实现方法:IoDH(initialization on demand holder)
即在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用
class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
原理:
- 加载外部类Singleton时,不会加载内部静态类,而单例对象instance不是Singleton的成员变量,所以不会在加载Singleton时被初始化
- 第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量
- 由Java虚拟机来保证其线程安全性,类的初始化阶段是连续的,确保了该成员变量只能初始化一次
- 由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响
问题:
- 任何初始化失败都会导致单例类不可用,这种实现方式只能用于能保证初始化不会失败的情况
- 与编程语言本身的特性相关,很多面向对象语言不支持IoDH
单例模式优点:
- 系统内存中只存在一个对象,因此可以节约系统资源,不用频繁创建和销毁的对象,提高系统的性能
单例模式缺点:
- 没有抽象层,不容易扩展
- 职责过重,违背了“单一职责原则”。既充当了工厂角色,提供了工厂方法,又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起
- 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失
6 适配器模式 Adapter Pattern
情景:现有一个电器,工作电压是20V,但是家庭用电是220V,则需要引入一个电源适配器,俗称充电器或变压器,将电压转换
软件开发中,有时也存在类似这种不兼容的情况
与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象(即被适配的类)称为适配者(Adaptee)。
适配器的实现就是把客户类的请求(需要20V电压)转化为对适配者的相应接口(220V电压)的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
适配器模式中包含如下几个角色:
- 目标抽象类(Target):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类
- 适配者类(Adaptee):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码
- 适配器类(Adapter):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系
典型的对象适配器代码:
class Adapter extends Target {
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() { //request()是客户调用的方法
adaptee.specificRequest(); //转发调用,specificRequest()方法是客户端所需要存在的方法
}
}
还有一种形式,那就是类适配器,适配器和适配者使用继承关系而不是关联关系
class Adapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
由于Java、C#等语言不支持多重类继承,因此类适配器的使用受到很多限制,例如如果目标抽象类Target不是接口,而是一个类,就无法使用类适配器;此外,如果适配者Adapter为最终(Final)类,也无法使用类适配器。用的不多。
适配器模式优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配
适配器模式缺点:
- 置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂
7 装饰模式 Decorator Pattern
类似新房装修的技术,可以对已有对象(新房)的功能进行扩展(装修),以获得更加符合用户需求的对象,使得对象具有更加强大的功能
模式定义:装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。就增加对象功能来说,装饰模式比生成子类实现更为灵活
在装饰模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类,而将具体的装饰类作为它的子类
在装饰模式中包含如下几个角色:
- 抽象构件( Component):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
- 具体构件(ConcreteComponent):它是抽象构件类的子类,即被装饰的类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
- 抽象装饰类 (Decorator):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
- 具体装饰类(ConcreteDecorator):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
抽象装饰类典型代码:
class Decorator implements Component
{
private Component component; //维持一个对抽象构件对象的引用
public Decorator(Component component) //注入一个抽象构件类型的对象
{
this.component=component;
}
public void operation()
{
component.operation(); //调用原有业务方法
//Decorator中并未真正实现operation()方法,而只是调用原有component对象的operation()方法
//它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成
}
}
Decorator的子类即具体装饰类中将继承operation()方法并根据需要进行扩展,典型的具体装饰类代码:
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation(); //调用原有业务方法
addedBehavior(); //调用新增业务方法
}
//新增业务方法
public void addedBehavior()
{
……
}
}
由于在抽象装饰类Decorator中注入的是Component类型的对象,因此我们可以将一个具体构件对象注入其中,再通过具体装饰类来进行装饰;
此外,我们还可以将一个已经装饰过的Decorator子类的对象再注入其中进行多次装饰,从而对原有功能的多次扩展
如果只有一个具体构件类,那么抽象装饰类可以作为该具体构件类的直接子类
装饰模式优点:
- 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加
- 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”
装饰模式缺点:
- 使用装饰模式进行系统设计时将产生很多小对象,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能
- 比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐
8 外观模式 Facade Pattern
模式定义:又称为门面模式,通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度
外观模式包含如下两个角色:
- 外观(Facade):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
- 子系统(SubSystem):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
子系统类通常是一些业务类,实现了一些具体的、独立的业务功能,典型代码:
class SubSystemA
{
public void MethodA()
{
//业务实现代码
}
}
class SubSystemB
{
public void MethodB()
{
//业务实现代码
}
}
class SubSystemC
{
public void MethodC()
{
//业务实现代码
}
}
与子系统业务类之间的交互统一由外观类来完成,外观类代码:
class Facade
{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void Method()
{
obj1.MethodA();
obj2.MethodB();
obj3.MethodC();
}
}
外观模式的优点:
- 对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易
- 子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
- 子系统内部变化也不会影响到外观对象
外观模式的缺点:
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则
9 代理模式 Proxy Pattern
模式定义:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口
代理模式包含如下三个角色:
- 抽象主题 (Subject):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程
- 真实主题角色(RealSubject):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
- 代理主题角色(Proxy):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
抽象主题可以是接口、抽象类或具体类:
abstract class Subject
{
public abstract void Request();
}
真实主题类继承了抽象主题类,提供了业务方法的具体实现:
class RealSubject extends Subject
{
public void Request()
{
//业务方法具体实现代码
}
}
代理类:
class Proxy extends Subject
{
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void PreRequest()
{
…... //前操作
}
public override void Request()
{
PreRequest();
realSubject.Request(); //调用真实主题对象的方法
PostRequest();
}
public void PostRequest()
{
…… //后操作
}
}
代理模式根据其目的和实现方式不同可分为很多种类:
- 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
- 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
代理模式优点:
- 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度
- 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
代理模式缺点:
- 可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂
10 观察者模式 Observer Pattern
模式定义:定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。也称为发布-订阅(Publish-Subscribe)模式
观察者模式中包含如下几个角色:
- 抽象目标(Subject):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
- 具体目标(ConcreteSubject):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
- 抽象观察者(Observer):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update()
- 具体观察者(ConcreteObserver):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。
抽象目标Subject:
abstract class Subject {
//定义一个观察者集合用于存储所有观察者对象
protected ArrayList observers<Observer> = new ArrayList();
//注册方法,用于向观察者集合中增加一个观察者
public void attach(Observer observer) {
observers.add(observer);
}
//注销方法,用于在观察者集合中删除一个观察者
public void detach(Observer observer) {
observers.remove(observer);
}
//声明抽象通知方法
public abstract void notify();
}
具体目标类:
class ConcreteSubject extends Subject {
//实现通知方法
public void notify() {
//遍历观察者集合,调用每一个观察者的响应方法
for(Object obs:observers) {
((Observer)obs).update();
}
}
}
抽象观察者角色一般定义为一个接口,通常只声明一个update()方法,为不同观察者的更新(响应)行为定义相同的接口,这个方法在其子类中实现,不同的观察者具有不同的响应方法
interface Observer {
//声明响应方法
public void update();
}
在具体观察者ConcreteObserver中实现了update()方法:
class ConcreteObserver implements Observer {
//实现响应方法
public void update() {
//具体响应代码
}
}
观察者模式优点:
- 在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者
- 支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度
- 满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码
观察者模式缺点:
- 将所有的观察者都通知到会花费很多时间
- 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
11 策略模式 Strategy Pattern
情景:实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。如:在外出旅游时可以选择多种不同的出行方式,骑自行车、坐汽车、坐火车或者坐飞机
模式定义:策略模式中,我们可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里,每一个封装算法的类我们都可以称之为一种策略(Strategy)。一般会提供一个抽象的策略类来做规则的定义,而每种算法则对应于一个具体策略类。让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。
策略模式中包含如下几个角色:
- Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
- Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
- ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
抽象策略类代码:
abstract class AbstractStrategy {
public abstract void algorithm(); //声明抽象算法
}
具体策略类代码:
class ConcreteStrategyA extends AbstractStrategy {
//算法的具体实现
public void algorithm() {
//算法A
}
}
对Context类而言,在它与抽象策略类之间建立一个关联关系,代码:
class Context {
private AbstractStrategy strategy; //维持一个对抽象策略类的引用
public void setStrategy(AbstractStrategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
一般客户端使用的代码:
……
Context context = new Context();
AbstractStrategy strategy;
strategy = new ConcreteStrategyA(); //可在运行时指定类型
context.setStrategy(strategy);
context.algorithm();
……
策略模式优点:
- 提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为
- 提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码
- 提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。
- 可以避免多重条件选择语句。多重条件选择语句不易维护
- 提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类
策略模式缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类
- 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
12 模板方法模式 Template Method Pattern
模式定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,模板方法模式是一种基于继承的代码复用技术
模板方法模式包含如下两个角色:
- AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
- ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口
基本方法是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
- 抽象方法(Abstract Method):一个抽象方法由抽象类声明、由其具体子类实现
- 具体方法(Concrete Method):一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承
- 钩子方法(Hook Method):一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现,并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现
抽象类的典型代码:
abstract class AbstractClass
{
//模板方法
public void TemplateMethod()
{
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
//基本方法—具体方法
public void PrimitiveOperation1()
{
//实现代码
}
//基本方法—抽象方法
public abstract void PrimitiveOperation2();
//基本方法—钩子方法
public virtual void PrimitiveOperation3()
{ }
}
模板方法模式优点:
- 使用继承实现了代码复用
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
- 不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则
模板方法模式缺点:
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象