GOF23

概览

23种设计模式主要可以分为三种类型:

  1. 创建型模式:用来创建对象

    单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

  2. 结构型模式:是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。(关注对象和类的组成关系)

    适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。

  3. 行为型模式:关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责

    模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

Go常用设计模式

单例模式

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

单例模式同时解决了两个问题, 所以违反了单一职责原则

  1. 保证一个类只有一个实例。 为什么会想要控制一个类所拥有的实例数量? 最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。它的运作方式是这样的: 如果创建了一个对象, 同时过一会儿后决定再创建一个新对象, 此时会获得之前已创建的对象, 而不是一个新对象。

    注意, 普通构造函数无法实现上述行为, 因为构造函数的设计决定了它必须总是返回一个新对象。

  2. 为该实例提供一个全局访问节点。 全局变量在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。

    和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。

    还有一点: 我们不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

常见的五种单例模式实现方式

  • 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
  • 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
  • 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
  • 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
  • 枚举单例(线程安全,调用效率高,不能延时加载)

饿汉式实现

类初始化时立即加载这个对象

public class SingletonDemo1 {
      //类初始化时,立即加载这个对象。
      //加载类时,天然是线程安全的
    private static SingletonDemo1 instance = new SingletonDemo1();
      //私有化构造器
    private SingletonDemo1(){
    }
      //方法没有同步,调用效率高
    public static SingletonDemo1 getInstance(){
        return instance;
    }
}

饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

懒汉式实现

单例对象延迟加载(懒加载),真正需要使用的时候才加载

package com.xm.singleton;

/**
 * 懒汉式延迟加载
 */
public class SingletonDemo2 {
    private static SingletonDemo2 instance;
    private SingletonDemo2(){}
    
    //方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance(){
        if(instance==null){
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

资源利用率高,但是,每次调用getInstance()方法都要同步,并发效率较低。

双重检测锁实现

这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。

public class SingletonDemo3 {
    private static SingletonDemo3 instance=null;
    private SingletonDemo3(){}
    public static SingletonDemo3 getInstance(){
        if(instance==null){
            SingletonDemo3 singleton;
            synchronized (SingletonDemo3.class){
                singleton=instance;
                if(singleton == null){
                    synchronized (SingletonDemo3.class){
                        if(singleton == null){
                            singleton = new SingletonDemo3();
                        }
                    }
                    instance = singleton;
                }
            }
        }
        return instance;
    }
}

由于编译器优化原因和 JVM 底层内部模型原因, 偶尔会出问题。不建议使用。

Go 版本的实现:

package singleton

import (
    "fmt"
    "sync"
)

var lock = &sync.Mutex{}

type single struct{}

var singleInstance *single

func getInstance() *single {
    if singleInstance == nil {
        lock.Lock()
        defer lock.Unlock()
        if singleInstance == nil {
            fmt.Println("Creating single instance now.")
            singleInstance = &single{}
            return singleInstance
        }
        fmt.Println("Single instance already created.")
        return singleInstance
    }
    fmt.Println("Single instance already created.")
    return singleInstance
}

线程安全版本(基于 sync.Once)的 Go 实现:

import (
    "fmt"
    "sync"
)

var once sync.Once

type single2 struct {
}

var singleInstance2 *single2

func getInstance2() *single2 {
    if singleInstance == nil {
        once.Do(func() {
            fmt.Println("Creating single instance now.")
            singleInstance2 = &single2{}
        })
        return singleInstance2
    }

    fmt.Println("Single instance already created.")
    return singleInstance2
}

静态内部类的实现

静态内部类实现也是一种懒加载的方式

public class SingletonDemo4 {
    
    //静态内部类
    private static class SingletonClassInstance{
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }
    
    public static SingletonDemo4 getInstance(){
        return SingletonClassInstance.instance;
    }
    
    private SingletonDemo4(){}
}

优点:

  • 外部类没有static属性,则不会像饿汉式那样立即加载对象。
  • 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的instancestatic final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.
  • 兼备了并发高效调用和延迟加载的优势

使用枚举的实现

public enum SingletonDemoe5 {
    //这个枚举元素,本身就是单例对象
    INSTANCE;

    //添加自己需要的操作
    public void singletonOperation(){

    }
}
  • 实现简单
  • 枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞!
  • 缺点:无延迟加载

利用反射破解单例(针对枚举之外)

/**
 * 懒汉式反射反序列化破解
 */
public class Client {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        SingletonDemo2 s1 = SingletonDemo2.getInstance();
        SingletonDemo2 s2 = SingletonDemo2.getInstance();

        System.out.println(s1);
        System.out.println(s2);

        Class<SingletonDemo2> singletonDemo2Class = (Class<SingletonDemo2>) Class.forName("com.xm.singleton.SingletonDemo2");
        //获取无参构造方法
        Constructor<SingletonDemo2> constructor = singletonDemo2Class.getDeclaredConstructor(null);
        constructor.setAccessible(true);//忽略权限修饰符
        SingletonDemo2 s3 = constructor.newInstance();//通过newInstance创建对象
        SingletonDemo2 s4 = constructor.newInstance();//通过newInstance创建对象

        System.out.println(s3);
        System.out.println(s4);
    }
}

解决方案:在调用构造方法时,如果实例已经存在则抛出异常

/**
 * 懒汉式延迟加载
 */
public class SingletonDemo2 {
    private static SingletonDemo2 instance;
    private SingletonDemo2(){
          //避免反射破解
        if(instance != null){
            throw new RuntimeException();
        }
    }

    //方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance(){
        if(instance==null){
            instance = new SingletonDemo2();
        }
        return instance;
    }
}

利用序列化破解单例(针对枚举之外)

public class Client2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        SingletonDemo2 s1 = SingletonDemo2.getInstance();
        System.out.println(s1);

        //通过反序列化的方式构造多个对象
        //序列化
        FileOutputStream fos = new FileOutputStream("a.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.close();
        fos.close();

        //反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
        SingletonDemo2 s2 = (SingletonDemo2)ois.readObject();
        System.out.println(s2);
    }
}

解决方案:在类中定义readResolve()方法,反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不需要再单独创建新对象

public class SingletonDemo2 implements Serializable {
    private static SingletonDemo2 instance;
    private SingletonDemo2(){
        if(instance != null){
            throw new RuntimeException();
        }
    }

    //方法同步,调用效率低
    public static synchronized SingletonDemo2 getInstance(){
        if(instance==null){
            instance = new SingletonDemo2();
        }
        return instance;
    }

    private Object readResolve(){
        return instance;
    }
}

利用多线程测试

借助同步辅助类CountDownLatch,在完成一组正在其他线程中执行的操作之前,它允许一 个或多个线程一直等待。

  • countDown() 当前线程调此方法,则计数减一(建议放在 finally里执行)
  • await(), 调用此方法会一直阻塞当前线程,直到计时器的值为0
public class Client3 {
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        int threadNum = 10;
          //初始计数器为10
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);

        for(int i=0;i<threadNum;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=0;i<10000000;i++){
//                        Object o = SingletonDemo4.getInstance();
                        Object o = SingletonDemo5.INSTANCE;
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();

        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}

如果不使用CountDownLatch工具类,main进程在创建出子进程后,会继续往下执行,不会等待子进程执行结束,会造成end记录的时间不是正确的结束时间,而是main进程执行到这里的时间

适用场景

  1. 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。

单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。

  1. 如果需要更加严格地控制全局变量, 可以使用单例模式。

单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。

请注意, 可以随时调整限制并设定生成单例实例的数量, 只需修改获取实例方法。

优缺点

优点

  1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
  2. 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理

缺点

  1. 违反了单一职责原则。 该模式同时解决了两个问题。
  2. 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
  3. 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  4. 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

与其他设计模式的关系

  • 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
  • 如果能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。
  • 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。

工厂模式

工厂模式实现了创建者和调用者分离,本质上是实例化对象,用工厂方法代替 new 操作。 同时,将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。

工厂模式可以分为三类:

  • 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
  • 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
  • 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

简单工厂模式

简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法, 通过接收的参数的不同来返回不同的对象实例。 其缺陷是对于增加新产品无能为力,不修改代码的话,是无法扩展的。

//汽车接口
public interface Car {
    void run();
}

//汽车接口的实现类
public class Audi implements Car {
    @Override
    public void run() {
        System.out.println("奥迪汽车");
    }
}
public class Byd implements Car {
    @Override
    public void run() {
        System.out.println("比亚迪汽车");
    }
}

//简单工厂
public class CarFactory {
    public static Car createCar(String type){
        if("奥迪".equals(type)){
            return new Audi();
        }else if("比亚迪".equals(type)){
            return new Byd();
        }else{
            return null;
        }
    }
}
public class CarFactory2 {
    public static Car createAudi(){
        return new Audi();
    }

    public static Car createByd(){
        return new Byd();
    }
}

/**
 * 简单工厂调用
 */
public class Client {
    public static void main(String[] args) {
        Car car1 = CarFactory.createCar("奥迪");
        Car car2 = CarFactory.createCar("比亚迪");

        car1.run();
        car2.run();
    }
}

go 代码实现:

package factory

import "fmt"

// IGun 枪支接口
type IGun interface {
    setName(name string)
    setPower(power int)
    getName() string
    getPower() int
}

// AK47 具体品牌的枪支 AK47
type AK47 struct {
    name  string
    power int
}

func newAK47() IGun {
    return &AK47{}
}

func (a *AK47) setName(name string) {
    a.name = name
}

func (a *AK47) setPower(power int) {
    a.power = power
}

func (a *AK47) getName() string {
    return a.name
}

func (a *AK47) getPower() int {
    return a.power
}

// Musket 具体产品 Musket
type Musket struct {
    name  string
    power int
}

func newMusket() IGun {
    return &Musket{}
}

func (m *Musket) setName(name string) {
    m.name = name
}

func (m *Musket) setPower(power int) {
    m.power = power
}

func (m *Musket) getName() string {
    return m.name
}

func (m *Musket) getPower() int {
    return m.power
}

// getGun 具体工厂
func getGun(gunType string) (IGun, error) {
    if gunType == "ak47" {
        return newAK47(), nil
    }
    if gunType == "musket" {
        return newMusket(), nil
    }
    return nil, fmt.Errorf("unsupported gun type")
}

工厂方法模式

工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。

//统一工厂接口
public interface CarFactory {
    Car createCar();
}

//不同的汽车创建不同的工厂实现类
public class AudiFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Audi();
    }
}
public class BydFactory implements CarFactory {
    @Override
    public Car createCar() {
        return new Byd();
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Car car1 = new AudiFactory().createCar();
        Car car2 = new BydFactory().createCar();

        car1.run();
        car2.run();
    }
}

根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式。

简单工厂/工厂方法模式的应用场景

  1. 当在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法

    工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。

    例如, 如果需要向应用中添加一种新产品, 只需要开发新的创建者子类, 然后重写其工厂方法即可。

  2. 如果希望用户能扩展软件库或框架的内部组件, 可使用工厂方法

    继承可能是扩展软件库或框架默认行为的最简单方法。 但是当使用子类替代标准组件时, 框架如何辨识出该子类?

    解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。

    假设使用开源 UI 框架编写自己的应用,并希望在应用中使用圆形按钮, 但是原框架仅支持矩形按钮。 此时可以使用圆形按钮 RoundButton 子类来继承标准的按钮 Button 类。 但是, 还需要告诉 UI框架 UIFramework 类使用新的子类按钮代替默认按钮。

    为了实现这个功能, 可以根据基础框架类开发子类圆形按钮 UI UIWithRoundButtons , 并且重写其 createButton 创建按钮方法。 基类中的该方法返回按钮对象, 而开发的子类返回圆形按钮对象。 现在, 就可以使用圆形按钮 UI 类代替 UI 框架类。

工厂模式的优缺点

优点

  • 可以避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。 可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
  • 开闭原则。 无需更改现有客户端代码, 就可以在程序中引入新的产品类型。

缺点

  • 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。

与其他设计模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、 原型模式或生成器模式(更灵活但更加复杂)。
  • 抽象工厂模式通常基于一组工厂方法, 但也可以使用原型模式来生成这些类的方法。
  • 可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
  • 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
  • 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

抽象工厂模式

抽象工厂模式针对的是产品族,用来生产不同产品族的全部产品。(对于增加新的产品,无能为力; 支持增加产品族)。在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

//产品群接口
public interface Engine {
    void run();
    void start();
}
public interface Seat {
    void message();
}
public interface Tyre {
    void revolve();
}


//产品群接口实现类
public class LuxuryEngine implements Engine {

    @Override
    public void run() {
        System.out.println("高端发动机run");
    }

    @Override
    public void start() {
        System.out.println("高端发动机start");
    }
}
public class LowEngine implements Engine {
    @Override
    public void run() {
        System.out.println("低端发动机run");
    }

    @Override
    public void start() {
        System.out.println("低端发动机start");
    }
}

public class LuxurySeat implements Seat {
    @Override
    public void message() {
        System.out.println("高端椅子");
    }
}
public class LowSeat implements Seat {
    @Override
    public void message() {
        System.out.println("低端椅子");
    }
}

public class LuxuryTyre implements Tyre {
    @Override
    public void revolve() {
        System.out.println("高端轮胎");
    }
}
public class LowTyre implements Tyre {
    @Override
    public void revolve() {
        System.out.println("低端轮胎");
    }
}


//工厂接口
public class LowTyre implements Tyre {
    @Override
    public void revolve() {
        System.out.println("低端轮胎");
    }
}

//工厂接口的实现:针对不同产品组合
public class LuxuryCarFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LuxuryEngine();
    }

    @Override
    public Seat createSeat() {
        return new LuxurySeat();
    }

    @Override
    public Tyre createTyre() {
        return new LuxuryTyre();
    }
}

public class LowCarFactory implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LowEngine();
    }

    @Override
    public Seat createSeat() {
        return new LowSeat();
    }

    @Override
    public Tyre createTyre() {
        return new LowTyre();
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        CarFactory factory = new LuxuryCarFactory();
        Engine engine = factory.createEngine();
        engine.run();
        engine.start();
    }
}

