23种设计模式 - Java
文章结构整理自:《Java 设计模式 - 刘伟》
UML 类图来源:23种常用设计模式的UML类图 - ShareAndCreate
面向对象设计原则
- 单一职责原则 Single Responsibility Principle, SRP
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中 - 开闭原则 Open-Closed Principle, OCP
软件实体应当对扩展开放,对修改关闭 - 里氏替换原则 Liskov Substitution Principle, LSP
所有引用基类的地方必须能透明地使用其子类的对象 - 依赖倒转原则 Dependence Inversion Principle, DIP
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象 - 接口隔离原则 Interface Segregation Principle, ISP
客户端不应该依赖那些它不需要的接口 - 合成复用原则 Composite Reuse Principle, CRP
优先使用对象组合,而不是通过继承来达到复用的目的 - 迪米特法则 Law of Demeter, LoD
每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
设计模式之间的关系
创建型
这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。
工厂方法模式 - Factory Method
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。
- Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
- ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
- Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
- ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。
与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或者具体类。
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();
// ……
可以通过配置文件来存储具体工厂类 ConcreteFactory 的类名,更换新的具体工厂时无须修改源代码,系统扩展更为方便。
抽象工厂模式 - Abstract Factory
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的产品,这些产品构成了一个产品族。
- AbstractFactory(抽象工厂):它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
- ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
- AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
- ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法。
在抽象工厂中声明了多个工厂方法,用于创建不同类型的产品,抽象工厂可以是接口,也可以是抽象类或者具体类。
abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); //工厂方法一
public abstract AbstractProductB createProductB(); //工厂方法二
……
}
具体工厂实现了抽象工厂,每一个具体的工厂方法可以返回一个特定的产品对象,而同一个具体工厂所创建的产品对象构成了一个产品族。
class ConcreteFactory1 extends AbstractFactory {
//工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
……
}
与工厂方法模式一样,抽象工厂模式也可为每一种产品提供一组重载的工厂方法,以不同的方式对产品对象进行创建。
建造者(生成器)模式 - Builder
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- Builder(抽象建造者):它为创建一个产品 Product 对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是 buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是 getResult(),它们用于返回复杂对象。Builder 既可以是抽象类,也可以是接口。
- ConcreteBuilder(具体建造者):它实现了 Builder 接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
- Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。
- Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其 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;
}
}
在 ConcreteBuilder 中实现了 buildPartX() 方法,通过调用 Product 的 setPartX() 方法可以给产品对象的成员属性设值。
public class ConcreteBuilder1 extends Builder {
public void buildPartA() {
product.setPartA("A1");
}
public void buildPartB() {
product.setPartB("B1");
}
public void buildPartC() {
product.setPartC("C1");
}
}
Director 类主要有两个作用:一方面它隔离了客户与创建过程;另一方面它控制产品的创建过程,包括某个 buildPartX() 方法是否被调用以及多个 buildPartX() 方法调用的先后次序等。
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
// 产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
客户端调用:
Builder builder = new ConcreteBuilder(); //可通过配置文件实现
Director director = new Director(builder);
Product product = director.construct();
原型模式 - Prototype
使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
- Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
- ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类 Prototype 编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
原型模式的核心在于如何实现克隆方法,下面将介绍两种在 Java 语言中常用的克隆实现方法:
1、通用实现方法
通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同。
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.setAttr(this.attr);
return prototype;
}
}
// 调用
Prototype obj1 = new ConcretePrototype();
obj1.setAttr("Sunny");
Prototype obj2 = obj1.clone();
2、Java 语言提供的 clone() 方法
class ConcretePrototype implements Cloneable {
// ……
public Prototype clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return (Prototype) object;
}
// ……
}
一般而言,Java语言中的 clone() 方法满足:
(1) 对任何对象 x,都有 x.clone() != x,即克隆对象与原型对象不是同一个对象;
(2) 对任何对象 x,都有 x.clone().getClass() == x.getClass(),即克隆对象与原型对象的类型一样;
(3) 如果对象 x 的 equals() 方法定义恰当,那么 x.clone().equals(x) 应该成立。
浅拷贝:在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。在 Java 语言中,通过覆盖 Object 类的 clone() 方法可以实现浅克隆。
深拷贝:在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。在 Java 语言中,如果需要实现深克隆,可以通过序列化(Serialization)等方式来实现。
// 深拷贝实现 public T deepClone() throws IOException, ClassNotFoundException, OptionalDataException { // 将对象写入流中 ByteArrayOutputStream bao=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bao); oos.writeObject(this); // 将对象从流中取出 ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray()); ObjectInputStream ois=new ObjectInputStream(bis); return (T) ois.readObject(); }
单例模式 - Singleton
确保某一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
- Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的 getInstance() 工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个 Singleton 类型的静态对象,作为外部共享的唯一实例。
Java 实现单例模式的五种方式:
1、饿汉式
public class Singleton {
private static Singleton instance = new Singleton(); // 直接初始化一个实例对象
// private类型的构造函数,确保其它类对象不能new一个本类的实例
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
饿汉式在类被加载的时候就创建好了实例,此时有可能实例还没有用到。
优点:实现简单,比较直观;
缺点:当系统中这样的类比较多时,会使启动速度变慢。
2、懒汉式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式只有在实际需要的时候才会创建实例,实现起来也很简单,由一个静态方法返回实例,同时对这个静态方法加了一把锁,所有来获取实例的方法都会去竞争这把锁,不管实例有没有创建。
但这也带来了一个问题,就是锁的粒度太粗。实际上,只有首次创建实例时,为了防止多线程同时创建多个实例才需要锁,当实例已经创建完成后,直接返回实例就好,不再需要什么锁了。但在当前示例里,不管实例有没有创建,只要想获取实例,就要去竞争锁,显然是扩大了锁的竞争范围,效率肯定会降低。
3、双重锁检测模式(Double Check Lock)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
从代码中可以看出,DCL模式在方法内部,对实例是否已创建进行了两次检测,一次加锁,所以叫双重锁检测。
4、静态内部类模式
public class Singleton {
private Singleton() {
}
private static class Inner {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Inner.instance;
}
}
静态内部类模式的实现原理:外部类加载时,并不会立刻加载内部类,内部类不被加载,就不会去创建实例,只有当 getInstance() 方法被调用时,才会加载内部类去创建实例,所以实现了延迟加载功能。
5、通过枚举实现
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("do something!");
}
}
// 调用方法
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。
结构型
这类模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
适配器模式 - Adapter
将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器的使用频率更高。
- Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
- Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承 Target 并关联一个 Adaptee 对象使二者产生联系。
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
1、类适配器
class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}
2、对象适配器
class Adapter extends Target {
private Adaptee adaptee; // 维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest(); // 转发调用
}
}
桥接模式 - Bridge
将抽象部分与它的实现部分解耦,使得两者都能够独立变化。
- Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个 Implementor(实现类接口)类型的对象并可以维护该对象,它与 Implementor 之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
- RefinedAbstraction(扩充抽象类):扩充由 Abstraction 定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在 Abstraction 中声明的抽象业务方法,在 RefinedAbstraction 中可以调用在 Implementor 中定义的业务方法。
- Implementor(实现类接口):定义实现类的接口,这个接口不一定要与 Abstraction 的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor 接口仅提供基本操作,而 Abstraction 定义的接口可能会做更多更复杂的操作。Implementor 接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在 Abstraction 中不仅拥有自己的方法,还可以调用到 Implementor 中定义的方法,使用关联关系来替代继承关系。
- ConcreteImplementor(具体实现类):具体实现 Implementor 接口,在不同的 ConcreteImplementor 中提供基本操作的不同实现,在程序运行时,ConcreteImplementor 对象将替换其父类对象,提供给抽象类具体的业务操作方法。
interface Implementor {
public void operationImpl();
}
abstract class Abstraction {
protected Implementor impl; // 定义实现类接口对象
public void setImpl(Implementor impl) {
this.impl = impl;
}
public abstract void operation(); // 声明抽象业务方法
}
class RefinedAbstraction extends Abstraction {
public void operation() {
//业务代码
impl.operationImpl(); // 调用实现类的方法
//业务代码
}
}
组合模式 - Composite
组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。
在组合模式中引入了抽象构件类 Component,它是所有容器类和叶子类的公共父类,客户端针对 Component 进行编程。
- Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
- Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
abstract class Component {
public abstract void add(Component c); // 增加成员
public abstract void remove(Component c); // 删除成员
public abstract Component getChild(int i); // 获取成员
public abstract void operation(); // 业务方法
}
class Leaf extends Component {
public void add(Component c) {
// 异常处理或错误提示
}
public void remove(Component c) {
// 异常处理或错误提示
}
public Component getChild(int i) {
// 异常处理或错误提示
return null;
}
public void operation() {
// 叶子构件具体业务方法的实现
}
}
class Composite extends Component {
private ArrayList<Component> list = new ArrayList<>();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
return (Component) list.get(i);
}
public void operation() {
// 容器构件具体业务方法的实现
// 递归调用成员构件的业务方法
for (Object obj : list) {
((Component) obj).operation();
}
}
}
装饰模式 - Decorator
动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。
- Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
- ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
- Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
- ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
class Decorator implements Component {
private Component component; //维持一个对抽象构件对象的引用
public Decorator(Component component) { //注入一个抽象构件类型的对象
this.component = component;
}
public void operation() {
component.operation(); //调用原有业务方法
}
}
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation(); //调用原有业务方法
addedBehavior(); //调用新增业务方法
}
//新增业务方法
public void addedBehavior() {
//……
}
}
外观模式 - Facade
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式中所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统。子系统类通常是一些业务类,实现了一些具体的、独立的业务功能。
- 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();
}
}
享元模式 - Flyweight
运用共享技术有效地支持大量细粒度对象的复用。
- Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key) {
//如果对象存在,则直接从享元池获取
if(flyweights.containsKey(key)){
return (Flyweight) flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。
class Flyweight {
//内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时也可以传入不同的外部状态
public void operation(String extrinsicState) {
//......
}
}
代理模式 - Proxy
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
- Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
- Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
- RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
代理模式的结构图比较简单,但是在真实的使用和实现过程中要复杂很多,特别是代理类的设计和实现。
abstract class Subject {
public abstract void Request();
}
class RealSubject : Subject {
public override void Request() {
//业务方法具体实现代码
}
}
class Proxy : Subject {
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void PreRequest() {
//......
}
public override void Request() {
PreRequest();
realSubject.Request(); //调用真实主题对象的方法
PostRequest();
}
public void PostRequest() {
//......
}
}
在实际开发过程中,代理类的实现比上述代码要复杂很多,代理模式根据其目的和实现方式不同可分为很多种类:
1、远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可以是在另一台主机中,远程代理又称为大使(Ambassador)。
2、虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
3、保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
4、缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
5、智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
Java 动态代理:
public interface Star {
String sing(String name);
void dance();
}
public class SuperStar implements Star {
private String name;
public SuperStar(String name) {
this.name = name;
}
public String sing(String name) {
System.out.println(this.name + "正在唱:" + name);
return "谢谢!谢谢!";
}
public void dance() {
System.out.println(this.name + "正在优美地跳舞~");
}
}
public class ProxyUtil {
public static Star createProxy(SuperStar superStar) {
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(method);
// 通过反射代理方法
Object result = method.invoke(superStar, args);
after();
return result;
}
public void before(Method method) {
if (method.getName().equals("sing")) {
System.out.println("准备话筒,收钱 20 万");
} else if (method.getName().equals("dance")) {
System.out.println("准备场地,收钱 1000 万");
}
}
public void after() {
System.out.println("收拾场地");
}
});
return starProxy;
}
}
public class Test {
public static void main(String[] args) {
Star starProxy = ProxyUtil.createProxy(new SuperStar("杨超越"));
System.out.println(starProxy.sing("好日子"));
starProxy.dance();
}
}
Output:
准备话筒,收钱 20 万
杨超越正在唱:好日子
收拾场地
谢谢!谢谢!
准备场地,收钱 1000 万
杨超越正在优美地跳舞~
收拾场地
行为模式
这类模式负责对象间的高效沟通和职责委派。
职责链模式 - Chain of Responsibility
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
- Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每一个处理者的下家还是一个处理者,因此在抽象处理者中定义了一个抽象处理者类型的对象(如结构图中的 successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
- ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。
abstract class Handler {
//维持对下家的引用
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(String request);
}
class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (请求满足条件) {
//处理请求
}
else {
this.successor.handleRequest(request); //转发请求
}
}
}
命令模式 - Command
将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
其别名为动作(Action)模式或事务(Transaction)模式。
- Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的 execute() 等方法,通过这些方法可以调用请求接收者的相关操作。
- ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现 execute() 方法时,将调用接收者对象的相关操作(Action)。
- Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的 execute() 方法,从而实现间接调用请求接收者的相关操作。
- Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的 execute() 方法,每个具体命令类将一个 Receiver 类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了 execute() 方法的不同实现,并调用不同接收者的请求处理方法。
abstract class Command {
public abstract void execute();
}
class Invoker {
private Command command;
//构造注入
public Invoker(Command command) {
this.command = command;
}
//设值注入
public void setCommand(Command command) {
this.command = command;
}
//业务方法,用于调用命令类的execute()方法
public void call() {
command.execute();
}
}
具体命令类继承了抽象命令类,它与请求接收者相关联,实现了在抽象命令类中声明的 execute() 方法,并在实现时调用接收者的请求响应方法 action()。
class ConcreteCommand extends Command {
private Receiver receiver; //维持一个对请求接收者对象的引用
public void execute() {
receiver.action(); //调用请求接收者的业务处理方法action()
}
}
class Receiver {
public void action() {
//具体操作
}
}
解释器模式 - Interpreter
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
- AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
- TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
- NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
- Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。
abstract class AbstractExpression {
public abstract void interpret(Context ctx);
}
class TerminalExpression extends AbstractExpression {
public void interpret(Context ctx) {
//终结符表达式的解释操作
}
}
class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left,AbstractExpression right) {
this.left = left;
this.right = right;
}
public void interpret(Context ctx) {
//递归调用每一个组成部分的interpret()方法
//在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}
除了上述用于表示表达式的类以外,通常在解释器模式中还提供了一个环境类 Context,用于存储一些全局信息,通常在 Context 中包含了一个 HashMap 或 ArrayList 等类型的集合对象(也可以直接由 HashMap 等集合类充当环境类),存储一系列公共信息,如变量名与值的映射关系(key/value)等,用于在进行具体的解释操作时从中获取相关信息。
class Context {
private HashMap map = new HashMap();
public void assign(String key, String value) {
//往环境类中设值
}
public String lookup(String key) {
//获取存储在环境类中的值
}
}
迭代器模式 - Iterator
提供一种方法顺序访问一个聚合对象中的各个元素,而又不用暴露该对象的内部表示。
- Iterator(抽象迭代器):它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法,例如:用于获取第一个元素的 first() 方法,用于访问下一个元素的 next() 方法,用于判断是否还有下一个元素的 hasNext() 方法,用于获取当前元素的 currentItem() 方法等,在具体迭代器中将实现这些方法。
- ConcreteIterator(具体迭代器):它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数。
- Aggregate(抽象聚合类):它用于存储和管理元素对象,声明一个 createIterator() 方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
- ConcreteAggregate(具体聚合类):它实现了在抽象聚合类中声明的 createIterator() 方法,该方法返回一个与该具体聚合类对应的具体迭代器 ConcreteIterator 实例。
在具体迭代器中将实现抽象迭代器声明的遍历数据的方法。
interface Iterator {
public void first(); //将游标指向第一个元素
public void next(); //将游标指向下一个元素
public boolean hasNext(); //判断是否存在下一个元素
public Object currentItem(); //获取游标指向的当前元素
}
class ConcreteIterator implements Iterator {
private ConcreteAggregate objects; //维持一个对具体聚合对象的引用,以便于访问存储在聚合对象中的数据
private int cursor; //定义一个游标,用于记录当前访问位置
public ConcreteIterator(ConcreteAggregate objects) {
this.objects = objects;
}
public void first() { ...... }
public void next() { ...... }
public boolean hasNext() { ...... }
public Object currentItem() { ...... }
}
具体聚合类作为抽象聚合类的子类,一方面负责存储数据,另一方面实现了在抽象聚合类中声明的工厂方法 createIterator(),用于返回一个与该具体聚合类对应的具体迭代器对象。
interface Aggregate {
Iterator createIterator();
}
class ConcreteAggregate implements Aggregate {
//......
public Iterator createIterator() {
return new ConcreteIterator(this);
}
//......
}
中介者模式 - Mediator
定义一个对象来封装一系列对象的交互。中介者模式使各对象之间不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- Mediator(抽象中介者):它定义一个接口,该接口用于与各同事对象之间进行通信。
- ConcreteMediator(具体中介者):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。
- Colleague(抽象同事类):它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
- ConcreteColleague(具体同事类):它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。
中介者模式的核心在于中介者类的引入,在中介者模式中,中介者类承担了两方面的职责:
1、中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。
2、协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。
在具体中介者类中将调用同事类的方法,调用时可以增加一些自己的业务代码对调用进行控制。
abstract class Mediator {
protected ArrayList<Colleague> colleagues; //用于存储同事对象
//注册方法,用于增加同事对象
public void register(Colleague colleague) {
colleagues.add(colleague);
}
//声明抽象的业务方法
public abstract void operation();
}
class ConcreteMediator extends Mediator {
//实现业务方法,封装同事之间的调用
public void operation() {
//......
((Colleague) (colleagues.get(0))).method1(); //通过中介者调用同事类的方法
//......
}
}
在抽象同事类中维持了一个抽象中介者的引用,用于调用中介者的方法。
abstract class Colleague {
protected Mediator mediator; //维持一个抽象中介者的引用
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void method1(); //声明自身方法,处理自己的行为
//定义依赖方法,与中介者进行通信
public void method2() {
mediator.operation();
}
}
class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator mediator) {
super(mediator);
}
//实现自身方法
public void method1() {
//......
}
}
备忘录模式 - Memento
在不破坏封装的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
- Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
- Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
- Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性。
public class Originator {
private String state;
public Originator() {}
// 创建一个备忘录对象
public Memento createMemento() {
return new Memento(this);
}
// 根据备忘录对象恢复原发器状态
public void restoreMemento(Memento m) {
state = m.state;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return this.state;
}
}
对于备忘录类 Memento 而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态。
在设计备忘录类时需要考虑其封装性,除了 Originator 类,不允许其他类来调用备忘录类 Memento 的构造函数与相关方法,如果不考虑封装性,允许其他类调用 setState() 等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义。
// 备忘录类,默认可见性,包内可见
class Memento {
private String state;
public Memento(Originator o) {
state = o.getState();
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return this.state;
}
}
对于负责人类 Caretaker,它用于保存备忘录对象,并提供 getMemento() 方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
观察者模式 - Observer
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
- Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法 notify()。目标类可以是接口,也可以是抽象类或具体类。
- ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
- Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法 update(),因此又称为抽象观察者。
- ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者 Observer 中定义的 update() 方法。通常在实现时,可以调用具体目标类的 attach() 方法将自己添加到目标类的集合中或通过 detach() 方法将自己从目标类的集合中删除。
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();
}
class ConcreteObserver implements Observer {
//实现响应方法
public void update() {
//具体响应代码
}
}
观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web 应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。观察者模式广泛应用于各种编程语言的 GUI 事件处理的实现,在基于事件的 XML 解析技术(如 SAX2)以及 Web 事件处理中也都使用了观察者模式。
状态模式 - State
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类 State 的实例,这个实例定义当前状态,在具体实现时,它是一个 State 子类的对象。
- State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
- ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
abstract class State {
//声明抽象业务方法,不同的具体状态类可以不同的实现
public abstract void handle();
}
class ConcreteState extends State {
public void handle() {
//方法具体实现代码
}
}
环境类维持一个对抽象状态类的引用,通过 setState() 方法可以向环境类注入不同的状态对象,再在环境类的业务方法中调用状态对象的方法。
class Context {
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,该属性值的变化可能会导致对象状态发生变化
//设置状态对象
public void setState(State state) {
this.state = state;
}
public void request() {
//其他代码
state.handle(); //调用状态对象的业务方法
//其他代码
}
}
策略模式 - Strategy
定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。
- Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
- Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
- ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
abstract class AbstractStrategy {
public abstract void algorithm(); //声明抽象算法
}
class ConcreteStrategyA extends AbstractStrategy {
//算法的具体实现
public void algorithm() {
//算法A
}
}
class Context {
private AbstractStrategy strategy; //维持一个对抽象策略类的引用
public void setStrategy(AbstractStrategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
模板方法模式 - Template Method
定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- AbstractClass(抽象类):在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
- ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
abstract class AbstractClass {
//模板方法
public void TemplateMethod() {
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
//基本方法—具体方法
public void PrimitiveOperation1() {
//实现代码
}
//基本方法—抽象方法
public abstract void PrimitiveOperation2();
//基本方法—钩子方法
public void PrimitiveOperation3() {}
}
class ConcreteClass extends AbstractClass {
public void PrimitiveOperation2() {
//实现代码
}
public void PrimitiveOperation3() {
//实现代码
}
}
访问者模式 - Visitor
提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
- Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类 ConcreteElement 声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
- ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
- Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个 accept() 方法,该方法通常以一个抽象访问者作为参数。
- ConcreteElement(具体元素):具体元素实现了 accept() 方法,在 accept() 方法中调用访问者的访问方法以便完成对一个元素的操作。
- ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个 List 对象或一个 Set 对象。
在访问者模式中,抽象访问者定义了访问元素对象的方法,通常为每一种类型的元素对象都提供一个访问方法,而具体访问者可以实现这些访问方法。这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,如 visitElementA(ElementA elementA),还有一种是统一取名为 visit(),通过参数类型的不同来定义一系列重载的 visit() 方法。
abstract class Visitor {
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
public void visit(ConcreteElementC elementC) {
//元素ConcreteElementC操作代码
}
}
class ConcreteVisitor extends Visitor {
public void visit(ConcreteElementA elementA) {
//元素ConcreteElementA操作代码
}
public void visit(ConcreteElementB elementB) {
//元素ConcreteElementB操作代码
}
}
对于元素类而言,在其中一般都定义了一个 accept() 方法,用于接受访问者的访问。
interface Element {
public void accept(Visitor visitor);
}
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
//业务方法
}
}
这种调用机制也称为“双重分派”,正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的 accept() 方法,程序运行时将回调在新增 Visitor 类中定义的 visit() 方法,从而增加新的元素访问方式。