go 版本实现:

package factory

import "fmt"

// IShoe 抽象产品群-1 鞋子
type IShoe interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

// IShirt 抽象产品群-2 衬衫
type IShirt interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

type Shoe struct {
    logo string
    size int
}

func (s *Shoe) setLogo(logo string) {
    s.logo = logo
}

func (s *Shoe) getLogo() string {
    return s.logo
}

func (s *Shoe) setSize(size int) {
    s.size = size
}

func (s *Shoe) getSize() int {
    return s.size
}

type Shirt struct {
    logo string
    size int
}

func (s *Shirt) setLogo(logo string) {
    s.logo = logo
}

func (s *Shirt) getLogo() string {
    return s.logo
}

func (s *Shirt) setSize(size int) {
    s.size = size
}

func (s *Shirt) getSize() int {
    return s.size
}

// AdidasShoe 鞋子具体产品-1
type AdidasShoe struct{ Shoe }

// NikeShoe 鞋子具体产品-2
type NikeShoe struct{ Shoe }

// AdidasShirt 衬衫具体产品-1
type AdidasShirt struct{ Shirt }

// NikeShirt 衬衫具体产品-2
type NikeShirt struct{ Shirt }

// Adidas adidas 具体工厂
type Adidas struct {
}

func (a *Adidas) makeShoe() IShoe {
    return &AdidasShoe{
        Shoe: Shoe{
            logo: "adidas",
            size: 14,
        },
    }
}

func (a *Adidas) makeShirt() IShirt {
    return &AdidasShirt{
        Shirt: Shirt{
            logo: "adidas",
            size: 14,
        },
    }
}

// Nike nike 具体工厂
type Nike struct {
}

func (n *Nike) makeShoe() IShoe {
    return &NikeShoe{
        Shoe: Shoe{
            logo: "nike",
            size: 14,
        },
    }
}

func (n *Nike) makeShirt() IShirt {
    return &NikeShirt{
        Shirt: Shirt{
            logo: "nike",
            size: 14,
        },
    }
}

// ISportsFactory 抽象工厂
type ISportsFactory interface {
    makeShoe() IShoe
    makeShirt() IShirt
}

func GetSportsFactory(brand string) (ISportsFactory, error) {
    if brand == "adidas" {
        return &Adidas{}, nil
    }

    if brand == "nike" {
        return &Nike{}, nil
    }

    return nil, fmt.Errorf("wrong brand type passed")
}

// 具体使用
func main() {
    factory, _ := GetSportsFactory("adidas")
    adidasShoe := factory.makeShoe()
    adidasShirt := factory.makeShirt()
    adidasShoe.getLogo()
    adidasShirt.getSize()
}

抽象工厂模式适用场景

  1. 如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 不希望代码基于产品的具体类进行构建, 在这种情况下, 可以使用抽象工厂。
  2. 抽象工厂提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么就不会生成与应用程序已生成的产品类型不一致的产品。
  • 如果有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。
  • 在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

抽象工厂的优缺点

优点

  • 可以确保同一工厂生成的产品相互匹配。

  • 可以避免客户端和具体产品代码的耦合。

  • 单一职责原则。 可以将产品生成代码抽取到同一位置, 使得代码易于维护。

  • 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。

缺点

  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、原型模式或生成器模式](更灵活但更加复杂)。
  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许在获取产品前执行一些额外构造步骤。
  • 抽象工厂模式通常基于一组工厂方法, 但也可以使用原型模式来生成这些类的方法。
  • 当只需对客户端代码隐藏子系统创建对象的方式时, 可以使用抽象工厂来代替外观模式。
  • 可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

建造者(生成器)模式

当某个类的创建需要很多的其他类组成时,可以使用建造者模式,它可以分步骤的创建复杂对象,该模式允许使用相同的创建代码生成不同类型和形式的对象。

建造者模式的本质

  • 分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象; 相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
/**
 * 建造飞船
 */
public class AirShip {
    private Engine engine;//发动机
    private OrbitalModule orbitalModule;//轨道舱
    private ExcapeTower excapeTower;//逃逸塔

    //一下省略getter,setter和toString方法
}

class Engine{
    private String name;

    public Engine(String name) {
        this.name = name;
    }
}
class OrbitalModule{
    private String name;

    public OrbitalModule(String name) {
        this.name = name;
    }
}
class ExcapeTower{
    private String name;

    public ExcapeTower(String name) {
        this.name = name;
    }
}


//构建组件
public interface AirShipBuilder {
    Engine builderEngine();
    OrbitalModule builderOrbitalModule();
    ExcapeTower builderExcapeTower();
}

//对组件进行组装
public interface AirShipDirector {
    AirShip directAirShip();
}


//创建具体需要的组件(实现类)
public class XMAirShipBuilder implements AirShipBuilder {

    @Override
    public Engine builderEngine() {
        System.out.println("构造小铭发动机");
        return new Engine("小铭发动机");
    }

    @Override
    public OrbitalModule builderOrbitalModule() {
        System.out.println("构造小铭轨道舱");
        return new OrbitalModule("小铭轨道舱");
    }

    @Override
    public ExcapeTower builderExcapeTower() {
        System.out.println("构造小铭逃逸塔");
        return new ExcapeTower("小铭逃逸塔");
    }
}


//对组件进行组装
public class XMAirShipDirector implements AirShipDirector {

    private XMAirShipBuilder builder;

    public XMAirShipDirector(XMAirShipBuilder builder) {
        this.builder = builder;
    }

    @Override
    public AirShip directAirShip() {
        //通过使用builder来获取子组件
        Engine engine = builder.builderEngine();
        OrbitalModule orbitalModule = builder.builderOrbitalModule();
        ExcapeTower excapeTower = builder.builderExcapeTower();

        //对组件进行组装
        AirShip airShip = new AirShip();
        airShip.setEngine(engine);
        airShip.setOrbitalModule(orbitalModule);
        airShip.setExcapeTower(excapeTower);

        return airShip;
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        XMAirShipDirector director = new XMAirShipDirector(new XMAirShipBuilder());
        AirShip airShip = director.directAirShip();
        System.out.println(airShip);
    }
}

go 版本实现

// Package builder 生成器模式,也叫建造者模式
package builder

import "fmt"

// House 具体的产品
type House struct {
    windowType string
    doorType   string
    floor      int
}

// IBuilder 生成器接口
type IBuilder interface {
    setWindowType()
    setDoorType()
    setNumFloor()
    getHouse() House
}

func getBuilder(builderType string) IBuilder {
    if builderType == "normal" {
        return newNormalBuilder()
    }

    if builderType == "igloo" {
        return newIglooBuilder()
    }
    return nil
}

// NormalBuilder 生成器-1
type NormalBuilder struct {
    windowType string
    doorType   string
    floor      int
}

func newNormalBuilder() *NormalBuilder {
    return &NormalBuilder{}
}

func (b *NormalBuilder) setWindowType() {
    b.windowType = "Wooden Window"
}

func (b *NormalBuilder) setDoorType() {
    b.doorType = "Wooden Door"
}

func (b *NormalBuilder) setNumFloor() {
    b.floor = 2
}

func (b *NormalBuilder) getHouse() House {
    return House{
        doorType:   b.doorType,
        windowType: b.windowType,
        floor:      b.floor,
    }
}

// IglooBuilder 生成器-2
type IglooBuilder struct {
    windowType string
    doorType   string
    floor      int
}

func newIglooBuilder() *IglooBuilder {
    return &IglooBuilder{}
}

func (b *IglooBuilder) setWindowType() {
    b.windowType = "Snow Window"
}

func (b *IglooBuilder) setDoorType() {
    b.doorType = "Snow Door"
}

func (b *IglooBuilder) setNumFloor() {
    b.floor = 1
}

func (b *IglooBuilder) getHouse() House {
    return House{
        doorType:   b.doorType,
        windowType: b.windowType,
        floor:      b.floor,
    }
}

// Director 主管类
type Director struct {
    builder IBuilder
}

func newDirector(b IBuilder) *Director {
    return &Director{
        builder: b,
    }
}

func (d *Director) setBuilder(b IBuilder) {
    d.builder = b
}

func (d *Director) buildHouse() House {
    d.builder.setDoorType()
    d.builder.setWindowType()
    d.builder.setNumFloor()
    return d.builder.getHouse()
}

func main() {
    normalBuilder := getBuilder("normal")
    iglooBuilder := getBuilder("igloo")

    director := newDirector(normalBuilder)
    normalHouse := director.buildHouse()

    director.setBuilder(iglooBuilder)
    iglooHouse := director.buildHouse()

    fmt.Printf("normal house: %+v, igloo house: %+v\n", normalHouse, iglooHouse)
}

适用场景

  1. 使用生成器模式可避免 “重叠构造函数 (tele­scop­ing con­struc­tor)” 的出现。

    假设构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。

    class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    // ...
    

    只有在 C# 或 Java 等支持方法重载的编程语言中才能写出如此复杂的构造函数。

    生成器模式可以分步骤生成对象, 而且允许仅使用必须的步骤。 应用该模式后, 再也不需要将几十个参数塞进构造函数里了。

  2. 当希望使用代码创建不同形式的产品 (例如石头房屋或木头房屋) 时, 可使用生成器模式。

    如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用生成器模式。

    基本生成器接口中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。

  3. 使用生成器构造组合树或其他复杂对象。

    生成器模式能分步骤构造产品。 可以延迟执行某些步骤而不会影响最终产品。 甚至可以递归调用这些步骤, 这在创建对象树时非常方便。

    生成器在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。

优缺点

优点

  • 可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
  • 生成不同形式的产品时, 可以复用相同的制造代码。
  • 单一职责原则。 可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点

由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式(更灵活但更加复杂)。
  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许在获取产品前执行一些额外构造步骤。
  • 可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。
  • 可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。
  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

原型模式(prototype)

通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

实际上原型模式就是 java 中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点 。其优势为:效率高(直接克隆,避免了重新执行构造过程步骤) 。

克隆类似于new,但是不同于 new。new 创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象改变不会影响原型对象。然后, 再修改克隆对象的值。

克隆产生的是一个新对象

原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个克隆方法。

所有的类对 克隆方法的实现都非常相似。 该方法会创建一个当前类的对象, 然后将原始对象所有的成员变量值复制到新建的类中。 甚至可以复制私有成员变量, 因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。

支持克隆的对象即为原型。 当对象有几十个成员变量和几百种类型时, 对其进行克隆甚至可以代替子类的构造。其运作方式如下: 创建一系列不同类型的对象并不同的方式对其进行配置。 如果所需对象与预先配置的对象相同, 那么只需克隆原型即可, 无需新建一个对象。

浅克隆和深克隆

当对象的属性中有引用变量时,实际上克隆后的对象跟原对象所指向的是同一个地址,如果此时修改引用变量的值,会使两个对象的引用变量都发生改变,这就是浅克隆;可以将引用变量也做一份克隆,这就是深克隆(也叫深复制)。

public class Sheep implements Cloneable{
    private String name;
    private Date birthday;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        //深克隆添加如下代码:
        Sheep sheep = (Sheep)obj;
        sheep.birthday = (Date) this.birthday.clone();//把属性也进行克隆
        return sheep;
    }

    public Sheep(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }
      
      //省略getter,setter和toString方法
}


//客户端操作
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date(System.currentTimeMillis());
        Sheep sheep1 = new Sheep("多莉",date);
        Sheep sheep2 = (Sheep) sheep1.clone();

        date.setTime(23490738574947548L);//改变时间值,如果是浅克隆,两个都会改变

        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep1 == sheep2);
    }
}

利用序列化和反序列化技术实现深克隆

不使用java的clone技术,而使用序列化和反序列化

package com.xm.prototype;

import java.io.*;
import java.util.Date;

public class Client2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Date date = new Date(System.currentTimeMillis());
        Sheep sheep1 = new Sheep("多莉",date);

        //序列化sheep1
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(sheep1);//把sheep1写入baos
        byte[] bytes = baos.toByteArray();

        //反序列化创建出克隆对象
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        Sheep sheep2 = (Sheep) ois.readObject();

        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep1==sheep2);

        date.setTime(485974938756438L);
        System.out.println("————————————更改时间——————————");
        System.out.println(sheep1);
        System.out.println(sheep2);
    }
}

效率测试

public class Client3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        testNew(1000);
        testClone(1000);
    }

    public static void testNew(int size){
        long start = System.currentTimeMillis();
        for(int i=0;i<size;i++){
            new Laptop();
        }
        long end = System.currentTimeMillis();
        System.out.println("new方式耗时:"+ (end-start));
    }

    public static void testClone(int size) throws CloneNotSupportedException {
        long start = System.currentTimeMillis();
        Laptop l = new Laptop();
        for(int i=0;i<size;i++){
            Laptop laptop = (Laptop) l.clone();
        }
        long end = System.currentTimeMillis();
        System.out.println("clone方式耗时:"+(end-start));
    }
}

class Laptop implements Cloneable{
    public Laptop(){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Go 版本实现

package prototype

import "fmt"

// Inode 原型接口
type Inode interface {
    print(string)
    clone() Inode
}

// File 具体原型1:文件
type File struct {
    name string
}

func (f *File) print(s string) {
    fmt.Println(s + f.name)
}

func (f *File) clone() Inode {
    return &File{name: f.name + "_clone"}
}

// Folder 具体原型2:文件夹
type Folder struct {
    children []Inode
    name     string
}

func (f *Folder) print(s string) {
    fmt.Println(s + f.name)
    for i := range f.children {
        f.children[i].print(s)
    }
}

func (f *Folder) clone() Inode {
    cloneFolder := &Folder{name: f.name + "_clone"}
    var tempChildren []Inode
    for i := range f.children {
        cloneChildren := f.children[i].clone()
        tempChildren = append(tempChildren, cloneChildren)
    }
    cloneFolder.children = tempChildren
    return cloneFolder
}

func main() {
    file1 := &File{name: "File1"}
    file2 := &File{name: "File2"}
    file3 := &File{name: "File3"}

    folder1 := &Folder{
        children: []Inode{file1},
        name:     "Folder1",
    }

    folder2 := &Folder{
        children: []Inode{folder1, file2, file3},
        name:     "Folder2",
    }
    fmt.Println("\nPrinting hierarchy for Folder2")
    folder2.print("  ")

    cloneFolder := folder2.clone()
    fmt.Println("\nPrinting hierarchy for clone Folder")
    cloneFolder.print("  ")
}

适用场景

  1. 如果需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。

    这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 代码也不能依赖这些对象所属的具体类, 因为你不知道它们的具体信息。

    原型模式为客户端代码提供一个通用接口, 客户端代码可通过这一接口与所有实现了克隆的对象进行交互, 它也使得客户端代码与其所克隆的对象具体类独立开来。

  2. 如果子类的区别仅在于其对象的初始化方式, 那么可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。

    在原型模式中, 可以使用一系列预生成的、 各种类型的对象作为原型。

    客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可。

优缺点

优点

  • 可以克隆对象, 而无需与它们所属的具体类相耦合。

  • 可以克隆预生成原型, 避免反复运行初始化代码。

  • 可以更方便地生成复杂对象。

  • 可以用继承以外的方式来处理复杂对象的不同配置。

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式(更灵活但更加复杂)。
  • 抽象工厂模式通常基于一组工厂方法, 但也可以使用原型模式来生成这些类的方法。
  • 原型可用于保存命令模式的历史记录。
  • 大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。
  • 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
  • 有时候原型可以作为备忘录模式的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。
  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

适配器模式中的角色

  • 目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
  • 需要适配的类(Adaptee):需要适配的类或适配者类,客户端需要调用该类,但是没有接口可以调用,需要被适配。
  • 适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。

实现

适配器模式的实现有类适配器对象适配器,类适配器的局限是只能使用适配一个类,对象适配器可以适配多个

/**
 * 需要被适配的类
 */
public class Adaptee {
    public void request(){
        System.out.println("可以完成客户请求所需要的功能");
    }
}

/**
 * 客户端所期待的接口
 */
public interface Target {
    void handleReq();
}

/**
 * 适配器:类适配器
 */
public class Adapter extends Adaptee implements Target {

    @Override
    public void handleReq() {
        super.request();
    }
}

/**
 * 适配器:对象适配器
 */
public class Adapter2 implements Target {
   
    private  Adaptee adaptee;

    public Adapter2(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void handleReq() {
        adaptee.request();
    }
}

/**
 * 客户端类
 */
public class Client {
    public void test(Target target){
        target.handleReq();
    }

    public static void main(String[] args) {
        Client client = new Client();
        Adaptee adaptee = new Adaptee();
        Target target1 = new Adapter();
        Target target2 = new Adapter2(adaptee);
        client.test(target1);
        client.test(target2);
    }
}

go 版本实现

package adapter

import "fmt"

// Window 提供 usb 插口
type Window struct{}

func (w *Window) InsertUSBPort() {
    fmt.Println("use usb port")
}

// Lightning 接口
type Lightning interface {
    InsertLightingPort()
}

// Mac 电脑,提供了 lightning 接口
type Mac struct{}

func (m *Mac) InsertLightingPort() {
    fmt.Println("use lightning port")
}

// Client 客户端使用 Lightning 接口
type Client struct{}

func (c *Client) UserLightningPort(lightning Lightning) {
    lightning.InsertLightingPort()
}

// USB2LightningAdapter use 转 lightning 适配器
type USB2LightningAdapter struct {
    windowMachine *Window
}

func (a *USB2LightningAdapter) InsertLightingPort() {
    a.windowMachine.InsertUSBPort()
}

func main() {
    client := &Client{}
    mac := &Mac{}
    client.UserLightningPort(mac) // mac 提供了 lightning 接口

    // window 没有 lightning 接口,使用适配器
    win := &Window{}
    adapter := &USB2LightningAdapter{windowMachine: win}
    client.UserLightningPort(adapter)
}

适用场景

  1. 当希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。

    适配器模式允许创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。

  2. 如果需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

    可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。

    将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。

优缺点

优点

  • 单一职责原则,可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点

  • 代码整体复杂度增加, 因为需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

与其他模式的关系

  • 桥接模式通常会于开发前期进行设计, 能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。
  • 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。
  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。
  • 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。
  • 桥接、 状态模式和策略模式(在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 还可以使用它们来和其他开发者讨论模式所解决的问题。

代理模式

通过代理,控制对对象的访问,可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即:AOP的微观实现)。

代理模式可以分为静态代理和动态代理。

AOP(Aspect Oriented Programming面向切面编程)的核心实现机制。

代理模式中的核心角色

  • 抽象角色:定义代理角色和真实角色的公共对外方法
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。(关注真正的业务逻辑)
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。(将统一的流程控制放到代理角色中处理!)

实现方式

静态代理:代理类需要自己创建

//抽象角色
public interface Star {
    void confer();//面谈
    void signContract();//签合同
    void bookTicket();//订票
    void sing();//唱歌
    void collectMoney();//收钱
}

//真实角色
public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("realstar.confer()");
    }

    @Override
    public void signContract() {
        System.out.println("realstar.signContract()");
    }

    @Override
    public void bookTicket() {
        System.out.println("realstar.bookTicker()");
    }

    @Override
    public void sing() {
        System.out.println("realstar.sing()");
    }

    @Override
    public void collectMoney() {
        System.out.println("realstar.collectMoney()");
    }
}

//代理角色
public class ProxyStar implements Star {

    private Star star;//代理的对象

    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void confer() {
        System.out.println("ProxyStar.confer");
    }

    @Override
    public void signContract() {
        System.out.println("ProxyStar.signContract");
    }

    @Override
    public void bookTicket() {
        System.out.println("ProxyStar.bookTicket");
    }

    @Override
    public void sing() {
        //这里需要真实角色的功能
        star.sing();
    }

    @Override
    public void collectMoney() {
        System.out.println("ProxyStar.collectMoney");
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxyStar = new ProxyStar(realStar);

        proxyStar.confer();
        proxyStar.signContract();
        proxyStar.bookTicket();
        proxyStar.sing();//此时调用真实角色
        proxyStar.collectMoney();
    }
}

动态代理:代理类动态生成

实现的方式有以下几种:

  • JDK自带的动态代理
  • javaassist字节码操作库实现
  • CGLIB
  • ASM(底层使用指令,可维护性较差)

JDK实现动态代理

//抽象角色
public interface Star {
    void confer();//面谈
    void signContract();//签合同
    void bookTicket();//订票
    void sing();//唱歌
    void collectMoney();//收钱
}

//真实角色
public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("realstar.confer()");
    }

    @Override
    public void signContract() {
        System.out.println("realstar.signContract()");
    }

    @Override
    public void bookTicket() {
        System.out.println("realstar.bookTicker()");
    }

    @Override
    public void sing() {
        System.out.println("realstar.sing()");
    }

    @Override
    public void collectMoney() {
        System.out.println("realstar.collectMoney()");
    }
}

//动态代理处理器
public class StarHandler implements InvocationHandler {

    Star realStar;

    public StarHandler(Star realStar) {
        this.realStar = realStar;
    }

    @Override
    /**
     *
     * @param proxy 代理类对象
     * @param method 被代理对象的方法
     * @param args 方法的参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = null;
        System.out.println("真正的方法执行前。。。");
        System.out.println("面谈,签合同,预付款,订票");
        if(method.getName().equals("sing")){
            object = method.invoke(realStar, args);//激活调用方法
        }
        System.out.println("真正的方法执行后");
        System.out.println("收尾款");
        return object;
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Star realStar = new RealStar();
        StarHandler handler = new StarHandler(realStar);

        Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Star.class},handler);

        //会调用invoke方法
        proxy.bookTicket();
        proxy.sing();

    }
}

通过Proxy生成的代理类内部会有一个handler属性,调用方法时,实际上都会传到使用handler中的invoke方法,将当前类,当前方法和变量传入到invoke

Go 版本实现

// Package proxy 代理模式
// 例子:Nginx 服务器充当应用程序服务器的代理
package proxy

import "fmt"

// server 服务主题接口
type server interface {
    handleRequest(string, string) (int, string)
}

// Application 真实应用程序
type Application struct{}

func (a *Application) handleRequest(url, method string) (int, string) {
    if url == "/app/status" && method == "GET" {
        return 200, "Ok"
    }

    if url == "/create/user" && method == "POST" {
        return 201, "User Created"
    }
    return 404, "Not Ok"
}

// Nginx 应用程序代理
type Nginx struct {
    application       *Application
    maxAllowedRequest int
    rateLimiter       map[string]int
}

func newNginxServer() *Nginx {
    return &Nginx{
        application:       &Application{},
        maxAllowedRequest: 2,
        rateLimiter:       map[string]int{},
    }
}

func (n *Nginx) handleRequest(url, method string) (int, string) {
    allowed := n.checkRateLimiting(url)
    if !allowed {
        return 403, "Not Allowed"
    }
    return n.application.handleRequest(url, method)
}

func (n *Nginx) checkRateLimiting(url string) bool {
    if n.rateLimiter[url] == 0 {
        n.rateLimiter[url] = 1
    }
    if n.rateLimiter[url] > n.maxAllowedRequest {
        return false
    }
    n.rateLimiter[url] = n.rateLimiter[url] + 1
    return true
}

func main() {
    nginxServer := newNginxServer()
    appStatusURL := "/app/status"
    createuserURL := "/create/user"

    httpCode, body := nginxServer.handleRequest(appStatusURL, "GET")
    fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

    httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
    fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

    httpCode, body = nginxServer.handleRequest(appStatusURL, "GET")
    fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

    httpCode, body = nginxServer.handleRequest(createuserURL, "POST")
    fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)

    httpCode, body = nginxServer.handleRequest(createuserURL, "GET")
    fmt.Printf("\nUrl: %s\nHttpCode: %d\nBody: %s\n", appStatusURL, httpCode, body)
}

适用场景

  1. 延迟初始化 (虚拟代理)。 如果有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。

    无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

  2. 访问控制 (保护代理)。 如果只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。

    代理可仅在客户端凭据满足要求时将请求传递给服务对象。

  3. 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。

    在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。

  4. 记录日志请求 (日志记录代理)。 适用于当需要保存对于服务对象的请求历史记录时。

    代理可以在向服务传递请求前进行记录。

  5. 缓存请求结果 (缓存代理)。适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。

    代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。

  6. 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。

    代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

    代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

优缺点

优点

  • 可以在客户端毫无察觉的情况下控制服务对象。
  • 如果客户端对服务对象的生命周期没有特殊要求, 可以对生命周期进行管理。
  • 即使服务对象还未准备好或不存在, 代理也可以正常工作。
  • 开闭原则。 可以在不对服务或客户端做出修改的情况下创建新代理。

缺点

  • 代码可能会变得复杂, 因为需要新建许多类。
  • 服务响应可能会延迟。

与其他模式的关系

  • 适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。
  • 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。
  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

桥接模式

用于处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联。

桥接模式可以取代多层继承的方案。

多层继承违背了单一职责原则, 复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个 数,从而降低管理和维护的成本。极大的提高了系统可扩展性,在两个变化维度中任意扩展一 个维度,都不需要修改原有的系统,符合开闭原则。

桥接模式将实现的功能分为抽象部分和实现部分。

抽象部分 (也被称为接口) 是一些实体的高阶控制层。 该层自身不完成任何具体的工作, 它需要将工作委派给实现部分层 (也被称为平台)

例如,在实际的程序中, 抽象部分是图形用户界面 (GUI), 而实现部分则是底层操作系统代码 (API), GUI 层调用 API 层来对用户的各种操作做出响应。

一般来说, 可以在两个独立方向上扩展这种应用:

  • 开发多个不同的 GUI (例如面向普通用户和管理员进行分别配置)
  • 支持多个不同的 API (例如, 能够在 Windows、 Linux 和 macOS 上运行该程序)。

实现

其实现核心是在主类中将其中一个或多个维度作为自身属性。

例如电脑可以分为笔记本,台式,平板,又可以有多个牌子,如果采用多层继承,如果增加一个牌子,需要分别增加对应不同笔记本类型的类

//牌子接口
public interface Brand {
    void sale();
}
//不同的牌子
public class Mac implements Brand {
    @Override
    public void sale() {
        System.out.println("苹果电脑");
    }
}
public class Dell implements Brand {
    @Override
    public void sale() {
        System.out.println("戴尔电脑");
    }
}

//电脑类型父类
public class Computer {
     protected Brand brand;//引入品牌变量作为桥梁

    public Computer(Brand brand) {
        this.brand = brand;
    }

    public void sale(){
         brand.sale();
     }
}
//不同的电脑类型
public class Desktop extends Computer {

    public Desktop(Brand brand) {
        super(brand);
    }

    @Override
    public void sale() {
        super.sale();
        System.out.println("台式机");
    }
}
public class Laptop extends Computer {

    public Laptop(Brand brand) {
        super(brand);
    }

    @Override
    public void sale() {
        super.sale();
        System.out.println("笔记本");
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        Computer computer = new Laptop(new Mac());
        computer.sale();
    }
}

Go 版本实现

// Package bridge 桥接模式
// 使用任意计算机连接任意的打印机来完成打印操作
// 抽象层:计算机
// 实施层:打印机
package bridge

import "fmt"

// Printer 打印机实施类
type Printer interface {
    PrintFile()
}

// Computer 电脑抽象类
type Computer interface {
    Print()
    SetPrinter(Printer)
}

// Mac 具体抽象层 1
type Mac struct {
    printer Printer // 抽象层引用实施层
}

func (m *Mac) Print() {
    fmt.Println("print by mac")
    m.printer.PrintFile()
}

func (m *Mac) SetPrinter(printer Printer) {
    m.printer = printer
}

// Window 具体抽象层 2
type Window struct {
    printer Printer
}

func (w *Window) Print() {
    fmt.Println("print by window")
    w.printer.PrintFile()
}

func (w *Window) SetPrinter(printer Printer) {
    w.printer = printer
}

// Epson 具体实施层 1
type Epson struct{}

func (e *Epson) PrintFile() {
    fmt.Println("print by Epson Printer")
}

// Hp 具体实施层 2
type Hp struct{}

func (h *Hp) PrintFile() {
    fmt.Println("print by Hp Printer")
}

func main() {
    hpPrinter := &Hp{}
    epsonPrinter := &Epson{}

    macComputer := &Mac{}
    macComputer.SetPrinter(hpPrinter)
    macComputer.Print()
    macComputer.SetPrinter(epsonPrinter)
    macComputer.Print()

    windowComputer := &Window{}
    windowComputer.SetPrinter(hpPrinter)
    windowComputer.Print()
    windowComputer.SetPrinter(epsonPrinter)
    windowComputer.Print()
}

应用场景

  1. 如果想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。

    类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。

    桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。

  2. 如果希望在几个独立维度上扩展一个类, 可使用该模式。

    桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。

  3. 如果需要在运行时切换不同实现方法, 可使用桥接模式。

    当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。

    顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。

优缺点

优点

  • 可以创建与平台无关的类和程序。
  • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
  • 开闭原则。 可以新增抽象部分和实现部分, 且它们之间不会相互影响。
  • 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。

缺点

  • 对高内聚的类使用该模式可能会让代码更加复杂。

与其他模式的关系

  • 桥接模式通常会于开发前期进行设计, 使能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。
  • 桥接、 状态模式和策略模式(在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。
  • 可以结合使用生成器模式和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。

组合模式

组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子。当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员, 并调用执行。其中,使用了递归调用的机制对整个结构进行处理。适用于把部分和整体的关系能够用树形结构表示的情况,从而使客户端可以使用统一的方式处理部分对象和整体对象。

核心

  • 抽象构件(Component)角色: 定义了叶子和容器构件的共同点
  • 叶子(Leaf)构件角色:无子节点
  • 容器(Composite)构件角色: 有容器特征,可以包含子节点

实现

模拟病毒文件和文件夹查杀:

/**
 * 抽象构建
 */
public interface AbstractFile {
    void killVirus();
}

/**
 * 相当于叶子构建角色
 */
public class ImageFile implements AbstractFile {

    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

    @Override
    public void killVirus() {
        System.out.println("图像文件 "+name+":开始查杀!");
    }
}
public class TextFile implements AbstractFile {

    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    @Override
    public void killVirus() {
        System.out.println("文本文件 "+name+":开始查杀!");
    }
}

/**
 * 相当于容器构建角色
 */
public class FolderFile implements AbstractFile {

    private String name;
    List<AbstractFile> fileList = new ArrayList<>();//文件夹下的文件

    public FolderFile(String name) {
        this.name = name;
    }

    public void add(AbstractFile file){
        fileList.add(file);
    }

    public void remove(AbstractFile file){
        fileList.remove(file);
    }

    public AbstractFile getFile(int index){
        AbstractFile file = fileList.get(index);
        return file;
    }

    @Override
    public void killVirus() {
        System.out.println("文件夹 "+name+":开始查杀!");
        for (AbstractFile file : fileList) {
            file.killVirus();//天然递归
        }
    }
}

//客户端调用
public class Client {
    public static void main(String[] args) {
        AbstractFile f1,f2;
        f1 = new ImageFile("照片.jpg");
        f2 = new TextFile("文本.txt");
        FolderFile f3 = new FolderFile("文件夹");
        FolderFile f4 = new FolderFile("文件夹2");

        f3.add(f1);
        f3.add(f2);

        f4.add(f1);
        f4.add(f2);
        f4.add(f3);

        f1.killVirus();
        f4.killVirus();
    }
}

Go版本实现

// Package composite 组合模式
// 模拟文件和文件夹的搜索
package composite

import "fmt"

// Component 定义抽象构建
type Component interface {
    search(string)
}

// File 文件
type File struct {
    name string
}

func (f *File) search(keyword string) {
    fmt.Printf("Searching for keyword %s in file %s\n", keyword, f.name)
}

func (f *File) getName() string {
    return f.name
}

// Folder 文件夹
type Folder struct {
    components []Component
    name       string
}

func (f *Folder) search(keyword string) {
    fmt.Printf("Searching recursively for keyword %s in folder %s\n", keyword, f.name)
    for _, composite := range f.components {
        composite.search(keyword)
    }
}

func (f *Folder) add(c Component) {
    f.components = append(f.components, c)
}

func main() {
    file1 := &File{name: "File1"}
    file2 := &File{name: "File2"}
    file3 := &File{name: "File3"}

    folder1 := &Folder{
        name: "Folder1",
    }

    folder1.add(file1)

    folder2 := &Folder{
        name: "Folder2",
    }
    folder2.add(file2)
    folder2.add(file3)
    folder2.add(folder1)

    folder2.search("rose")
}

应用场景

  1. 如果需要实现树状对象结构, 可以使用组合模式。

    组合模式提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得可以构建树状嵌套递归对象结构。

  2. 如果希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。

    组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。

优缺点

优点

  • 可以利用多态和递归机制更方便地使用复杂树结构。

  • 开闭原则。 无需更改现有代码, 就可以在应用中添加新元素, 使其成为对象树的一部分。

缺点

  • 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 需要过度一般化组件接口, 使其变得令人难以理解。

与其他模式的关系

  • 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。

  • 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 可以使用迭代器模式来遍历组合树。

  • 可以使用访问者模式对整个组合树执行操作。

  • 可以使用享元模式实现组合树的共享叶节点以节省内存。

  • 组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 可以通过该模式来复制复杂结构, 而非从零开始重新构造。

装饰器模式

动态的为一个对象增加新的功能。装饰模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类。

实现细节

  • Component 抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。
  • ConcreteComponent具体构件角色(真实对象)
  • Decorator装饰角色:持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象 。这样,就能在真实对象调用前后增加新的功能。
  • ConcreteDecorator具体装饰角色:负责给构件对象增加新的责任。

实现

/**
 * 抽象构建
 */
public interface ICar {
    void move();
}

/**
 * 被装饰对象,具体构建角色,真实对象
 */
public class Car implements ICar {
    @Override
    public void move() {
        System.out.println("能够在陆地上跑。");
    }
}

/**
 * Decorator装饰角色,具体装饰者的基类
 */
public class SuperCar implements ICar {

    protected ICar car;

    public SuperCar(ICar car) {
        this.car = car;
    }

    @Override
    public void move() {
        car.move();
    }
}

/**
 * ConcreteDecorator具体装饰角色,需要继承装饰角色
 */
public class FlyCar extends SuperCar {

    public FlyCar(ICar car) {
        super(car);
    }

    //新功能
    public void fly(){
        System.out.println("能够在天上飞");
    }

    @Override
    public void move() {
        super.move();
        fly();
    }
}
public class WaterCar extends SuperCar {

    public WaterCar(ICar car) {
        super(car);
    }

    public void swim(){
        System.out.println("能够在水上游。");
    }

    @Override
    public void move() {
        super.move();
        swim();
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        ICar car  = new Car();
        car.move();

        ICar car1 = new FlyCar(car);
        car1.move();

        ICar car2 = new WaterCar(new FlyCar(new Car()));
        car2.move();
    }
}

Go 版本实现

// Package decorator 装饰器模式
package decorator

import "fmt"

// ICar 抽象构建-汽车
type ICar interface {
    move()
}

// Car 具体被装饰的基类
type Car struct{}

func (c *Car) move() {
    fmt.Println("car move")
}

// FlyCar 具体装饰
type FlyCar struct {
    car ICar
}

func (c *FlyCar) move() {
    c.car.move()
}

// fly 新功能
func (c *FlyCar) fly() {
    fmt.Println("car fly")
}

func main() {
    car := &Car{}
    flyCar := &FlyCar{car: car}
    flyCar.move()
    flyCar.fly()
}

应用场景

  1. 如果希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。

    装饰能将业务逻辑组织为层次结构, 可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。

  2. 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 可以使用该模式。

    许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。

优缺点

优点

  • 无需创建新子类即可扩展对象的行为,不会导致类个数急剧增加
  • 可以在运行时添加或删除对象的功能。
  • 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象。
  • 单一职责原则。 可以将实现了许多不同行为的一个大类拆分为多个较小的类。
  • 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加 新的具体构件子类和具体装饰子类。

缺点

  • 在封装器栈中删除特定封装器比较困难。
  • 实现行为不受装饰栈顺序影响的装饰比较困难。
  • 各层的初始化配置代码看上去可能会很糟糕。
  • 产生很多小对象。大量小对象占据内存,一定程度上影响性能。
  • 装饰模式易于出错,调试排查比较麻烦。

与其他模式的关系

  • 适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

  • 责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

  • 组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 可以通过该模式来复制复杂结构, 而非从零开始重新构造。

  • 装饰可更改对象的外表, 策略模式则能够改变其本质。

  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

  • 装饰模式和桥接模式的区别:两个模式都是为了解决过多子类对象问题。但他们的诱因不一样。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。

外观模式

外观模式为子系统提供统一的入口(门面),封装子系统的复杂性,便于客户端调用。

实现

public interface 工商局 {
    void checkName();
}
public class 揭阳工商局 implements 工商局 {
    @Override
    public void checkName() {
        System.out.println("在揭阳工商局检查名字是否有冲突!");
    }
}

public interface 质检局 {
    void orgCodeCertificate();
}
public class 揭阳质检局 implements 质检局 {
    @Override
    public void orgCodeCertificate() {
        System.out.println("在揭阳质检局办理组织机构代码证!");
    }
}

public interface 税务局 {
    void taxCertificate();
}
public class 揭阳税务局 implements 税务局 {
    @Override
    public void taxCertificate() {
        System.out.println("在揭阳税务局办理税务登记!");
    }
}

public interface 银行 {
    void openAccount();
}
public class 中国工商银行 implements 银行 {
    @Override
    public void openAccount() {
        System.out.println("在中国工商银行开户!");
    }
}

//门面
public class RegisterFacade {
    public void register(){
        工商局 a = new 揭阳工商局();
        质检局 b = new 揭阳质检局();
        税务局 c = new 揭阳税务局();
        银行 d = new 中国工商银行();

        a.checkName();
        b.orgCodeCertificate();
        c.taxCertificate();
        d.openAccount();
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        RegisterFacade facade = new RegisterFacade();
        facade.register();
    }
}

Go 版本实现

// Package facade 外观模式
// 支付交易中,包含了以下流程;
// 检查账户
// 检查安全码
// 借记/贷记余额
// 账簿录入
// 发送消息通知
//
// 这里隐藏内部细节,对外暴露存钱和扣钱接口
package facade

import (
    "fmt"
    "log"
)

// Account 账户校验
type Account struct {
    name string
}

func newAccount(accountName string) *Account {
    return &Account{
        name: accountName,
    }
}

func (a *Account) checkAccount(accountName string) error {
    if a.name != accountName {
        return fmt.Errorf("account name is incorrect")
    }
    fmt.Println("Account Verified")
    return nil
}

// SecurityCode 安全码校验
type SecurityCode struct {
    code int
}

func newSecurityCode(code int) *SecurityCode {
    return &SecurityCode{
        code: code,
    }
}

func (s *SecurityCode) checkCode(incomingCode int) error {
    if s.code != incomingCode {
        return fmt.Errorf("security code is incorrect")
    }
    fmt.Println("SecurityCode Verified")
    return nil
}

// Wallet 借记/贷记余额
type Wallet struct {
    balance int
}

func newWallet() *Wallet {
    return &Wallet{
        balance: 0,
    }
}

func (w *Wallet) creditBalance(amount int) {
    w.balance += amount
    fmt.Println("Wallet balance added successfully")
    return
}

func (w *Wallet) debitBalance(amount int) error {
    if w.balance < amount {
        return fmt.Errorf("Balance is not sufficient")
    }
    fmt.Println("Wallet balance is Sufficient")
    w.balance = w.balance - amount
    return nil
}

// Ledger 账簿录入
type Ledger struct {
}

func (s *Ledger) makeEntry(accountID, txnType string, amount int) {
    fmt.Printf("Make ledger entry for accountId %s with txnType %s for amount %d\n", accountID, txnType, amount)
    return
}

// Notification 发送消息通知
type Notification struct {
}

func (n *Notification) sendWalletCreditNotification() {
    fmt.Println("Sending wallet credit notification")
}

func (n *Notification) sendWalletDebitNotification() {
    fmt.Println("Sending wallet debit notification")
}

// WalletFacade 封装对外接口
type WalletFacade struct {
    account      *Account
    wallet       *Wallet
    securityCode *SecurityCode
    notification *Notification
    ledger       *Ledger
}

func newWalletFacade(accountID string, code int) *WalletFacade {
    fmt.Println("Starting create account")
    walletFacacde := &WalletFacade{
        account:      newAccount(accountID),
        securityCode: newSecurityCode(code),
        wallet:       newWallet(),
        notification: &Notification{},
        ledger:       &Ledger{},
    }
    fmt.Println("Account created")
    return walletFacacde
}

func (w *WalletFacade) addMoneyToWallet(accountID string, securityCode int, amount int) error {
    fmt.Println("Starting add money to wallet")
    err := w.account.checkAccount(accountID)
    if err != nil {
        return err
    }
    err = w.securityCode.checkCode(securityCode)
    if err != nil {
        return err
    }
    w.wallet.creditBalance(amount)
    w.notification.sendWalletCreditNotification()
    w.ledger.makeEntry(accountID, "credit", amount)
    return nil
}

func (w *WalletFacade) deductMoneyFromWallet(accountID string, securityCode int, amount int) error {
    fmt.Println("Starting debit money from wallet")
    err := w.account.checkAccount(accountID)
    if err != nil {
        return err
    }

    err = w.securityCode.checkCode(securityCode)
    if err != nil {
        return err
    }
    err = w.wallet.debitBalance(amount)
    if err != nil {
        return err
    }
    w.notification.sendWalletDebitNotification()
    w.ledger.makeEntry(accountID, "credit", amount)
    return nil
}

func main() {
    fmt.Println()
    walletFacade := newWalletFacade("abc", 1234)
    fmt.Println()

    err := walletFacade.addMoneyToWallet("abc", 1234, 10)
    if err != nil {
        log.Fatalf("Error: %s\n", err.Error())
    }

    fmt.Println()
    err = walletFacade.deductMoneyFromWallet("abc", 1234, 5)
    if err != nil {
        log.Fatalf("Error: %s\n", err.Error())
    }
}

应用场景

  1. 如果需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。

    子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

  2. 如果需要将子系统组织为多层结构, 可以使用外观。

    创建外观来定义子系统中各层次的入口。 可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。

优缺点

优点

可以让自己的代码独立于复杂子系统。

缺点

外观可能成为与程序中所有类都耦合的上帝对象。

与其他模式的关系

  • 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。
  • 当只需对客户端代码隐藏子系统创建对象的方式时, 可以使用抽象工厂模式来代替外观。
  • 享元模式展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统。
  • 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
  • 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。

享元模式

内存属于稀缺资源,不要随便浪费。如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存。
享元模式以共享的方式高效地支持大量细粒度对象的重用。享元对象能做到共享的关键是区分了内部状态外部状态

  • 内部状态:可以共享,不会随环境变化而改变
  • 外部状态:不可以共享,会随环境变化而改变

享元模式的实现

  • FlyweightFactory享元工厂类:创建并管理享元对象,享元池一般设计成键值对
  • FlyWeight抽象享元类:通常是一个接口或抽象类,声明公共方法,这些方法可以向外界提供对象的内部状态,同时可以设置外部状态
  • ConcreteFlyWeight具体享元类:为内部状态提供成员变量进行存储
  • UnsharedConcreteFlyWeight非共享享元类:不能被共享的子类可以设计为非共享享元类
/**
 * 外部状态,非共享享元类
 */
public class Coordinate {
    private int x,y;

    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

/**
 * 享元抽象类
 */
public interface ChessFlyWeight {
    //向外提供访问内部状态
    void setColor(String color);
    String getColor();

    void display(Coordinate c);//设置外部状态
}
/**
 * 具体享元类
 */
public class ChessConcreteFlyWeight implements ChessFlyWeight {

    private String color;//为内部状态提供成员变量进行存储

    public ChessConcreteFlyWeight(String color) {
        this.color = color;
    }

    @Override
    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String getColor() {
        return color;
    }

    @Override
    public void display(Coordinate c) {
        System.out.println("棋子颜色:"+ color);
        System.out.println("棋子位置:"+ c.getX() + ":" + c.getY());
    }
}

/**
 * 享元工厂
 */
public class ChessFlyWeightFactory {
    //享元池对象
    private static Map<String,ChessFlyWeight> map = new HashMap<>();

    public static ChessFlyWeight getChess(String color){
        if(map.get(color) != null){
            return  map.get(color);
        }else{
            ChessFlyWeight chess = new ChessConcreteFlyWeight(color);
            map.put(color,chess);
            return chess;
        }
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色");
        ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色");
        System.out.println(chess1 == chess2);

        System.out.println("增加外部状态的处理--------");
        chess1.display(new Coordinate(10,12));
        chess2.display(new Coordinate(12,10));
    }
}

Go 代码实现

// Package flyweight 享元模式
// 假设游戏里的玩家可以设置 2 种不同的服装,这里服装就可以提取成享元对象
package flyweight

import "fmt"

const (
    //TerroristDressType terrorist dress type
    TerroristDressType = "tDress"
    //CounterTerrroristDressType terrorist dress type
    CounterTerrroristDressType = "ctDress"
)

// Dress 服装,享元接口
type Dress interface {
    getColor() string
}

// TerroristDress 具体享元对象-服装1
type TerroristDress struct {
    color string
}

func (t *TerroristDress) getColor() string {
    return t.color
}

func newTerroristDress() *TerroristDress {
    return &TerroristDress{color: "red"}
}

// CounterTerroristDress 具体享元对象-服装2
type CounterTerroristDress struct {
    color string
}

func (c *CounterTerroristDress) getColor() string {
    return c.color
}

func newCounterTerroristDress() *CounterTerroristDress {
    return &CounterTerroristDress{color: "green"}
}

type DressFactory struct {
    dressMap map[string]Dress
}

// 享元池对象
var dressFactorySingleInstance = &DressFactory{dressMap: make(map[string]Dress)}

func (d *DressFactory) getDressByType(dressType string) (Dress, error) {
    if d.dressMap[dressType] != nil {
        return d.dressMap[dressType], nil
    }

    if dressType == TerroristDressType {
        d.dressMap[dressType] = newTerroristDress()
        return d.dressMap[dressType], nil
    }
    if dressType == CounterTerrroristDressType {
        d.dressMap[dressType] = newCounterTerroristDress()
        return d.dressMap[dressType], nil
    }

    return nil, fmt.Errorf("Wrong dress type passed")
}

func getDressFactorySingleInstance() *DressFactory {
    return dressFactorySingleInstance
}

// Player 非共享享元类-玩家
type Player struct {
    dress      Dress
    playerType string
    lat        int
    long       int
}

func newPlayer(playerType, dressType string) *Player {
    dress, _ := getDressFactorySingleInstance().getDressByType(dressType)
    return &Player{
        playerType: playerType,
        dress:      dress,
    }
}

func (p *Player) newLocation(lat, long int) {
    p.lat = lat
    p.long = long
}

// game 非共享享元类-游戏
type game struct {
    terrorists        []*Player
    counterTerrorists []*Player
}

func newGame() *game {
    return &game{
        terrorists:        make([]*Player, 1),
        counterTerrorists: make([]*Player, 1),
    }
}

func (c *game) addTerrorist(dressType string) {
    player := newPlayer("T", dressType)
    c.terrorists = append(c.terrorists, player)
    return
}

func (c *game) addCounterTerrorist(dressType string) {
    player := newPlayer("CT", dressType)
    c.counterTerrorists = append(c.counterTerrorists, player)
    return
}

func main() {
    game := newGame()

    //Add Terrorist
    game.addTerrorist(TerroristDressType)
    game.addTerrorist(TerroristDressType)
    game.addTerrorist(TerroristDressType)
    game.addTerrorist(TerroristDressType)

    //Add CounterTerrorist
    game.addCounterTerrorist(CounterTerrroristDressType)
    game.addCounterTerrorist(CounterTerrroristDressType)
    game.addCounterTerrorist(CounterTerrroristDressType)

    dressFactoryInstance := getDressFactorySingleInstance()

    for dressType, dress := range dressFactoryInstance.dressMap {
        fmt.Printf("DressColorType: %s\nDressColor: %s\n", dressType, dress.getColor())
    }
}

应用场景

  1. 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

    应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态。

优缺点

优点

  • 极大减少内存中对象的数量
  • 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能
  • 外部状态相对独立,不影响内部状态

缺点

  • 模式较复杂,使程序逻辑复杂化
  • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间

与其他模式的关系

  • 可以使用享元模式实现组合模式树的共享叶节点以节省内存。
  • 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统。
  • 如果能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。

责任链模式

将能够处理同一类请求的对象练成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果有则处理,如果不能则传递给链上的下一个对象。

由于责任链的创建完全在客户端,因此新增新的具体处理者对原有类库没有任何影响,只需添加新的类,然后在客户端调用时添加即可。 符合开闭原则。

实现

责任链的实现方法有两种:

  • 链表方式定义职责链
  • 非链表方式实现职责链:通过集合、数组生成职责链更加实用!实际上,很多项目中,每个具体的Handler并不是由开发团队定义的,而是项目上线后由外部单位追加的,所以使用链表方式定义COR链就很困难。

//要处理的请求
public class LeaveRequest {
    private String empName;
    private int leaveDays;
    private String reason;

    public LeaveRequest(String empName, int leaveDays, String reason) {
        this.empName = empName;
        this.leaveDays = leaveDays;
        this.reason = reason;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public int getLeaveDays() {
        return leaveDays;
    }

    public void setLeaveDays(int leaveDays) {
        this.leaveDays = leaveDays;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }
}

//责任链上对象的基类
public abstract class Leader {

    protected String name;
    protected Leader nextLeader;//责任链后继对象

    public Leader(String name) {
        this.name = name;
    }

    //设置责任链上的后继对象
    public void setNextLeader(Leader nextLeader) {
        this.nextLeader = nextLeader;
    }

    //处理请求的核心业务方法
    public abstract void handleRequest(LeaveRequest request);
}

public class Director extends Leader {

    public Director(String name) {
        super(name);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<3){
            System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:主任"+this.name+",审批通过!");
        }else{
            if(this.nextLeader!=null){
                this.nextLeader.handleRequest(request);
            }
        }
    }
}
public class Manager extends Leader {

    public Manager(String name) {
        super(name);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<10){
            System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:经理"+this.name+",审批通过!");
        }else{
            if(this.nextLeader!=null){
                this.nextLeader.handleRequest(request);
            }
        }
    }
}
public class GeneralManager extends Leader {

    public GeneralManager(String name) {
        super(name);
    }

    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<30){
            System.out.println("员工:"+request.getEmpName()+"请假,天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:总经理"+this.name+",审批通过!");
        }else{
            System.out.println("请假"+request.getLeaveDays()+"天,直接辞职吧!");
        }
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Leader leader1 = new Director("张三");
        Leader leader2 = new Manager("李四");
        Leader leader3 = new GeneralManager("王五");

        //组织责任链对象的关系
        leader1.setNextLeader(leader2);
        leader2.setNextLeader(leader3);

        //开启请假处理
        LeaveRequest request = new LeaveRequest("小铭",90,"想出去玩!!!");
        leader1.handleRequest(request);
    }
}

Go 版本实现

// Package chain_of_responsibility 责任链模式
// 假设病人来访医院,他们首先都会去前台,然后是看医生,取药,最后结账。
// 也就是说,病人需要通过一条部门链,每个部门都在完成其职能后将病人进一步沿着链条输送。
package chain_of_responsibility

import "fmt"

// Patient 病人
type Patient struct {
    name              string
    registrationDone  bool
    doctorCheckUpDone bool
    medicineDone      bool
    paymentDone       bool
}

// Department 处理者接口
type Department interface {
    execute(*Patient)
    setNext(Department)
}

// Reception 处理者1-前台
type Reception struct {
    next Department
}

func (r *Reception) execute(p *Patient) {
    if p.registrationDone {
        fmt.Println("Patient registration already done")
        r.next.execute(p)
        return
    }
    fmt.Println("Reception registering patient")
    p.registrationDone = true
    r.next.execute(p)
}

func (r *Reception) setNext(next Department) {
    r.next = next
}

// Doctor 具体处理者2-医生
type Doctor struct {
    next Department
}

func (d *Doctor) execute(p *Patient) {
    if p.doctorCheckUpDone {
        fmt.Println("Doctor checkup already done")
        d.next.execute(p)
        return
    }
    fmt.Println("Doctor checking patient")
    p.doctorCheckUpDone = true
    d.next.execute(p)
}

func (d *Doctor) setNext(next Department) {
    d.next = next
}

// Medical 具体处理者3-取药
type Medical struct {
    next Department
}

func (m *Medical) execute(p *Patient) {
    if p.medicineDone {
        fmt.Println("Medicine already given to patient")
        m.next.execute(p)
        return
    }
    fmt.Println("Medical giving medicine to patient")
    p.medicineDone = true
    m.next.execute(p)
}

func (m *Medical) setNext(next Department) {
    m.next = next
}

// Cashier 具体处理者4-付款
type Cashier struct {
    next Department
}

func (c *Cashier) execute(p *Patient) {
    if p.paymentDone {
        fmt.Println("Payment Done")
    }
    fmt.Println("Cashier getting money from patient patient")
}

func (c *Cashier) setNext(next Department) {
    c.next = next
}


func main() {

    cashier := &Cashier{}

    //Set next for medical department
    medical := &Medical{}
    medical.setNext(cashier)

    //Set next for doctor department
    doctor := &Doctor{}
    doctor.setNext(medical)

    //Set next for reception department
    reception := &Reception{}
    reception.setNext(doctor)

    patient := &Patient{name: "abc"}
    //Patient visiting
    reception.execute(patient)
}

适用场景

  1. 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。

    该模式能将多个处理者连接成一条链。 接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。 这样所有处理者都有机会来处理请求。

  2. 当必须按顺序执行多个处理者时, 可以使用该模式。

    无论以何种顺序将处理者连接成一条链, 所有请求都会严格按照顺序通过链上的处理者。

  3. 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。

    如果在处理者类中有对引用成员变量的设定方法, 将能动态地插入和移除处理者, 或者改变其顺序。

优缺点

优点

  • 可以控制请求处理的顺序。
  • 单一职责原则。 可对发起操作和执行操作的类进行解耦。
  • 开闭原则。 可以在不更改现有代码的情况下在程序中新增处理者。

缺点

  • 部分请求可能未被处理。

与其他模式的关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

  • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。

  • 命令在发送者和请求者之间建立单向连接。

  • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。

  • 观察者允许接收者动态地订阅或取消接收请求。

  • 责任链通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 责任链的管理者可使用命令模式实现。 在这种情况下, 可以对由请求代表的同一个上下文对象执行许多不同的操作。

    还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 可以对由一系列不同上下文连接而成的链执行相同的操作。

  • 责任链和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

迭代器模式

迭代器模式提供一种可以遍历聚合对象的方式。又称为:游标cursor模式。

迭代器模式能在不暴露复杂数据结构内部细节的情况下遍历其中所有的元素。

  • 聚合对象:存储数据
  • 迭代器:遍历数据

代码实现

/**
 * 自定义的迭代器接口
 */
public interface MyIterator {
    void first();//将游标指向第一个元素
    void next();//将游标指向下一个元素
    boolean hasNext();//判断是否存在下一个元素
    boolean isFirst();//判断是否是第一个元素
    boolean isLast();//判断是否是最后一个元素
    Object getCurrentObj();//获得当前元素
}

/**
 * 自定义聚合类
 */
public class ConcreteMyAggregate {
    private List<Object> list = new ArrayList<>();

    public void addObject(Object o){
        list.add(o);
    }

    public void removeObject(Object o){
        list.remove(o);
    }

    public List<Object> getList() {
        return list;
    }

    public void setList(List<Object> list) {
        this.list = list;
    }

    public MyIterator createIterator(){
        return new MyConcreteIterator();
    }

    /**
     * 迭代器作为内部类
     */
    private class MyConcreteIterator implements MyIterator{

        private int cursor;//游标

        @Override
        public void first() {
            cursor = 0;
        }

        @Override
        public void next() {
            if(cursor < list.size()){
                cursor++;
            }
        }

        @Override
        public boolean hasNext() {
            return cursor < list.size();
        }

        @Override
        public boolean isFirst() {
            return cursor == 0;
        }

        @Override
        public boolean isLast() {
            return cursor == list.size()-1;
        }

        @Override
        public Object getCurrentObj() {
            return list.get(cursor);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        ConcreteMyAggregate cma = new ConcreteMyAggregate();
        cma.addObject("abc");
        cma.addObject("123");
        cma.addObject(new ConcreteMyAggregate());

        MyIterator iterator = cma.createIterator();
        while (iterator.hasNext()){
            System.out.println(iterator.getCurrentObj());
            iterator.next();
        }
    }
}

Go 版本实现

// Package iterator 迭代器模式
package iterator

import "fmt"

// User 用户信息
type User struct {
    name string
    age  int
}

// Iterator 迭代器接口
type Iterator interface {
    hasNext() bool
    getNext() *User
}

// UserIterator 用户数据迭代器
type UserIterator struct {
    index int
    users []*User
}

func (u *UserIterator) hasNext() bool {
    if u.index < len(u.users) {
        return true
    }
    return false
}

func (u *UserIterator) getNext() *User {
    if u.hasNext() {
        user := u.users[u.index]
        u.index++
        return user
    }
    return nil
}

// Collection 集合
type Collection interface {
    createIterator() Iterator
}

// UserCollection 具体集合
type UserCollection struct {
    users []*User
}

func (u *UserCollection) createIterator() Iterator {
    return &UserIterator{
        users: u.users,
    }
}

func main() {
    user1 := &User{
        name: "a",
        age:  30,
    }
    user2 := &User{
        name: "b",
        age:  20,
    }

    userCollection := &UserCollection{
        users: []*User{user1, user2},
    }

    iterator := userCollection.createIterator()

    for iterator.hasNext() {
        user := iterator.getNext()
        fmt.Printf("User is %+v\n", user)
    }
}

适用场景

  1. 当集合背后为复杂的数据结构, 且希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。

    迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。

  2. 使用该模式可以减少程序中重复的遍历代码。

    重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。

  3. 如果希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

    该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。

优缺点

优点

  • 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
  • 开闭原则。 可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
  • 可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
  • 相似的, 可以暂停遍历并在需要时继续。

缺点

  • 如果程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
  • 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。

与其他模式的关系

  • 可以使用迭代器模式来遍历组合模式树。
  • 可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
  • 可以同时使用备忘录模式和迭代器来获取当前迭代器的状态, 并且在需要的时候进行回滚。
  • 可以同时使用访问者模式和迭代器来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。

中介者模式

如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多关系,将导致关系极其复杂,这些对象被称为同事对象,可以引入一个中介者对象,使各个同事只跟中介者对象打交道,将复杂的网络结构简单化。

中介者模式解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系

代码实现

/**
 * 抽象同事类
 */
public interface Department {
    void selfAction();//本部门内部事务
    void outAction();//向总经理发出申请
}

/**
 * 抽象中介者
 */
public interface Mediator {
    void register(String dname,Department d);//管理的相关部门
    void command(String dname);//向有关部门发出命令
}

//具体同事类
public class Development implements Department {

    private Mediator mediator;//持有中介者的引用

    public Development(Mediator mediator) {
        this.mediator = mediator;
        mediator.register("development",this);//使中介者拥有当前引用
    }

    @Override
    public void selfAction() {
        System.out.println("科研部内部事务");
    }

    @Override
    public void outAction() {
        System.out.println("科研部需要财务部拨钱");
        mediator.command("financial");
    }
}
public class Financial implements Department {
    private Mediator mediator;//持有中介者的引用

    public Financial(Mediator mediator) {
        this.mediator = mediator;
        mediator.register("financial",this);//使中介者拥有当前引用
    }

    @Override
    public void selfAction() {
        System.out.println("财务部内部事务");
    }

    @Override
    public void outAction() {
        System.out.println("财务部接收其他部门财务申请");
        mediator.command("development");
        mediator.command("market");
    }
}
public class Market implements Department {

    private Mediator mediator;//持有中介者的引用

    public Market(Mediator mediator) {
        this.mediator = mediator;
        mediator.register("market",this);//使中介者拥有当前引用
    }

    @Override
    public void selfAction() {
        System.out.println("市场部内部事务");
    }

    @Override
    public void outAction() {
        System.out.println("市场部需要财务部拨钱");
        mediator.command("financial");
    }
}

/**
 * 具体中介者对象(总经理)
 */
public class President implements Mediator {

    private Map<String,Department> map = new HashMap<>();

    @Override
    public void register(String dname, Department d) {
        map.put(dname, d);
    }

    @Override
    public void command(String dname) {
        map.get(dname).selfAction();
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Mediator mediator = new President();//中介者对象

        Market market = new Market(mediator);
        Development development = new Development(mediator);
        Financial financial = new Financial(mediator);

        development.outAction();
        financial.outAction();
    }
}

Go 代码实现

// Package mediator 中介者模式
// 模拟火车站交通系统,两列火车通过车站经历(中介者)来控制进出站
package mediator

import "fmt"

// Train 组件,火车接口
type Train interface {
    arrive()
    depart()
    permitArrival()
}

// Mediator 中介者接口
type Mediator interface {
    canArrive(Train) bool
    notifyAboutDeparture()
}

// StationManager 具体中介者
type StationManager struct {
    isPlatformFree bool
    trainQueue     []Train
}

func newStationManger() *StationManager {
    return &StationManager{
        isPlatformFree: true,
    }
}

func (s *StationManager) canArrive(t Train) bool {
    if s.isPlatformFree {
        s.isPlatformFree = false
        return true
    }
    s.trainQueue = append(s.trainQueue, t)
    return false
}

func (s *StationManager) notifyAboutDeparture() {
    if !s.isPlatformFree {
        s.isPlatformFree = true
    }
    if len(s.trainQueue) > 0 {
        firstTrainInQueue := s.trainQueue[0]
        s.trainQueue = s.trainQueue[1:]
        firstTrainInQueue.permitArrival()
    }
}

// PassengerTrain 具体火车1
type PassengerTrain struct {
    mediator Mediator
}

func (g *PassengerTrain) arrive() {
    if !g.mediator.canArrive(g) {
        fmt.Println("PassengerTrain: Arrival blocked, waiting")
        return
    }
    fmt.Println("PassengerTrain: Arrived")
}

func (g *PassengerTrain) depart() {
    fmt.Println("PassengerTrain: Leaving")
    g.mediator.notifyAboutDeparture()
}

func (g *PassengerTrain) permitArrival() {
    fmt.Println("PassengerTrain: Arrival permitted, arriving")
    g.arrive()
}

// FreightTrain 具体火车2
type FreightTrain struct {
    mediator Mediator
}

func (g *FreightTrain) arrive() {
    if !g.mediator.canArrive(g) {
        fmt.Println("FreightTrain: Arrival blocked, waiting")
        return
    }
    fmt.Println("FreightTrain: Arrived")
}

func (g *FreightTrain) depart() {
    fmt.Println("FreightTrain: Leaving")
    g.mediator.notifyAboutDeparture()
}

func (g *FreightTrain) permitArrival() {
    fmt.Println("FreightTrain: Arrival permitted")
    g.arrive()
}

func main() {
    stationManager := newStationManger()

    passengerTrain := &PassengerTrain{
        mediator: stationManager,
    }
    freightTrain := &FreightTrain{
        mediator: stationManager,
    }

    passengerTrain.arrive()
    freightTrain.arrive()
    passengerTrain.depart()
}

适用场景

  1. 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式。

    该模式让你将对象间的所有关系抽取成为一个单独的类, 以使对于特定组件的修改工作独立于其他组件。

  2. 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式。

    应用中介者模式后, 每个组件不再知晓其他组件的情况。尽管这些组件无法直接交流,但它们仍可通过中介者对象进行间接交流。如果希望在不同应用中复用一个组件, 则需要为其提供一个新的中介者类。

  3. 如果为了能在不同情景下复用一些基本行为,导致需要被迫创建大量组件子类时,可使用中介者模式。

    由于所有组件间关系都被包含在中介者中, 因此无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

优缺点

优点

  • 单一职责原则。 可以将多个组件间的交流抽取到同一位置, 使其更易于理解和维护。
  • 开闭原则。 无需修改实际组件就能增加新的中介者。
  • 可以减轻应用中多个组件间的耦合情况。
  • 可以更方便地复用各个组件。

缺点

  • 一段时间后, 中介者可能会演化成为上帝对象。

与其他模式的关系

  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 外观模式和中介者的职责类似:它们都尝试在大量紧密耦合的类中组织起合作。

    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 中介者和观察者之间的区别往往很难记住。在大部分情况下,可以使用其中一种模式,而有时可以同时使用。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时,它可能看上去与观察者非常相似。

    当你感到疑惑时,记住可以采用其他方式来实现中介者。 例如,可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同,但这仍是一种中介者模式。

    假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。

命令模式

将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

结构

  • Command抽象命令类
  • ConcreteCommand具体命令类
  • Invoker请求的调用者/请求者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute方法,间接调用接受者的相关操作
  • Receiver接收者:接收者执行与请求相关的操作,具体实现对请求的业务处理。(实际执行操作内容的对象)
  • Client客户类,需要创建调用者对象,具体命令类对象,在创建具体命令类对象时指定对应的接受者。发送者和接收者之间没有直接关系,都通过命令对象间接调用
/**
 * 真正的命令执行者
 */
public class Receiver {
    public void action(){
        System.out.println("真正的命令执行者!");
    }
}

/**
 * 命令抽象类
 */
public interface Command {

    /**
     * 这个方法是一个返回结果为空的方法
     * 实际项目中,可以根据需求设计多个不同的方法
     */
    void execute();
}

/**
 * 具体的命令类
 */
public class ConcreteCommand implements Command {

    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        System.out.println("命令类调用前处理");
        receiver.action();
        System.out.println("命令类调用后处理");
    }
}

/**
 * 命令发起者
 */
public class Invoker {

    private Command command;//可以通过容器List容纳多个命令对象,进行批处理

    public Invoker(Command command) {
        this.command = command;
    }

    //调用命令类方法
    public void call(){
        System.out.println("命令发起者调用前处理");
        command.execute();
        System.out.println("命令发起者调用后处理");
    }
}


public class Client {
    public static void main(String[] args) {
        Command command = new ConcreteCommand(new Receiver());
        Invoker invoker = new Invoker(command);
        invoker.call();
    }
}

Go 版本实现

// Package command 命令模式
// 以电视机为接受者,对其发送打开和关闭命令
package command

import "fmt"

// Device 命令执行者接口
type Device interface {
    on()
    off()
}

// TV 具体命令接受者
type TV struct {
    isRunning bool
}

func (t *TV) on() {
    t.isRunning = true
    fmt.Println("Turning tv on")
}

func (t *TV) off() {
    t.isRunning = false
    fmt.Println("Turning tv off")
}

// Command 命令接口
type Command interface {
    execute()
}

// OnCommand 具体命令:打开
type OnCommand struct {
    device Device
}

func (c *OnCommand) execute() {
    c.device.on()
}

// OffCommand 具体命令:关闭
type OffCommand struct {
    device Device
}

func (c *OffCommand) execute() {
    c.device.off()
}

type Button struct {
    command Command
}

func (b *Button) press() {
    b.command.execute()
}

func main() {
    tv := &TV{}

    onCommand := &OnCommand{
        device: tv,
    }

    offCommand := &OffCommand{
        device: tv,
    }

    onButton := &Button{
        command: onCommand,
    }
    onButton.press()

    offButton := &Button{
        command: offCommand,
    }
    offButton.press()
}

适用场景

  1. 如果需要通过操作来参数化对象, 可使用命令模式。

    命令模式可将特定的方法调用转化为独立对象。 这一改变也带来了许多有趣的应用: 可以将命令作为方法的参数进行传递、 将命令保存在其他对象中, 或者在运行时切换已连接的命令等。

    举个例子: 开发一个 GUI 组件 (例如上下文菜单), 希望用户能够配置菜单项, 并在点击菜单项时触发操作。

  2. 如果想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。

    同其他对象一样, 命令也可以实现序列化 (序列化的意思是转化为字符串), 从而能方便地写入文件或数据库中。 一段时间后, 该字符串可被恢复成为最初的命令对象。 因此, 可以延迟或计划命令的执行。 但其功能远不止如此! 使用同样的方式, 还可以将命令放入队列、 记录命令或者通过网络发送命令。

  3. 如果想要实现操作回滚功能, 可使用命令模式。

    尽管有很多方法可以实现撤销和恢复功能, 但命令模式可能是其中最常用的一种。

    为了能够回滚操作, 需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。

    这种方法有两个缺点。 首先, 程序状态的保存功能并不容易实现, 因为部分状态可能是私有的。 可以使用备忘录模式来在一定程度上解决这个问题。

    其次, 备份状态可能会占用大量内存。 因此, 有时需要借助另一种实现方式: 命令无需恢复原始状态, 而是执行反向操作。 反向操作也有代价: 它可能会很难甚至是无法实现。

优缺点

优点

  • 单一职责原则。 可以解耦触发和执行操作的类。
  • 开闭原则。 可以在不修改已有客户端代码的情况下在程序中创建新的命令。
  • 可以实现撤销和恢复功能。
  • 可以实现操作的延迟执行。
  • 可以将一组简单命令组合成一个复杂命令。

缺点

  • 代码可能会变得更加复杂, 因为在发送者和接收者之间增加了一个全新的层次。

与其他模式的关系

  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 责任链的管理者可使用命令模式实现。 在这种情况下, 可以对由请求代表的同一个上下文对象执行许多不同的操作。

    还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 可以对由一系列不同上下文连接而成的链执行相同的操作。

  • 可以同时使用命令和备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。

  • 命令和策略模式看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。

    • 可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。
    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 能够在同一个上下文类中切换算法。
  • 原型模式可用于保存命令的历史记录。

  • 可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。

解释器模式

是一种不常用的设计模式,用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的编译器和解释器设计。当我们需要开发一种新的语言时,可以考虑使用解释器模式。

访问者模式

对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的访问者其访问方式也有所不同。访问者模式表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作

Go 代码实现

package visitor

import "fmt"

// Visitor 定义访问者接口
type Visitor interface {
    visitForSquare(*Square)
    visitForCircle(*Circle)
    visitForrectangle(*Rectangle)
}

// AreaCalculator 访问者1
type AreaCalculator struct {
    area int
}

func (a *AreaCalculator) visitForSquare(s *Square) {
    // Calculate area for square.
    // Then assign in to the area instance variable.
    fmt.Println("Calculating area for square")
}

func (a *AreaCalculator) visitForCircle(s *Circle) {
    fmt.Println("Calculating area for circle")
}
func (a *AreaCalculator) visitForrectangle(s *Rectangle) {
    fmt.Println("Calculating area for rectangle")
}

type MiddleCoordinates struct {
    x int
    y int
}

func (a *MiddleCoordinates) visitForSquare(s *Square) {
    // Calculate middle point coordinates for square.
    // Then assign in to the x and y instance variable.
    fmt.Println("Calculating middle point coordinates for square")
}

func (a *MiddleCoordinates) visitForCircle(c *Circle) {
    fmt.Println("Calculating middle point coordinates for circle")
}
func (a *MiddleCoordinates) visitForrectangle(t *Rectangle) {
    fmt.Println("Calculating middle point coordinates for rectangle")
}

// Shape 定义形状接口
type Shape interface {
    getType() string
    accept(Visitor)
}

type Square struct {
    side int
}

func (s *Square) accept(v Visitor) {
    v.visitForSquare(s)
}

func (s *Square) getType() string {
    return "Square"
}

type Circle struct {
    radius int
}

func (c *Circle) accept(v Visitor) {
    v.visitForCircle(c)
}

func (c *Circle) getType() string {
    return "Circle"
}

type Rectangle struct {
    l int
    b int
}

func (t *Rectangle) accept(v Visitor) {
    v.visitForrectangle(t)
}

func (t *Rectangle) getType() string {
    return "rectangle"
}

func main() {
    square := &Square{side: 2}
    circle := &Circle{radius: 3}
    rectangle := &Rectangle{l: 2, b: 3}

    areaCalculator := &AreaCalculator{}

    square.accept(areaCalculator)
    circle.accept(areaCalculator)
    rectangle.accept(areaCalculator)

    fmt.Println()
    middleCoordinates := &MiddleCoordinates{}
    square.accept(middleCoordinates)
    circle.accept(middleCoordinates)
    rectangle.accept(middleCoordinates)
}

应用场景

  1. 如果需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
  2. 访问者模式通过在访问者对象中为多个目标类提供相同操作的变体, 能在属于不同类的一组对象上执行同一操作。
  3. 可以使用访问者模式来清理辅助行为的业务逻辑。
  4. 该模式会将所有非主要的行为抽取到一组访问者类中, 使得程序的主要类能更专注于主要的工作。
  5. 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。
  6. 可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。

优缺点

优点

  • 开闭原则。 可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
  • 单一职责原则。 可将同一行为的不同版本移到同一个类中。
  • 访问者对象可以在与各种对象交互时收集一些有用的信息。 当想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。

缺点

  • 每次在元素层次结构中添加或移除一个类时, 都要更新所有的访问者。
  • 在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。

与其他模式的关系

  • 可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。
  • 可以使用访问者对整个组合模式树执行操作。
  • 可以同时使用访问者和迭代器模式来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。

策略模式

策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者增加新的算法。并且由客户端决定调用哪个算法。

java 代码实现

//策略接口
public interface Strategy {
    double getPrice(double standardPrice);
}
//各种不同的策略
public class NewCustomerFewStrategy implements Strategy {
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("普通用户小批量,不打折");
        return standardPrice;
    }
}
public class NewCustomerManyStrategy implements Strategy {
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("普通用户da批量,打9折");
        return standardPrice*0.9;
    }
}
public class OldCustomerFewStrategy implements Strategy {
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("老用户小批量,打85折");
        return standardPrice*0.85;
    }
}
public class OldCustromerManyStrategy implements Strategy {
    @Override
    public double getPrice(double standardPrice) {
        System.out.println("老用户大批量,打八折");
        return standardPrice*0.8;
    }
}

/**
 * 负责和具体的策略类交互,实现具体算法与客户端调用分离
 */
public class Context {
    private Strategy strategy;//当前采用的算法

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void printPrice(double price){
        double p = strategy.getPrice(price);
        System.out.println("最终价格:"+p+"元");
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Strategy strategy = new OldCustomerFewStrategy();
        Context context = new Context(strategy);
        context.printPrice(2113211);
    }
}

Go 代码实现

package strategy

import "fmt"

// EvictionAlgo 策略接口
type EvictionAlgo interface {
    evict(c *Cache)
}

// Fifo 具体策略
type Fifo struct{}

func (l *Fifo) evict(c *Cache) {
    fmt.Println("Evicting by fifo strtegy")
}

type Lru struct{}

func (l *Lru) evict(c *Cache) {
    fmt.Println("Evicting by lru strtegy")
}

type Lfu struct{}

func (l *Lfu) evict(c *Cache) {
    fmt.Println("Evicting by lfu strtegy")
}

type Cache struct {
    storage      map[string]string
    evictionAlgo EvictionAlgo
    capacity     int
    maxCapacity  int
}

func initCache(e EvictionAlgo) *Cache {
    storage := make(map[string]string)
    return &Cache{
        storage:      storage,
        evictionAlgo: e,
        capacity:     0,
        maxCapacity:  2,
    }
}

func (c *Cache) setEvictionAlgo(e EvictionAlgo) {
    c.evictionAlgo = e
}

func (c *Cache) add(key, value string) {
    if c.capacity == c.maxCapacity {
        c.evict()
    }
    c.capacity++
    c.storage[key] = value
}

func (c *Cache) get(key string) {
    delete(c.storage, key)
}

func (c *Cache) evict() {
    c.evictionAlgo.evict(c)
    c.capacity--
}

func main() {
    lfu := &Lfu{}
    cache := initCache(lfu)

    cache.add("a", "1")
    cache.add("b", "2")

    cache.add("c", "3")

    lru := &Lru{}
    cache.setEvictionAlgo(lru)

    cache.add("d", "4")

    fifo := &Fifo{}
    cache.setEvictionAlgo(fifo)

    cache.add("e", "5")
}

应用场景

  1. 当想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。策略模式能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。

  2. 当有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。策略模式能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。

  3. 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。策略模式能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。

  4. 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。

优缺点

优点

  • 可以在运行时切换对象内的算法。
  • 可以将算法的实现和使用算法的代码隔离开来。
  • 可以使用组合来代替继承。
  • 开闭原则。 无需对上下文进行修改就能够引入新的策略。

缺点

  • 如果算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
  • 客户端必须知晓策略间的不同——它需要选择合适的策略。
  • 许多现代编程语言支持函数类型功能, 允许在一组匿名函数中实现不同版本的算法。 这样, 使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。

与其他模式的关系

  • 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 命令模式和策略看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。

    • 可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。

    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 能够在同一个上下文类中切换算法。

  • 装饰模式可更改对象的外表, 策略则能够改变其本质。

  • 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

  • 状态可被视为策略)的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

模版方法模式

模版方法定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
通常处理步骤在父类中定义好,具体实现延迟到子类中定义。子类不能调用父类,而通过父类调用子类,这些步骤在父类中已经写好,完全由父类控制整个过程,这个过程又叫做方法回调(钩子方法)

JAVA 代码实现

public abstract class BankTemplateMethod {

    public void takeNumber(){
        System.out.println("取号");
    }

    public abstract void transact();//办理具体的业务(钩子方法)

    public void evaluate(){
        System.out.println("评价本次服务");
    }

    /**
     * 模版方法,将基本操作组合在一起,子类一般不能重写
     */
    public final void process(){
        this.takeNumber();
        this.transact();
        this.evaluate();
    }
}

public class Client {
    public static void main(String[] args) {
        //可以采用匿名内部类的方式调用
        BankTemplateMethod btm = new BankTemplateMethod() {
            @Override
            public void transact() {
                System.out.println("取款");
            }
        };
        btm.process();
    }
}

Go 代码实现

package template_method

import "fmt"

// IOtp 定义模版方法接口
type IOtp interface {
    genRandomOTP(int) string
    saveOTPCache(string)
    getMessage(string) string
    sendNotification(string) error
}

type Otp struct {
    iOtp IOtp
}

func (o *Otp) genAndSendOTP(otpLength int) error {
    otp := o.iOtp.genRandomOTP(otpLength)
    o.iOtp.saveOTPCache(otp)
    message := o.iOtp.getMessage(otp)
    if err := o.iOtp.sendNotification(message); err != nil {
        return err
    }
    return nil
}

// Sms 具体实现方1
type Sms struct {
    Otp
}

func (s *Sms) genRandomOTP(len int) string {
    randomOTP := "1234"
    fmt.Printf("SMS: generating random otp %s\n", randomOTP)
    return randomOTP
}

func (s *Sms) saveOTPCache(otp string) {
    fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}

func (s *Sms) getMessage(otp string) string {
    return "SMS OTP for login is " + otp
}

func (s *Sms) sendNotification(message string) error {
    fmt.Printf("SMS: sending sms: %s\n", message)
    return nil
}

// Email 具体实现方2
type Email struct {
    Otp
}

func (s *Email) genRandomOTP(len int) string {
    randomOTP := "1234"
    fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
    return randomOTP
}

func (s *Email) saveOTPCache(otp string) {
    fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}

func (s *Email) getMessage(otp string) string {
    return "EMAIL OTP for login is " + otp
}

func (s *Email) sendNotification(message string) error {
    fmt.Printf("EMAIL: sending email: %s\n", message)
    return nil
}

func main() {
    smsOTP := &Sms{}
    o := Otp{
        iOtp: smsOTP,
    }
    o.genAndSendOTP(4)

    fmt.Println("")
    emailOTP := &Email{}
    o = Otp{
        iOtp: emailOTP,
    }
    o.genAndSendOTP(4)
}

适用场景

  • 当只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。
  • 当多个类的算法除一些细微不同之外几乎完全一样时, 可使用该模式。 但其后果就是, 只要算法发生变化, 就可能需要修改所有的类。在将算法转换为模板方法时, 可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。

优缺点

优点

  • 可仅允许客户端重写一个大型算法中的特定部分, 使得算法其他部分修改对其所造成的影响减小。
  • 可将重复代码提取到一个超类中。

缺点

  • 部分客户端可能会受到算法框架的限制。

  • 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。

  • 模板方法中的步骤越多, 其维护工作就可能会越困难。

与其他模式的关系

  • 工厂方法模式是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。
  • 模板方法基于继承机制: 它允许通过扩展子类中的部分内容来改变部分算法。 策略模式基于组合机制: 可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

状态模式

用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题

结构

  • Context环境类:环境类中维护一个State对象,它定义了当前的状态。
  • State抽象状态类
  • ConcreteState具体状态类:每一个类封装了一个状态对应的行为
/**
 * 状态抽象类
 */
public interface State {
    void handle();
}

public class FreeState implements State {
    @Override
    public void handle() {
        System.out.println("房间空闲,可预定!");
    }
}
public class BookedState implements State {
    @Override
    public void handle() {
        System.out.println("房间已经被预定!");
    }
}
public class CheckedState implements State {
    @Override
    public void handle() {
        System.out.println("房间已入驻,请勿打扰!");
    }
}

/**
 * 环境类:代表着当前的状态以及状态切换的核心方法
 */
public class HotelContext {
    private State state;

    public void setState(State state) {
        System.out.println("修改状态!");
        this.state = state;
        state.handle();
    }
}

public class Client {
    public static void main(String[] args) {
        HotelContext context = new HotelContext();
        context.setState(new FreeState());
        context.setState(new CheckedState());
    }
}

Go 代码实现

// Package state 状态模式
// 假设自动售卖机有不同的状态:有商品 (hasItem)、无商品 (noItem)、商品已请求 (itemRequested)、收到纸币 (hasMoney)
// 请求执行时,会根据不同状态执行不同的操作
package state

import (
    "fmt"
    "log"
)

// VendingMachine 自动售卖机
type VendingMachine struct {
    hasItem       State
    itemRequested State
    hasMoney      State
    noItem        State

    currentState State

    itemCount int
    itemPrice int
}

func newVendingMachine(itemCount, itemPrice int) *VendingMachine {
    v := &VendingMachine{
        itemCount: itemCount,
        itemPrice: itemPrice,
    }
    hasItemState := &HasItemState{
        vendingMachine: v,
    }
    itemRequestedState := &ItemRequestedState{
        vendingMachine: v,
    }
    hasMoneyState := &HasMoneyState{
        vendingMachine: v,
    }
    noItemState := &NoItemState{
        vendingMachine: v,
    }

    v.setState(hasItemState)
    v.hasItem = hasItemState
    v.itemRequested = itemRequestedState
    v.hasMoney = hasMoneyState
    v.noItem = noItemState
    return v
}

func (v *VendingMachine) requestItem() error {
    return v.currentState.requestItem()
}

func (v *VendingMachine) addItem(count int) error {
    return v.currentState.addItem(count)
}

func (v *VendingMachine) insertMoney(money int) error {
    return v.currentState.insertMoney(money)
}

func (v *VendingMachine) dispenseItem() error {
    return v.currentState.dispenseItem()
}

func (v *VendingMachine) setState(s State) {
    v.currentState = s
}

func (v *VendingMachine) incrementItemCount(count int) {
    fmt.Printf("Adding %d items\n", count)
    v.itemCount = v.itemCount + count
}

// State 状态接口
type State interface {
    addItem(int) error
    requestItem() error
    insertMoney(money int) error
    dispenseItem() error
}

// NoItemState 无商品状态
type NoItemState struct {
    vendingMachine *VendingMachine
}

func (i *NoItemState) requestItem() error {
    return fmt.Errorf("Item out of stock")
}

func (i *NoItemState) addItem(count int) error {
    i.vendingMachine.incrementItemCount(count)
    i.vendingMachine.setState(i.vendingMachine.hasItem)
    return nil
}

func (i *NoItemState) insertMoney(money int) error {
    return fmt.Errorf("Item out of stock")
}
func (i *NoItemState) dispenseItem() error {
    return fmt.Errorf("Item out of stock")
}

// HasItemState 有商品状态
type HasItemState struct {
    vendingMachine *VendingMachine
}

func (i *HasItemState) requestItem() error {
    if i.vendingMachine.itemCount == 0 {
        i.vendingMachine.setState(i.vendingMachine.noItem)
        return fmt.Errorf("No item present")
    }
    fmt.Printf("Item requestd\n")
    i.vendingMachine.setState(i.vendingMachine.itemRequested)
    return nil
}

func (i *HasItemState) addItem(count int) error {
    fmt.Printf("%d items added\n", count)
    i.vendingMachine.incrementItemCount(count)
    return nil
}

func (i *HasItemState) insertMoney(money int) error {
    return fmt.Errorf("Please select item first")
}
func (i *HasItemState) dispenseItem() error {
    return fmt.Errorf("Please select item first")
}

// ItemRequestedState 商品已请求状态
type ItemRequestedState struct {
    vendingMachine *VendingMachine
}

func (i *ItemRequestedState) requestItem() error {
    return fmt.Errorf("Item already requested")
}

func (i *ItemRequestedState) addItem(count int) error {
    return fmt.Errorf("Item Dispense in progress")
}

func (i *ItemRequestedState) insertMoney(money int) error {
    if money < i.vendingMachine.itemPrice {
        return fmt.Errorf("Inserted money is less. Please insert %d", i.vendingMachine.itemPrice)
    }
    fmt.Println("Money entered is ok")
    i.vendingMachine.setState(i.vendingMachine.hasMoney)
    return nil
}
func (i *ItemRequestedState) dispenseItem() error {
    return fmt.Errorf("Please insert money first")
}

// HasMoneyState 收到纸币状态
type HasMoneyState struct {
    vendingMachine *VendingMachine
}

func (i *HasMoneyState) requestItem() error {
    return fmt.Errorf("Item dispense in progress")
}

func (i *HasMoneyState) addItem(count int) error {
    return fmt.Errorf("Item dispense in progress")
}

func (i *HasMoneyState) insertMoney(money int) error {
    return fmt.Errorf("Item out of stock")
}
func (i *HasMoneyState) dispenseItem() error {
    fmt.Println("Dispensing Item")
    i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
    if i.vendingMachine.itemCount == 0 {
        i.vendingMachine.setState(i.vendingMachine.noItem)
    } else {
        i.vendingMachine.setState(i.vendingMachine.hasItem)
    }
    return nil
}

func main() {
    vendingMachine := newVendingMachine(1, 10)

    err := vendingMachine.requestItem()
    if err != nil {
        log.Fatalf(err.Error())
    }

    err = vendingMachine.insertMoney(10)
    if err != nil {
        log.Fatalf(err.Error())
    }

    err = vendingMachine.dispenseItem()
    if err != nil {
        log.Fatalf(err.Error())
    }

    fmt.Println()

    err = vendingMachine.addItem(2)
    if err != nil {
        log.Fatalf(err.Error())
    }

    fmt.Println()

    err = vendingMachine.requestItem()
    if err != nil {
        log.Fatalf(err.Error())
    }

    err = vendingMachine.insertMoney(10)
    if err != nil {
        log.Fatalf(err.Error())
    }

    err = vendingMachine.dispenseItem()
    if err != nil {
        log.Fatalf(err.Error())
    }
}

应用场景

  1. 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。

  2. 模式建议将所有特定于状态的代码抽取到一组独立的类中。 这样一来, 可以在独立于其他状态的情况下添加新状态或修改已有状态, 从而减少维护成本。

  3. 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。

  4. 状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 同时, 还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。

  5. 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。

  6. 状态模式能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。

优缺点

优点

  • 单一职责原则。 将与特定状态相关的代码放在单独的类中。
  • 开闭原则。 无需修改已有状态类和上下文就能引入新状态。
  • 通过消除臃肿的状态机条件语句简化上下文代码。

缺点

如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。

与其他模式的关系

  • 桥接模式、状态模式和策略模式(在某种程度上包括适配器模式)模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 还可以使用它们来和其他开发者讨论模式所解决的问题。
  • 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

观察者模式

我们可以把多个订阅者、客户称之为观察者; 需要同步给多个订阅者的数据封装到对象中,称之为目标。
观察者模式主要用于1:N的通知。当一个对象(目标对象SubjectObjservable的状态变化时,它需要及时告知一系列对象(观察者对象Observer),令它们作出响应
通知观察者的方式有:

  • 推:每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接收
  • 拉:观察者只要知道有情况即可,至于什么时候获取内容,获取什么内容,都可以自主决定
/**
 * 目标类对象
 */
public class Subject {
    //存储所有观察者的集合
    protected List<Observer> list = new ArrayList<>();

    public void register(Observer observer){
        list.add(observer);
    }

    public void remove(Observer observer){
        list.remove(observer);
    }

    //通知所有的观察者更新状态
    public void notifyAllObservers(){
        for (Observer observer : list) {
            observer.update(this);
        }
    }
}

/**
 * 观察者
 */
public interface Observer {
    void update(Subject subject);
}

/**
 * 具体的目标类对象
 */
public class ConcreteSubject extends Subject {
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        //目标对象值发生变化,通知所有观察者
        this.notifyAllObservers();
    }
}

/**
 * 具体的观察者对象
 */
public class ObserverA implements Observer {

    private int myState;//myState需要跟目标对象的state保持一致

    public int getMyState() {
        return myState;
    }

    public void setMyState(int myState) {
        this.myState = myState;
    }

    @Override
    public void update(Subject subject) {
        myState = ((ConcreteSubject) subject).getState();
    }
}

public class Client {
    public static void main(String[] args) {
        //目标对象
        ConcreteSubject subject = new ConcreteSubject();

        //创建多个观察者
        ObserverA observer1 = new ObserverA();
        ObserverA observer2 = new ObserverA();
        ObserverA observer3 = new ObserverA();

        //将三个观察者绑定到目标对象
        subject.register(observer1);
        subject.register(observer2);
        subject.register(observer3);

        //改变subject的状态
        subject.setState(10);

        System.out.println(observer1.getMyState());
        System.out.println(observer2.getMyState());
        System.out.println(observer3.getMyState());

        subject.setState(100);
        System.out.println(observer1.getMyState());
        System.out.println(observer2.getMyState());
        System.out.println(observer3.getMyState());
    }
}

Go 代码实现

package observer

import "fmt"

// Observer 观察者
type Observer interface {
    update(string)
    getID() string
}

// Subject 被观察主题
type Subject interface {
    register(observer Observer)
    deregister(observer Observer)
    notifyAll()
}

// Item 具体主体实现
type Item struct {
    observerList []Observer
    name         string
    inStock      bool
}

func newItem(name string) *Item {
    return &Item{
        name: name,
    }
}
func (i *Item) updateAvailability() {
    fmt.Printf("Item %s is now in stock\n", i.name)
    i.inStock = true
    i.notifyAll()
}
func (i *Item) register(o Observer) {
    i.observerList = append(i.observerList, o)
}

func (i *Item) deregister(o Observer) {
    i.observerList = removeFromslice(i.observerList, o)
}

func (i *Item) notifyAll() {
    for _, observer := range i.observerList {
        observer.update(i.name)
    }
}

func removeFromslice(observerList []Observer, observerToRemove Observer) []Observer {
    observerListLength := len(observerList)
    for i, observer := range observerList {
        if observerToRemove.getID() == observer.getID() {
            observerList[observerListLength-1], observerList[i] = observerList[i], observerList[observerListLength-1]
            return observerList[:observerListLength-1]
        }
    }
    return observerList
}

// Customer 观察者
type Customer struct {
    id string
}

func (c *Customer) update(itemName string) {
    fmt.Printf("Sending email to customer %s for item %s\n", c.id, itemName)
}

func (c *Customer) getID() string {
    return c.id
}

func main() {
    shirtItem := newItem("Nike Shirt")

    observerFirst := &Customer{id: "abc@gmail.com"}
    observerSecond := &Customer{id: "xyz@gmail.com"}

    shirtItem.register(observerFirst)
    shirtItem.register(observerSecond)

    shirtItem.updateAvailability()
}

应用场景

  1. 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。

  2. 当使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。

  3. 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。

  4. 订阅列表是动态的, 因此订阅者可随时加入或离开该列表。

优缺点

优点

  • 开闭原则。 无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
  • 可以在运行时建立对象之间的联系。

缺点

  • 订阅者的通知顺序是随机的。

与其他模式的关系

  • 责任链模式、 命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 中介者和观察者之间的区别往往很难记住。在大部分情况下, 可以使用其中一种模式, 而有时可以同时使用。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。

    记住可以采用其他方式来实现中介者。 例如, 可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

备忘录模式

用于保存某个对象内部状态的拷贝,这样以后就可以将该对象恢复到原先的状态。

结构

  • Originator源发器类:要做备份的内容,其中有方法负责创建一个备忘录,用以记录当前时刻它的内部状态,并可以使用备忘录恢复到内部状态
  • Memonto备忘录类,负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问
  • CareTaker负责人类:负责保存好备忘录Memento
//源发器类
public class Emp {
    private String name;
    private int age;
    private double salary;

    public Emp() {
    }

    public Emp(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    //进行备忘录操作
    public EmpMemento memento(){
        return new EmpMemento(this);
    }

    //进行恢复操作
    public void recovery(EmpMemento memento){
        name = memento.getName();
        age = memento.getAge();
        salary = memento.getSalary();
    }

    @Override
    public String toString() {
        return "Emp{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

//备忘录类
public class EmpMemento {
    private String name;
    private int age;
    private double salary;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public EmpMemento(Emp emp) {
        name = emp.getName();
        age = emp.getAge();
        salary = emp.getSalary();
    }
}


/**
 * 负责人类
 */
public class CareTaker {
    private EmpMemento memento;

    //可以设置容器存储多个备份点
//    private List<EmpMemento> mementoList = new ArrayList<>();
      //还可以使用栈,同时还可以设置序列化和持久化

    public EmpMemento getMemento() {
        return memento;
    }

    public void setMemento(EmpMemento memento) {
        this.memento = memento;
    }
}

public class Client {
    public static void main(String[] args) {
        CareTaker careTaker = new CareTaker();
        Emp emp = new Emp("多多",2,1000.12);
        EmpMemento memento = emp.memento();//创建一个备忘录
        careTaker.setMemento(memento);//存储备忘录

        System.out.println("第一次打印:"+emp);

        emp.setAge(1);
        emp.setSalary(2000);
        System.out.println("第二次打印:"+emp);

        emp.recovery(careTaker.getMemento());
        System.out.println("第三次打印:"+emp);
    }
}

Go 版本实现

package memento

import "fmt"

// Memento 备忘录
type Memento struct {
    state string
}

func (m *Memento) getSavedState() string {
    return m.state
}

// Originator 当前原始数据
type Originator struct {
    state string
}

func (e *Originator) createMemento() *Memento {
    return &Memento{state: e.state}
}

func (e *Originator) restoreMemento(m *Memento) {
    e.state = m.getSavedState()
}

func (e *Originator) setState(state string) {
    e.state = state
}

func (e *Originator) getState() string {
    return e.state
}

// Caretaker 备忘录创建者
type Caretaker struct {
    mementoArray []*Memento
}

func (c *Caretaker) addMemento(m *Memento) {
    c.mementoArray = append(c.mementoArray, m)
}

func (c *Caretaker) getMemento(index int) *Memento {
    return c.mementoArray[index]
}

func main() {
    caretaker := &Caretaker{
        mementoArray: make([]*Memento, 0),
    }

    originator := &Originator{
        state: "A",
    }

    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())

    originator.setState("B")
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())

    originator.setState("C")
    fmt.Printf("Originator Current State: %s\n", originator.getState())
    caretaker.addMemento(originator.createMemento())

    originator.restoreMemento(caretaker.getMemento(1))
    fmt.Printf("Restored to State: %s\n", originator.getState())

    originator.restoreMemento(caretaker.getMemento(0))
    fmt.Printf("Restored to State: %s\n", originator.getState())
}

应用场景

  1. 当需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。

  2. 备忘录模式允许复制对象中的全部状态 (包括私有成员变量), 并将其独立于对象进行保存。 尽管大部分人因为 “撤销” 这个用例才记得该模式, 但其实它在处理事务 (比如需要在出现错误时回滚一个操作) 的过程中也必不可少。

  3. 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时,可以使用该模式。

  4. 备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。

优缺点

优点

  • 可以在不破坏对象封装情况的前提下创建对象状态快照。

  • 可以通过让负责人维护原发器状态历史记录来简化原发器代码。

  • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。

  • 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。

  • 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。

缺点

  • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
  • 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。
  • 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。

与其他模式的关系

  • 可以同时使用命令模式和备忘录模式来实现 “撤销”。 在这种情况下, 命令用于对目标对象执行各种不同的操作, 备忘录用来保存一条命令执行前该对象的状态。
  • 可以同时使用备忘录和迭代器模式来获取当前迭代器的状态, 并且在需要的时候进行回滚。
  • 有时候原型模式可以作为备忘录的一个简化版本, 其条件是需要在历史记录中存储的对象的状态比较简单,不需要链接其他外部资源,或者链接可以方便地重建。