设计模式学习整理

发布于:2025-05-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

UML类图

设计模式六大原则

1.单一职责原则

2.里氏替换原则

3.依赖倒置原则

4.接口隔离原则

5.迪米特法则(最少知道原则)

6.开(放封)闭原则

设计模式分类

1.创建型模式

2.结构型模式

4.行为型模式

一、工厂模式(factory——简单工厂模式和抽象工厂模式)

1.1、简单工厂模式(simple factory)

主要解决的问题

何时使用

如何解决

应用实例

优点

缺点

结构

示例

未使用设计模式的代码

使用设计模式的代码

 1.2、抽象工厂模式(abstract factory)

主要解决

适用场景

解决方案

关键代码

实例

优点

缺点

使用场景

注意事项

结构

实现

二、策略模式(strategy factory)

主要解决的问题

使用场景

应用实例

优点

缺点

使用建议

注意事项

示例

未使用设计模式的代码

使用设计模式的代码

三、装饰器模式(decorator factory)

主要解决的问题

使用场景

应用实例

优点

缺点

使用建议

注意事项

示例

未使用设计模式代码

使用设计模式代码

四、代理模式(proxy factory)

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

示例

未使用设计模式的代码

使用设计模式代码

五、工厂方法模式(factory function)

六、模板模式(template)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

实现

七、外观模式(decorator)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

八、建造者模式(builder)

概要

意图

主要解决

何时使用

如何解决

关键代码

应用实例

优点

缺点

使用场景

注意事项

对比

实现

九、观察者模式(observer)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十、命令模式(command)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

十一、原型模式(prototype)

概要

原型模式

何时使用

如何解决

关键代码

应用实例

优点

缺点

使用场景

注意事项

结构

实现实例

十二、状态模式(state)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十三、适配器模式(adapter)

概述

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

结构

实现

十四、备忘录模式(memento)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十五、组合模式(component)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十六、迭代器模式(Iterator )

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十七、单例模式(Singleton )

概要

单例模式(Singleton Pattern)

意图

主要解决

何时使用

如何解决

关键代码

应用实例

优点

缺点

使用场景

注意事项

结构

实现

单例模式的几种实现方式

1、懒汉式,线程不安全

2、懒汉式,线程安全

2、饿汉式

4、双检锁/双重校验锁(DCL,即 double-checked locking)

十八、桥接模式(bridge)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

十九、责任链模式(Chain of Responsibility)

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

二十、中介者模式

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

二十一、享元模式

概要

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

二十二、解释器模式

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

结构

实现

二十三、访问者模式

介绍

意图

主要解决的问题

使用场景

实现方式

关键代码

应用实例

优点

缺点

使用建议

注意事项

包含的几个主要角色

实现

参考文献


UML类图

虚线+箭头:依赖关系,比如动物依赖氧气和水。 实线+箭头:关联关系,比如企鹅关联到环境气候。 实线+三角空心箭头:继承关系,比如大雁、企鹅都继承鸟。 虚线+三角空心箭头:接口实现关系,比如大雁实现了飞翔接口 实心棱形+箭头:组合关系(也叫合成关系),每只鸟都有两只翅膀,鸟与翅膀谁也离不开谁,鸟与翅膀的关系就叫做组合 。 空心棱形+箭头:聚合关系,每一只大雁都有自己的雁群,每个雁群都有好多大雁。

设计模式六大原则

1.单一职责原则

原则思想:一个方法只负责一件事情。

描述:单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。

优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。

2.里氏替换原则

原则思想:使用的基类可以在任何地方使用继承的子类,完美的替换基类。

描述:子类可以扩展父类的功能,但不能改变父类原有的功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。 优点:增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。

3.依赖倒置原则

原则思想:高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

优点:可以减少需求变化带来的工作量,做并行开发更加友好。

4.接口隔离原则

原则思想:类和类之间应该建立在最小接口的上。

描述:类A通过接口依赖B,类C通过接口依赖D,如果接口类A和类B不是最小的接口,则依赖的类B和类D必须要实现他们不需要的方法。

优点:提高程序的灵活度,提高内聚,减少对外交互,使得最小的接口做最多的事情。

5.迪米特法则(最少知道原则)

原则思想:一个对象应当对其他对象有尽可能少地了解,简称类间解耦。

描述:一个类尽量减少自己对其他对象的依赖,原则是低耦合,高内聚,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。

优点:低耦合,高内聚。

6.开(放封)闭原则

原则思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化。

描述:一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。

优点:单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。

设计模式分类

1.创建型模式

单例模式,工厂模式,抽象工厂模式或简单工厂模式,建造者模式,原型模式。

2.结构型模式

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

4.行为型模式

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

一、工厂模式(factory——简单工厂模式和抽象工厂模式)

1.1、简单工厂模式(simple factory)

工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。

通过使用简单工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。

主要解决的问题

接口选择的问题。

何时使用

当我们需要在不同条件下创建不同实例时。

如何解决

通过让子类实现工厂接口,返回一个抽象的产品。

应用实例

  1. 汽车制造:你需要一辆汽车,只需从工厂提货,而不需要关心汽车的制造过程及其内部实现。

  2. Hibernate:更换数据库时,只需更改方言(Dialect)和数据库驱动(Driver),即可实现对不同数据库的切换。

优点

  1. 调用者只需要知道对象的名称即可创建对象。

  2. 扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。

  3. 屏蔽了产品的具体实现,调用者只关心产品的接口。

缺点

每次增加一个产品时,都需要增加一个具体类和修改对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖。

结构

工厂模式包含以下几个主要角色:

  • 抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。

  • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。

  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。

示例

实现一个加减乘除的计算器

未使用设计模式的代码

public class Operation {
​
    public static double GetResult(double numberA, double numberB, String operate){
        switch(operate){
            case "+":
                return numberA + numberB;
            case "-":
                return numberA - numberB;
            case "*":
                return numberA * numberB;
            case "/":
                return numberA / numberB;
            default:
                throw new IllegalStateException("未识别的符号");
        }
    }
}
public class Invoker {
    public static void main(String[] args) {
        double a = 2;
        double b = 3;
        System.out.println("加:" + Operation.GetResult(a, b, "+"));
        System.out.println("减:" + Operation.GetResult(a, b, "-"));
        System.out.println("乘:" + Operation.GetResult(a, b, "*"));
        System.out.println("除:" + Operation.GetResult(a, b, "/"));
    }
}
//输出结果:
加:5.0
减:-1.0
乘:6.0
除:0.6666666666666666
​

使用设计模式的代码

定义基础类(将要操作的两个数抽出来)和基础操作方法

import lombok.Data;
​
@Data
public abstract class Operation {
​
     double a;
​
     double b;
​
    public abstract double getResult();
}

定义

public class OperationAdd extends Operation{
​
    @Override
    public double getResult() {
        return getA() + getB();
    }
​
}
public class OperationSubtract extends Operation{
​
    @Override
    public double getResult() {
        return getA() - getB();
    }
​
}
public class OperationMulti extends Operation{
​
    @Override
    public double getResult() {
        return getA() * getB();
    }
​
}
public class OperationDiv extends Operation{
​
    @Override
    public double getResult() {
        if(getNumberB() == 0){
            throw new IllegalStateException("除数不能为0");
        }
        return getA() / getB();
    }
​
}
public class OperationFactory {
    public static Operation createOperation(String operate){
        switch (operate){
            case "+":
                return new OperationAdd();
            case "-":
                return new OperationSubtract();
            case "*":
                return new OperationMulti();
            case "/":
                return new OperationDiv();
            default:
                throw new IllegalStateException("未识别的符号");
        }
    }
}

调用者客户端:

public class Invoker {
    public static void main(String[] args) {
        double a = 2;
        double b = 3;
​
        Operation operationAdd = OperationFactory.createOperation("+");
        operationAdd.setA(a);
        operationAdd.setB(b);
        System.out.println("加:" + operationAdd.getResult());
​
        Operation operationSubtract = OperationFactory.createOperation("-");
        operationSubtract.setA(a);
        operationSubtract.setB(b);
        System.out.println("减:" + operationSubtract.getResult());
​
        Operation operationMulti = OperationFactory.createOperation("*");
        operationMulti.setA(a);
        operationMulti.setB(b);
        System.out.println("乘:" + operationMulti.getResult());
​
        Operation operationDiv = OperationFactory.createOperation("/");
        operationDiv.setA(a);
        operationDiv.setB(b);
        System.out.println("除:" + operationDiv.getResult());
    }
}
//输出结果
加:5.0
减:-1.0
乘:6.0
除:0.6666666666666666

 1.2、抽象工厂模式(abstract factory)

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。通过使用抽象工厂模式,可以将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。

主要解决

接口选择的问题。

适用场景

当系统需要创建多个相关或依赖的对象,而不需要指定具体类时。

解决方案

在一个产品族中定义多个产品,由具体工厂实现创建这些产品的方法。

关键代码

在一个工厂中聚合多个同类产品的创建方法。

实例

假设有不同类型的衣柜,每个衣柜(具体工厂)只能存放一类衣服(成套的具体产品),如商务装、时尚装等。每套衣服包括具体的上衣和裤子(具体产品)。所有衣柜都是衣柜类(抽象工厂)的具体实现,所有上衣和裤子分别实现上衣接口和裤子接口(抽象产品)。

优点

  1. 确保同一产品族的对象一起工作。

  2. 客户端不需要知道每个对象的具体类,简化了代码。

缺点

扩展产品族非常困难。增加一个新的产品族需要修改抽象工厂和所有具体工厂的代码。

使用场景

  1. QQ 换皮肤时,整套皮肤一起更换。

  2. 创建跨平台应用时,生成不同操作系统的程序。

注意事项

增加新的产品族相对容易,而增加新的产品等级结构比较困难。

结构

抽象工厂模式包含以下几个主要角色:

  • 抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。

  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。

  • 抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。

  • 具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。

抽象工厂模式通常涉及一族相关的产品,每个具体工厂类负责创建该族中的具体产品。客户端通过使用抽象工厂接口来创建产品对象,而不需要直接使用具体产品的实现类。

实现

package com.example.designmodestudy.abstractfunction.mode;
​
public interface Color {
    void fill();
}
​
package com.example.designmodestudy.abstractfunction.mode;
​
public class Blue implements Color {
​
    @Override
    public void fill() {
        System.out.println("蓝色");
    }
}
​
package com.example.designmodestudy.abstractfunction.mode;
public class Green implements Color {
​
    @Override
    public void fill() {
        System.out.println("绿色");
    }
}
​
​
package com.example.designmodestudy.abstractfunction.mode;
​
public class Red implements Color {
​
    @Override
    public void fill() {
        System.out.println("红色");
    }
}
​
package com.example.designmodestudy.abstractfunction.mode;
​
public interface Shape {
    void draw();
}
​
​
package com.example.designmodestudy.abstractfunction.mode;
​
public class Circle implements Shape {
​
    @Override
    public void draw() {
        System.out.println("圆形");
    }
}
​
​
package com.example.designmodestudy.abstractfunction.mode;
​
public class Rectangle implements Shape {
​
    @Override
    public void draw() {
        System.out.println("长方形.");
    }
}
​
​
​
package com.example.designmodestudy.abstractfunction.mode;
​
public class Square implements Shape {
​
    @Override
    public void draw() {
        System.out.println("正方形");
    }
}
​
​
package com.example.designmodestudy.abstractfunction.factory;
​
import com.example.designmodestudy.abstractfunction.mode.Color;
import com.example.designmodestudy.abstractfunction.mode.Shape;
​
public abstract class AbstractFactory {
    public abstract Color getColor(String color);
    public abstract Shape getShape(String shape);
}
​
​
package com.example.designmodestudy.abstractfunction.factory;
​
import com.example.designmodestudy.abstractfunction.mode.*;
​
public class ColorFactory extends AbstractFactory {
​
    @Override
    public Shape getShape(String shapeType){
        return null;
    }
​
    @Override
    public Color getColor(String color) {
        if(color == null){
            return null;
        }
        if(color.equalsIgnoreCase("RED")){
            return new Red();
        } else if(color.equalsIgnoreCase("GREEN")){
            return new Green();
        } else if(color.equalsIgnoreCase("BLUE")){
            return new Blue();
        }
        return null;
    }
}
​
​
package com.example.designmodestudy.abstractfunction.factory;
​
import com.example.designmodestudy.abstractfunction.mode.*;
​
public class ShapeFactory extends AbstractFactory {
​
    @Override
    public Shape getShape(String shapeType){
        if(shapeType == null){
            return null;
        }
        if(shapeType.equalsIgnoreCase("CIRCLE")){
            return new Circle();
        } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
            return new Rectangle();
        } else if(shapeType.equalsIgnoreCase("SQUARE")){
            return new Square();
        }
        return null;
    }
​
    @Override
    public Color getColor(String color) {
        return null;
    }
}
​
​
package com.example.designmodestudy.abstractfunction.factory;
​
public class FactoryProducer {
    public static AbstractFactory getFactory(String choice){
        if(choice.equalsIgnoreCase("SHAPE")){
            return new ShapeFactory();
        } else if(choice.equalsIgnoreCase("COLOR")){
            return new ColorFactory();
        }
        return null;
    }
}
​
package com.example.designmodestudy.abstractfunction;
​
import com.example.designmodestudy.abstractfunction.factory.AbstractFactory;
import com.example.designmodestudy.abstractfunction.factory.FactoryProducer;
import com.example.designmodestudy.abstractfunction.mode.Color;
import com.example.designmodestudy.abstractfunction.mode.Shape;
​
public class FactoryPatternDemo {
    public static void main(String[] args) {
​
        //获取形状工厂
        AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
​
        //获取形状为 Circle 的对象
        Shape shape1 = shapeFactory.getShape("CIRCLE");
​
        //调用 Circle 的 draw 方法
        shape1.draw();
​
        //获取形状为 Rectangle 的对象
        Shape shape2 = shapeFactory.getShape("RECTANGLE");
​
        //调用 Rectangle 的 draw 方法
        shape2.draw();
​
        //获取形状为 Square 的对象
        Shape shape3 = shapeFactory.getShape("SQUARE");
​
        //调用 Square 的 draw 方法
        shape3.draw();
​
        //获取颜色工厂
        AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
​
        //获取颜色为 Red 的对象
        Color color1 = colorFactory.getColor("RED");
​
        //调用 Red 的 fill 方法
        color1.fill();
​
        //获取颜色为 Green 的对象
        Color color2 = colorFactory.getColor("GREEN");
​
        //调用 Green 的 fill 方法
        color2.fill();
​
        //获取颜色为 Blue 的对象
        Color color3 = colorFactory.getColor("BLUE");
​
        //调用 Blue 的 fill 方法
        color3.fill();
    }
}

//结果:

圆形 长方形. 正方形 红色 绿色 蓝色

二、策略模式(strategy factory)

在策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

主要解决的问题

  • 解决在多种相似算法存在时,使用条件语句(如if...else)导致的复杂性和难以维护的问题。

使用场景

  • 当一个系统中有许多类,它们之间的区别仅在于它们的行为时。

应用实例

  1. 锦囊妙计:每个锦囊代表一个策略,包含不同的计策。

  2. 旅行方式选择:骑自行车、坐汽车等,每种方式都是一个可替换的策略。

  3. Java AWT的LayoutManager:不同的布局管理器实现了相同的接口,但提供了不同的布局算法。

优点

  1. 算法切换自由:可以在运行时根据需要切换算法。

  2. 避免多重条件判断:消除了复杂的条件语句。

  3. 扩展性好:新增算法只需新增一个策略类,无需修改现有代码。

缺点

  1. 策略类数量增多:每增加一个算法,就需要增加一个策略类。

  2. 所有策略类都需要暴露:策略类需要对外公开,以便可以被选择和使用。

  3. 在使用时,需要知道有哪些策略,要使用那种策略。

使用建议

  • 当系统中有多种算法或行为,且它们之间可以相互替换时,使用策略模式。

  • 当系统需要动态选择算法时,策略模式是一个合适的选择。

注意事项

  • 如果系统中策略类数量过多,考虑使用其他模式或设计技巧来解决类膨胀问题。

示例

实现一个满足促销的收费计算功能。

未使用设计模式的代码

public class DiscountOperate {
    public static double operate(double price, int number,  String operate){
        switch (operate){
            case "正常收费":
                return price * number;
            case "打八折":
                return price * number * 0.8;
            case "打七折":
                return price * number * 0.7;
            case "打五折":
                return price * number * 0.5;
            case "返利5元":
                return price * number - 5;
            default:
                throw new IllegalStateException("未识别的折扣");
        }
    }
}
调用者客户端:
public class Invoker {
    public static void main(String[] args) {
        double price = 10.5;
        int number = 5;
​
        String normalOperate = "正常收费";
        System.out.println(normalOperate + ":" + DiscountOperate.operate(price, number, normalOperate));
        String eightOperate = "打八折";
        System.out.println(eightOperate + ":" + DiscountOperate.operate(price, number, eightOperate));
        String sevenOperate = "打七折";
        System.out.println(sevenOperate + ":" + DiscountOperate.operate(price, number, sevenOperate));
        String rebateOperate = "返利5元";
        System.out.println(rebateOperate + ":" + DiscountOperate.operate(price, number, rebateOperate));
    }
}
//结果输出:
正常收费:52.5
打八折:42.0
打七折:36.75
返利5元:47.5

使用设计模式的代码

@Data
public abstract class CollectFee {
​
    private double price;
    private int number;
​
    //算法方法
    abstract double operate();
}
public class NormalCollectFee extends CollectFee{
    @Override
    public double operate() {
        return getPrice() * getNumber();
    }
}
public class EightCollectFee extends CollectFee{
    @Override
    public double operate() {
        return getPrice() * getNumber() * 0.8;
    }
}
public class SevenCollectFee extends CollectFee{
    @Override
    public double operate() {
        return getPrice() * getNumber() * 0.5;
    }
}
public class RebateCollectFee extends CollectFee{
    @Override
    public double operate() {
        return getPrice() * getNumber() - 5;
    }
}
public class Context {
​
    private CollectFee collectFee;
​
    //初始化时赋值对应的策略对象
    public Context(CollectFee collectFee){
        this.collectFee = collectFee;
    }
​
    //上下文接口方法
    public double contextInterface(){
        return collectFee.operate();
    }
​
}

调用者客户端:

public class Invoker {
    public static void main(String[] args) {
        int number = 5;
        double price = 10.5;
        NormalCollectFee collectFee = new NormalCollectFee();
        collectFee.setPrice(price);
        collectFee.setNumber(number);

        Context normalContext = new Context(collectFee);
        System.out.println("正常收费:" + normalContext.contextInterface());
        Context eightContext = new Context(collectFee);
        System.out.println("打八折收费:" + eightContext.contextInterface());
        Context sevenContext = new Context(collectFee);
        System.out.println("打七折收费:" + sevenContext.contextInterface());
        Context rebateContext = new Context(collectFee);
        System.out.println("返利5元收费:" + rebateContext.contextInterface());
    }
}
//输出结果:
正常收费:52.5
打八折收费:42.0
打七折收费:26.25
返利5元收费:47.5

三、装饰器模式(decorator factory)

主要解决的问题

  • 避免通过继承引入静态特征,特别是在子类数量急剧膨胀的情况下。

  • 允许在运行时动态地添加或修改对象的功能。

使用场景

  • 当需要在不增加大量子类的情况下扩展类的功能。

  • 当需要动态地添加或撤销对象的功能。

应用实例

  1. 孙悟空的72变:孙悟空(ConcreteComponent)通过变化(Decorator)获得新的能力。

  2. 画框装饰画:一幅画(ConcreteComponent)可以通过添加玻璃(ConcreteDecorator)和画框(ConcreteDecorator)来增强其展示效果。

优点

  • 低耦合:装饰类和被装饰类可以独立变化,互不影响。

  • 灵活性:可以动态地添加或撤销功能。

  • 替代继承:提供了一种继承之外的扩展对象功能的方式。

缺点

  • 复杂性:多层装饰可能导致系统复杂性增加。

使用建议

  • 在需要动态扩展功能时,考虑使用装饰器模式。

  • 保持装饰者和具体组件的接口一致,以确保灵活性。

注意事项

  • 装饰器模式可以替代继承,但应谨慎使用,避免过度装饰导致系统复杂。

示例

实现一个人可以选择性的装扮的功能。

未使用设计模式代码

import lombok.AllArgsConstructor;
import lombok.Data;
​
@AllArgsConstructor
@Data
public class Person {
​
    private String name;
​
    //形象展示
    public void show(){
        System.out.println("装扮的" + name + "。");
    }
}
/**
 * 服饰类
 */
public abstract class Finery {
​
    public abstract void show();
}
public class BigTrouser extends Finery{
    @Override
    public void show() {
        System.out.println("垮裤");;
    }
}
public class LeatherShoes extends Finery{
    @Override
    public void show() {
        System.out.println("皮鞋");;
    }
}
public class Sneakers extends Finery{
    @Override
    public void show() {
        System.out.println("破球鞋");;
    }
}
public class Suit extends Finery{
    @Override
    public void show() {
        System.out.println("西装");;
    }
}
public class Tie extends Finery{
    @Override
    public void show() {
        System.out.println("领带");;
    }
}
public class TShirts extends Finery{
    @Override
    public void show() {
        System.out.println("大T恤");;
    }
}

客户端调用:

public class Invoker {
​
    public static void main(String[] args) {
        Person person = new Person("程序员");
        System.out.println("========失业的装扮=========");
        Finery dtx = new TShirts();
        Finery kk = new BigTrouser();
        Finery pqx = new Sneakers();
        dtx.show();
        kk.show();
        pqx.show();
        person.show();
​
        System.out.println("=======上班的装扮========");
        Finery sz = new Suit();
        Finery ld = new Tie();
        Finery px = new LeatherShoes();
        sz.show();
        ld.show();
        px.show();
        person.show();
    }
}
//输出结果:
========失业的装扮=========
大T恤
垮裤
破球鞋
装扮的程序员。
========上班的装扮=========
西装
领带
皮鞋
装扮的程序员。

使用设计模式代码

装饰器模式模型:

本次业务演变模型:

import lombok.Data;
​
@Data
public class Person {
​
    private String name;
​
    public Person(){
    }
    public Person(String name){
        this.name = name;
    }
​
    public void show(){
        System.out.println("装扮的" + name + "。");
    }
​
}
/**
 * 服饰类
 */
public class Finery extends Person {
​
    protected Person component;
​
    public void finery(Person component){
        this.component = component;
    }
​
    @Override
    public void show(){
        component.show();
    }
}
public class BigTrouser extends Finery{
​
    @Override
    public void show() {
        System.out.println("垮裤");
        super.show();
    }
}
public class LeatherShoes extends Finery{
​
    @Override
    public void show() {
        System.out.println("皮鞋");
        super.show();
    }
}
public class Sneakers extends Finery{
​
    @Override
    public void show() {
        System.out.println("破球鞋");
        super.show();
    }
}
public class Suit extends Finery{
​
    @Override
    public void show() {
        System.out.println("西装");
        super.show();
    }
}
public class Tie extends Finery{
​
    @Override
    public void show() {
        System.out.println("领带");
        super.show();
    }
}
public class TShirts extends Finery{
​
    @Override
    public void show() {
        System.out.println("大T恤");
        super.show();
    }
}

调用者客户端:

public class Invoker {
​
    public static void main(String[] args) {
        Person person = new Person("程序员");
        System.out.println("========失业的装扮=========");
        Sneakers pqx = new Sneakers();
        BigTrouser kk = new BigTrouser();
        TShirts dtx = new TShirts();
        pqx.finery(person);
        kk.finery(pqx);
        dtx.finery(kk);
        dtx.show();
​
        System.out.println("========上班的装扮=========");
        LeatherShoes px = new LeatherShoes();
        Tie ld = new Tie();
        Suit sz = new Suit();
        px.finery(person);
        ld.finery(px);
        sz.finery(ld);
        sz.show();
    }
}
//输出结果
========失业的装扮=========
大T恤
垮裤
破球鞋
装扮的程序员。
========上班的装扮=========
西装
领带
皮鞋
装扮的程序员。

四、代理模式(proxy factory)

主要解决的问题

  • 代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。

使用场景

  • 当需要在访问一个对象时进行一些控制或额外处理时。

实现方式

  • 增加中间层:创建一个代理类,作为真实对象的中间层。

  • 代理与真实对象组合:代理类持有真实对象的引用,并在访问时进行控制。

关键代码

  • 代理类:实现与真实对象相同的接口,并添加额外的控制逻辑。

  • 真实对象:实际执行任务的对象。

应用实例

  • 快捷方式:Windows系统中的快捷方式作为文件或程序的代理。

  • 角色扮演:孙悟空作为高翠兰的代理,猪八戒无法区分。

  • 代售点:购买火车票时,代售点作为火车站的代理。

  • 支票:作为银行账户资金的代理,控制资金的访问。

  • Spring AOP:使用代理模式来实现面向切面编程。

优点

  • 职责分离:代理模式将访问控制与业务逻辑分离。

  • 扩展性:可以灵活地添加额外的功能或控制。

  • 智能化:可以智能地处理访问请求,如延迟加载、缓存等。

缺点

  • 性能开销:增加了代理层可能会影响请求的处理速度。

  • 实现复杂性:某些类型的代理模式实现起来可能较为复杂。

示例

实现一个窈窕淑女,君子好逑的功能(男生买各种东西追求女生),男孩因为害羞不敢直接送礼物给女孩,因此找中间人向女孩送礼物。

未使用设计模式的代码

import lombok.AllArgsConstructor;
import lombok.Data;
​
@AllArgsConstructor
@Data
public class Girl {
    private String name;
​
}
public class Boy{
​
    Girl girl;
​
    public Boy(Girl girl){
        this.girl = girl;
        System.out.println("我喜欢你, " + girl.getName() + "!");
    }
​
    public void giveDolls(){
        System.out.println("送洋娃娃");
    }
​
    public void giveFlowers(){
        System.out.println("送鲜花");
    }
​
    public void giveChocolate(){
        System.out.println("送巧克力");
    }
}
public class MiddlePerson extends Boy {
    public MiddlePerson(Girl girl) {
        super(girl);
        System.out.println("(中间人代送)");
    }
}

客户端调用者:

public class Invoker {
​
    public static void main(String[] args) {
        Girl girl = new Girl("小乔");
        MiddlePerson middlePerson = new MiddlePerson(girl);
        middlePerson.giveDolls();
        middlePerson.giveFlowers();
        middlePerson.giveChocolate();
    }
}
//结果输出:
我喜欢你, 小乔!
(中间人代送)
送洋娃娃
送鲜花
送巧克力

使用设计模式代码

代理模式原始模型:

本次业务的演变模型:

import lombok.AllArgsConstructor;
import lombok.Data;
​
@AllArgsConstructor
@Data
public class Girl {
    private String name;
​
}
public interface GiveGift {
​
     void giveDolls();
​
     void giveFlowers();
​
     void giveChocolate();
}
public class Boy implements GiveGift{
​
    Girl girl;
​
    public Boy(Girl girl){
        this.girl = girl;
        System.out.println("我喜欢你, " + girl.getName() + "!");
    }
​
    @Override
    public void giveDolls(){
        System.out.println("送洋娃娃");
    }
​
    @Override
    public void giveFlowers(){
        System.out.println("送鲜花");
    }
​
    @Override
    public void giveChocolate(){
        System.out.println("送巧克力");
    }
}
public class Proxy implements GiveGift{
​
    Boy boy;
​
    public Proxy(Girl girl){
        boy = new Boy(girl);
        System.out.println("(中间人代送)");
    }
​
    //功能扩展
    public void introduction(){
        System.out.println("自我介绍:我家有50套房、10辆豪车、1个亿的存款。");
    }
​
    @Override
    public void giveDolls(){
        System.out.println("送洋娃娃");
    }
​
    @Override
    public void giveFlowers(){
        System.out.println("送鲜花");
    }
​
    @Override
    public void giveChocolate(){
        System.out.println("送巧克力");
    }
}

客户端调用者

public class Invoker {
​
    public static void main(String[] args) {
        Girl girl = new Girl("小乔");
//        Boy proxy = new Boy(girl);
        Proxy proxy = new Proxy(girl);
        proxy.giveDolls();
        proxy.giveFlowers();
        proxy.giveChocolate();
    }
}

//输出结果
我喜欢你, 小乔! (中间人代送)
自我介绍:我家有50套房、10辆豪车、1个亿的存款。
送洋娃娃
送鲜花
送巧克力

五、工厂方法模式(factory function)

  • 封装性:将对象的创建过程封装在工厂类中,客户端代码通过工厂类来创建对象,而无需知道具体的创建逻辑。
  • 解耦:实现了客户端代码与具体产品类的解耦,客户端只需关心产品的接口,而无需关心产品的具体实现。
  • 可扩展性:当需要添加新的产品时,只需扩展工厂类即可,无需修改客户端代码。

package com.example.designmodestudy.factoryfunction;
​
import lombok.Data;
​
@Data
public abstract class Operation {
​
     double a;
​
     double b;
​
    public abstract double getResult();
}
​
package com.example.designmodestudy.factoryfunction;
​
public class OperationAdd extends Operation {
​
    @Override
    public double getResult() {
        return getA() + getB();
    }
​
}
​
​
package com.example.designmodestudy.factoryfunction;
​
public class OperationDiv extends Operation {
​
    @Override
    public double getResult() {
        if(getB() == 0){
            throw new IllegalStateException("除数不能为0");
        }
        return getA() / getB();
    }
​
}
​
​
package com.example.designmodestudy.factoryfunction;
​
public class OperationMulti extends Operation {
​
    @Override
    public double getResult() {
        return getA() * getB();
    }
​
}
​
​
package com.example.designmodestudy.factoryfunction;
​
public class OperationSubtract extends Operation {
​
    @Override
    public double getResult() {
        return getA() - getB();
    }
​
}
​


package com.example.designmodestudy.factoryfunction.factory;
​
import com.example.designmodestudy.factoryfunction.Operation;
​
public interface OperationFactory {
​
    Operation createOperation();
}
​
​
package com.example.designmodestudy.factoryfunction.factory;
​
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationAdd;
​
public class AddOperationFactory implements OperationFactory {
    @Override
    public Operation createOperation() {
        return new OperationAdd();
    }
}
​
​
package com.example.designmodestudy.factoryfunction.factory;
​
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationDiv;
​
public class DivOperationFactory implements OperationFactory {
    @Override
    public Operation createOperation() {
        return new OperationDiv();
    }
}
​
​
package com.example.designmodestudy.factoryfunction.factory;
​
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationMulti;
​
public class MultiOperationFactory implements OperationFactory {
    @Override
    public Operation createOperation() {
        return new OperationMulti();
    }
}
​
​
package com.example.designmodestudy.factoryfunction.factory;
​
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationSubtract;
​
public class SubtractOperationFactory implements OperationFactory {
    @Override
    public Operation createOperation() {
        return new OperationSubtract();
    }
}
​


package com.example.designmodestudy.factoryfunction;
​
import com.example.designmodestudy.factoryfunction.factory.*;
​
public class Invoker {
    public static void main(String[] args) {
        double a = 2;
        double b = 3;
​
        OperationFactory addOperationFactory = new AddOperationFactory();
        com.example.designmodestudy.factoryfunction.Operation operationAdd = addOperationFactory.createOperation();
        operationAdd.setA(a);
        operationAdd.setB(b);
        System.out.println("加:" + operationAdd.getResult());
​
        OperationFactory subtractOperationFactory = new SubtractOperationFactory();
        com.example.designmodestudy.factoryfunction.Operation operationSubtract = subtractOperationFactory.createOperation();
        operationSubtract.setA(a);
        operationSubtract.setB(b);
        System.out.println("减:" + operationSubtract.getResult());
​
        OperationFactory multiOperationFactory = new MultiOperationFactory();
        com.example.designmodestudy.factoryfunction.Operation operationMulti = multiOperationFactory.createOperation();
        operationMulti.setA(a);
        operationMulti.setB(b);
        System.out.println("乘:" + operationMulti.getResult());
​
        OperationFactory divOperationFactory = new DivOperationFactory();
        Operation operationDiv = divOperationFactory.createOperation();
        operationDiv.setA(a);
        operationDiv.setB(b);
        System.out.println("除:" + operationDiv.getResult());
    }
}

//结果:

加:5.0 减:-1.0 乘:6.0 除:0.6666666666666666

六、模板模式(template)

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

介绍

意图

在父类中定义了算法的骨架,并允许子类在不改变算法结构的前提下重定义算法的某些特定步骤。

主要解决的问题

  • 解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复。

使用场景

  • 当存在一些通用的方法,可以在多个子类中共用时。

实现方式

  • 定义抽象父类:包含模板方法和一些抽象方法或具体方法。

  • 实现子类:继承抽象父类并实现抽象方法,不改变算法结构。

关键代码

  • 模板方法:在抽象父类中定义,调用抽象方法和具体方法。

  • 抽象方法:由子类实现,代表算法的可变部分。

  • 具体方法:在抽象父类中实现,代表算法的不变部分。

应用实例

  • 建筑流程:地基、走线、水管等步骤相同,后期建筑如加壁橱、栅栏等步骤不同。

  • 西游记的81难:菩萨定好的81难代表一个顶层逻辑骨架。

  • Spring对Hibernate的支持:封装了如开启事务、获取Session、关闭Session等通用方法。

优点

  • 封装不变部分:算法的不变部分被封装在父类中。

  • 扩展可变部分:子类可以扩展或修改算法的可变部分。

  • 提取公共代码:减少代码重复,便于维护。

缺点

  • 类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。

使用建议

  • 当有多个子类共有的方法且逻辑相同时,考虑使用模板方法模式。

  • 对于重要或复杂的方法,可以考虑作为模板方法定义在父类中。

注意事项

  • 为了防止恶意修改,模板方法通常使用final关键字修饰,避免被子类重写。

实现

我们将创建一个定义操作的 TestPaper 抽象类,其中,模板方法设置为 final,这样它就不会被重写。TestPaperATestPaperB 是扩展了 TestPaper 的实体类,它们重写了抽象类的方法。

Invoker,是我们的演示类使用 TestPaper 来演示模板模式的用法。

package designmode.template;
​
/**
 * packageName designmode.template
 *
 * @author 青藤
 * @version JDK 11
 * @className Template (此处以class为例)
 * @date 2025/4/21 16:05
 * @description TODO
 */
public abstract class TestPaper {
​
    abstract void question1();
​
    abstract void question2();
​
    abstract String answer1();
​
    abstract String answer2();
​
​
    //模板方法
    public final void test() {
        question1();
        answer1();
​
        question2();
        answer2();
    }
}
​
package designmode.template;
​
/**
 * packageName designmode.template
 *
 * @author 青藤
 * @version JDK 11
 * @className TestPaperA (此处以class为例)
 * @date 2025/4/21 16:15
 * @description TODO
 */
public class TestPaperA extends TestPaper{
    @Override
    void question1() {
        System.out.println("题1. 1+1=? A.1 B.2 C.3 D.4");
        System.out.println("答案:" + answer1());
    }
​
    @Override
    void question2() {
        System.out.println("提2. 1+2=? A.1 B.2 C.3 D.4");
        System.out.println("答案:" + answer2());
    }
​
    @Override
    String answer1() {
        return "B";
    }
​
    @Override
    String answer2() {
        return "C";
    }
}
​
package designmode.template;
​
/**
 * packageName designmode.template
 *
 * @author 青藤
 * @version JDK 11
 * @className TestPaperA (此处以class为例)
 * @date 2025/4/21 16:15
 * @description TODO
 */
public class TestPaperB extends TestPaper{
    @Override
    void question1() {
        System.out.println("题1. 2+1=? A.1 B.2 C.3 D.4");
        System.out.println("答案:" + answer1());
    }
​
    @Override
    void question2() {
        System.out.println("题2. 2+2=? A.1 B.2 C.3 D.4");
        System.out.println("答案:" + answer2());
    }
​
    @Override
    String answer1() {
        return "C";
    }
​
    @Override
    String answer2() {
        return "D";
    }
}
​
package designmode.template;

/**
 * packageName designmode.template
 *
 * @author 青藤
 * @version JDK 11
 * @className Invoker (此处以class为例)
 * @date 2025/4/21 16:18
 * @description TODO
 */
public class Invoker {

    public static void main(String[] args) {

        System.out.println("=====A卷考试======");
        TestPaper testPaperA = new TestPaperA();
        testPaperA.test();

        System.out.println("=====B卷考试======");
        TestPaper testPaperB = new TestPaperB();
        testPaperB.test();
    }
}

//结果: 题1. 1+1=? A.1 B.2 C.3 D.4 答案:B 提2. 1+2=? A.1 B.2 C.3 D.4 答案:C =====B卷考试====== 题1. 2+1=? A.1 B.2 C.3 D.4 答案:C 题2. 2+2=? A.1 B.2 C.3 D.4 答案:D

七、外观模式(decorator)

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

介绍

意图

为一个复杂的子系统提供一个一致的高层接口。这样,客户端代码就可以通过这个简化的接口与子系统交互,而不需要了解子系统内部的复杂性。

主要解决的问题

  • 降低客户端与复杂子系统之间的耦合度。

  • 简化客户端对复杂系统的操作,隐藏内部实现细节。

使用场景

  • 当客户端不需要了解系统内部的复杂逻辑和组件交互时。

  • 当需要为整个系统定义一个清晰的入口点时。

实现方式

  • 创建外观类:定义一个类(外观),作为客户端与子系统之间的中介。

  • 封装子系统操作:外观类将复杂的子系统操作封装成简单的方法。

关键代码

  • Facade类:提供高层接口,简化客户端与子系统的交互。

  • 子系统类:实现具体的业务逻辑,被Facade类调用。

应用实例

  1. 医院接待:医院的接待人员简化了挂号、门诊、划价、取药等复杂流程。

  2. Java三层架构:通过外观模式,可以简化对表示层、业务逻辑层和数据访问层的访问。

优点

  1. 减少依赖:客户端与子系统之间的依赖减少。

  2. 提高灵活性:子系统的内部变化不会影响客户端。

  3. 增强安全性:隐藏了子系统的内部实现,只暴露必要的操作。

缺点

  • 违反开闭原则:对子系统的修改可能需要对外观类进行相应的修改。

使用建议

  • 在需要简化复杂系统访问时使用外观模式。

  • 确保外观类提供的方法足够简单,以便于客户端使用。

注意事项

  • 外观模式适用于层次化结构,可以为每一层提供一个清晰的入口。

  • 避免过度使用外观模式,以免隐藏过多的细节,导致维护困难。

结构

外观模式涉及以下核心角色:

  • 外观(Facade):

    • 提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。

  • 子系统(Subsystem):

    • 由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。

  • 客户端(Client):

    • 使用外观对象来与系统交互,而不需要了解系统内部的具体实现。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeMaker

ShapeMaker 类使用实体类来代表用户对这些类的调用。FacadePatternDemo 类使用 ShapeMaker 类来显示结果。

package designmode.facade;
​
public interface Shape {
​
    void draw();
}
​
package designmode.facade;
​
public class Circle  implements Shape{
    @Override
    public void draw() {
        System.out.println("画了个圆");
    }
}
​
package designmode.facade;
​
public class Rectangle implements Shape{
    @Override
    public void draw() {
        System.out.println("画了个长方形");
    }
}
​
package designmode.facade;
​
public class Square implements Shape{
    @Override
    public void draw() {
        System.out.println("画了个正方形");
    }
}
​
package designmode.facade;
​
public class ShapeMarker {
​
    private Shape circle;
​
    private Shape rectangle;
​
    private Shape square;
​
    public ShapeMarker(){
        circle = new Circle();
        rectangle = new Rectangle();
        square = new Square();
    }
​
    public void drawCircle(){
        circle.draw();
    }
​
    public void drawRectangle(){
        rectangle.draw();
    }
​
    public void drawSquare(){
        square.draw();
    }
}
​
package designmode.facade;
​
public class Invoker {
​
    public static void main(String[] args) {
        ShapeMarker shapeMarker = new ShapeMarker();
        shapeMarker.drawCircle();
        shapeMarker.drawRectangle();
        shapeMarker.drawSquare();
    }
}

//结果:

画了个圆 画了个长方形 画了个正方形

八、建造者模式(builder)

建造者模式是一种创建型设计模式,它允许你创建复杂对象的步骤与表示方式相分离。

建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建具有不同表示形式的对象。

概要

意图

将一个复杂的构建过程与其表示相分离,使得同样的构建过程可以创建不同的表示。

主要解决

在软件系统中,一个复杂对象的创建通常由多个部分组成,这些部分的组合经常变化,但组合的算法相对稳定。

何时使用

当一些基本部件不变,而其组合经常变化时。

如何解决

将变与不变的部分分离开。

关键代码

  • 建造者:创建并提供实例。

  • 导演:管理建造出来的实例的依赖关系和控制构建过程。

应用实例

  • 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出不同的"套餐"。

  • Java 中的 StringBuilder

优点

  • 分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。

  • 可以更好地控制构建过程,隐藏具体构建细节。

  • 代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

缺点

  • 如果产品的属性较少,建造者模式可能会导致代码冗余。

  • 增加了系统的类和对象数量。

使用场景

  • 需要生成的对象具有复杂的内部结构。

  • 需要生成的对象内部属性相互依赖。

注意事项

与工厂模式的区别是:建造者模式更加关注于零件装配的顺序。

对比

1、工厂方法模式VS建造者模式 工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

如果用工厂方法建造手机的话,直接就可以得到一个完整的手机,而用建造者模式,则需要按照指定顺序组装各个零件,才能得到一部手机。

2、抽象工厂模式VS建造者模式 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

实现

以生产手机为例:

Builder(抽象建造者):定义手机需要那些配件 具体建造者:定义不同手机配件的生产标准 Director(指挥者):定义手机需要组装什么配件,按照什么顺序进行组装。

package designmode.builder;
​
public abstract class Builder {
​
    protected Phone phone = new Phone();
​
    public abstract void buildCpu();
​
    public abstract void buildMemory();
​
    public abstract void buildColor();
​
    public Phone builder() {
        return phone;
    }
}
​
package designmode.builder;
​
import lombok.Data;
​
@Data
public class Phone {
​
    private String cpu;
​
    private String memory;
​
    private String color;
}
​
package designmode.builder;
​
public class XiaoMi extends Builder{
    @Override
    public void buildCpu() {
        phone.setCpu("天机");
    }
​
    @Override
    public void buildMemory() {
        phone.setMemory("512");
    }
​
    @Override
    public void buildColor() {
        phone.setColor("白色");
    }
}
​
package designmode.builder;
​
public class HuaWei extends Builder{
    @Override
    public void buildCpu() {
        phone.setCpu("麒麟");
    }
​
    @Override
    public void buildMemory() {
        phone.setMemory("512");
    }
​
    @Override
    public void buildColor() {
        phone.setColor("黑色");
    }
}
​
package designmode.builder;

import lombok.Data;

@Data
public class Director {

    private Builder builder;

    public Phone createPhone(){
        builder.buildCpu();
        builder.buildMemory();
        builder.buildColor();

        return builder.builder();
    }

}
package designmode.builder;
​
public class Invoker {
    public static void main(String[] args) {
        Director director = new Director();
        director.setBuilder(new XiaoMi());
        Phone xiaomi = director.createPhone();
        System.out.println("小米手机:" + xiaomi);
​
        director.setBuilder(new HuaWei());
        Phone huawei = director.createPhone();
        System.out.println("华为手机:" + huawei);
    }
}

结果:

小米手机:Phone(cpu=天机, memory=512, color=白色) 华为手机:Phone(cpu=麒麟, memory=512, color=黑色)

九、观察者模式(observer)

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

介绍

意图

创建了对象间的一种一对多的依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。

主要解决的问题

  • 观察者模式解决的是一个对象状态改变时,如何自动通知其他依赖对象的问题,同时保持对象间的低耦合和高协作性。

使用场景

  • 当一个对象的状态变化需要同时更新其他对象时。

实现方式

  • 定义观察者接口:包含一个更新方法。

  • 创建具体观察者:实现观察者接口,定义接收到通知时的行为。

  • 定义主题接口:包含添加、删除和通知观察者的方法。

  • 创建具体主题:实现主题接口,管理观察者列表,并在状态改变时通知它们。

关键代码

  • 观察者列表:在主题中维护一个观察者列表。

应用实例

  • 拍卖系统:拍卖师作为主题,竞价者作为观察者,拍卖价格更新时通知所有竞价者。

  • 西游记故事:菩萨洒水作为状态改变,老乌龟作为观察者,观察到这一变化。

优点

  • 抽象耦合:观察者和主题之间是抽象耦合的。

  • 触发机制:建立了一套状态改变时的触发和通知机制。

缺点

  • 性能问题:如果观察者众多,通知过程可能耗时。

  • 循环依赖:可能导致循环调用和系统崩溃。

  • 缺乏变化详情:观察者不知道主题如何变化,只知道变化发生。

使用建议

  • 在需要降低对象间耦合度,并且对象状态变化需要触发其他对象变化时使用。

  • 考虑使用Java内置的观察者模式支持类,如java.util.Observablejava.util.Observer

注意事项

  • 避免循环引用:注意观察者和主题之间的依赖关系,避免循环引用。

  • 异步执行:考虑使用异步通知避免单点故障导致整个系统卡壳。

结构

观察者模式包含以下几个核心角色:

  • 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。

  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。

  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。

  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。

实现

观察者模式使用三个类 Subject、Observer 和 Client。Subject 对象带有绑定观察者到 Client 对象和从 Client 对象解绑观察者的方法。我们创建 Subject 类、Observer 抽象类和扩展了抽象类 Observer 的实体类。

ObserverPatternDemo,我们的演示类使用 Subject 和实体类对象来演示观察者模式。

package designmode.observer;
​
/**
 * 观察者
 */
public abstract class Observer {
​
    protected Subject subject;
​
    public abstract void update();
}
package designmode.observer;
​
/**
 * 二进制观察者
 */
public class BinaryObserver extends Observer{
​
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }
​
    @Override
    public void update() {
        System.out.println("二进制:" + Integer.toBinaryString(subject.getState()));
    }
}
​
package designmode.observer;
​
/**
 * 十进制观察者
 */
public class OctalObserver extends Observer{
​
    public OctalObserver(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }
​
    @Override
    public void update() {
        System.out.println("十进制:" + Integer.toOctalString(subject.getState()));
    }
}
​
​
package designmode.observer;
​
/**
 * 16进制观察者
 */
public class HexaObserver extends Observer{
​
    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }
​
    @Override
    public void update() {
        System.out.println("十六进制:" + Integer.toHexString(subject.getState()).toUpperCase());
    }
}
package designmode.observer;
​
import lombok.Data;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * 主题
 */
@Data
public class Subject {
​
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
​
    public void addObserver(Observer observer){
        observers.add(observer);
    }
​
    public void notifyAllObservers(){
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
package designmode.observer;
​
/**
 * 调用类
 */
public class Invoker {
    public static void main(String[] args) {
        Subject subject = new Subject();
​
        new BinaryObserver(subject);
        new OctalObserver(subject);
        new HexaObserver(subject);
​
        int state = 15;
        System.out.println("=======数字:" + state);
        subject.setState(state);
        subject.notifyAllObservers();
​
        state = 10;
        System.out.println("=======数字:" + state);
        subject.setState(state);
        subject.notifyAllObservers();
    }
}

结果:

=======数字:15 二进制:1111 十进制:17 十六进制:F =======数字:10 二进制:1010 十进制:12 十六进制:A

十、命令模式(command)

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。

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

命令模式结构示意图:

介绍

意图

将请求封装为一个对象,允许用户使用不同的请求对客户端进行参数化。

主要解决的问题

  • 解决在软件系统中请求者和执行者之间的紧耦合问题,特别是在需要对行为进行记录、撤销/重做或事务处理等场景。

使用场景

  • 当需要对行为进行记录、撤销/重做或事务处理时,使用命令模式来解耦请求者和执行者。

实现方式

  • 定义命令接口:所有命令必须实现的接口。

  • 创建具体命令:实现命令接口的具体类,包含执行请求的方法。

  • 调用者:持有命令对象并触发命令的执行。

  • 接收者:实际执行命令的对象。

关键代码

  • 接收者(Receiver):执行命令的实际对象。

  • 命令(Command):定义执行命令的接口。

  • 调用者(Invoker):使用命令对象的入口点。

应用实例

  • Struts 1:ActionServlet作为Invoker,模型层的类作为具体的Command。

优点

  1. 降低耦合度:请求者和执行者之间的耦合度降低。

  2. 易于扩展:新命令可以很容易地添加到系统中。

缺点

  • 过多命令类:系统可能会有过多的具体命令类,增加系统的复杂度。

使用建议

  • 在GUI中,每个按钮或菜单项可以视为一条命令。

  • 在需要模拟命令行操作的场景中使用命令模式。

注意事项

  • 如果系统需要支持命令的撤销(Undo)和恢复(Redo)操作,命令模式是一个合适的选择。

结构

主要涉及到以下几个核心角色:

  • 命令(Command):

    • 定义了执行操作的接口,通常包含一个 execute 方法,用于调用具体的操作。

  • 具体命令(ConcreteCommand):

    • 实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。

  • 接收者(Receiver):

    • 知道如何执行与请求相关的操作,实际执行命令的对象。

  • 调用者/请求者(Invoker):

    • 发送命令的对象,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。

  • 客户端(Client):

    • 创建具体命令对象并设置其接收者,将命令对象交给调用者执行。

package designmode.command;
​
/**
 * 命令接口
 */
public interface Command {
    void execute();
    void undo();
}
​
package designmode.command;
​
/**
 * 具体命令类
 */
public class ConcreteCommand implements Command{
​
    private TextEditor textEditor;
​
    private String text;
​
    public ConcreteCommand(TextEditor textEditor, String text) {
        this.textEditor = textEditor;
        this.text = text;
    }
​
    @Override
    public void execute() {
        textEditor.setText(textEditor.getText() + text);
    }
​
    @Override
    public void undo() {
        int index = textEditor.getText().lastIndexOf(text);
        if (index >= 0){
            textEditor.setText(textEditor.getText().substring(0, index));
        }
    }
}
​
package designmode.command;
​
import lombok.Data;
​
/**
 * 文本类
 */
@Data
public class TextEditor {
​
    private String text = "Hello";
}
​
​
package designmode.command;
​
import lombok.Data;
​
/**
 * 命令执行器
 */
@Data
public class CommandExecutor {
​
    private Command command;
​
    public void executeCommand(Command command) {
        command.execute();
    }
}
​
package designmode.command;

/**
 * 调用者
 */
public class Invoker {
    public static void main(String[] args) {
        TextEditor textEditor = new TextEditor();
        CommandExecutor commandExecutor = new CommandExecutor();

        System.out.println("=====添加单词=======");
        Command command = new ConcreteCommand(textEditor, " World");
        commandExecutor.setCommand(command);
        commandExecutor.executeCommand(command);

        System.out.println(textEditor.getText());

        //执行撤销操作
        System.out.println("======撤销添加=========");
        command.undo();
        System.out.println(textEditor.getText());
    }
}

结果: =====添加单词======= Hello World ======撤销添加========= Hello

十一、原型模式(prototype)

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

概要

原型模式

意图:使用原型实例指定要创建对象的种类,并通过拷贝这些原型创建新的对象。

主要解决:在运行时动态建立和删除原型。

何时使用

  • 系统应独立于产品的创建、构成和表示。

  • 需要在运行时指定实例化的类,例如通过动态加载。

  • 避免创建与产品类层次平行的工厂类层次。

  • 类的实例只能有几种不同状态组合,克隆原型比手工实例化更方便。

如何解决

通过已有的一个原型对象,快速生成与原型对象相同的实例。

关键代码

  • 实现克隆操作:

    • 在 Java 中,实现 Cloneable 接口,重写 clone() 方法。

    • 在 .NET 中,使用 Object 类的 MemberwiseClone() 方法实现浅拷贝,或通过序列化实现深拷贝。

  • 隔离类对象的使用者和具体类型之间的耦合关系,要求"易变类"拥有稳定的接口。

应用实例

  • 细胞分裂

  • Java 中的 Object.clone() 方法

优点

  • 性能提高

  • 避免构造函数的约束

缺点

  • 配备克隆方法需要全面考虑类的功能,对已有类可能较难实现,特别是处理不支持串行化的间接对象或含有循环结构的引用时。

  • 必须实现 Cloneable 接口。

使用场景

  • 资源优化

  • 类初始化需要消耗大量资源(如数据、硬件资源)

  • 性能和安全要求高的场景

  • 通过 new 创建对象需要复杂的数据准备或访问权限时

  • 一个对象需要多个修改者

  • 对象需提供给其他对象访问并可能被各个调用者修改时

  • 通常与工厂方法模式一起使用,通过 clone 创建对象,然后由工厂方法提供给调用者

注意事项

与直接实例化类创建新对象不同,原型模式通过拷贝现有对象生成新对象。浅拷贝通过实现 Cloneable 实现,深拷贝通过实现 Serializable 读取二进制流实现。

结构

原型模式包含以下几个主要角色:

  • 原型接口(Prototype Interface):定义一个用于克隆自身的接口,通常包括一个 clone() 方法。

  • 具体原型类(Concrete Prototype):实现原型接口的具体类,负责实际的克隆操作。这个类需要实现 clone() 方法,通常使用浅拷贝或深拷贝来复制自身。

  • 客户端(Client):使用原型实例来创建新的对象。客户端调用原型对象的 clone() 方法来创建新的对象,而不是直接使用构造函数。

深拷贝和浅拷贝的概念

  • 浅拷贝(Shallow Copy):创建一个新对象,新对象的基本数据类型成员的值会被复制,而引用数据类型成员仍然指向原始对象所指向的内存地址。也就是说,浅拷贝只是复制了对象的“顶层”,对于引用类型的成员,实际上复制的是引用,而不是引用所指向的对象。
  • 深拷贝(Deep Copy):创建一个新对象,不仅复制基本数据类型成员的值,对于引用数据类型的成员,也会为其创建新的对象,并复制其内容,使得新对象和原始对象完全独立,修改其中一个对象的引用类型成员不会影响到另一个对象。

实现实例

实现一个简历信息部分复制的功能。

package designmode.prototype;
​
import lombok.Data;
​
/**
 * 简历类
 */
@Data
public class Resume implements Cloneable {
​
    private String name;
​
    private String sex;
​
    private String age;
​
    private String timeArea;
​
    private String company;
​
    @Override
    public Resume clone() {
        try {
            return (Resume) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
​
package designmode.prototype;
​
/**
 * 调用者
 */
public class Invoker {
    public static void main(String[] args) {
        Resume resume = new Resume();
        resume.setName("张三");
        resume.setSex("男");
        resume.setAge("25");
        resume.setTimeArea("2018-2020");
        resume.setCompany("华为");
​
        Resume resume1 = resume.clone();//深复制
        resume1.setTimeArea("2020-至今");
        resume1.setCompany("中兴");
​
        System.out.println(resume);
        System.out.println(resume1);
    }
}

结果:

Resume(name=张三, sex=男, age=25, timeArea=2018-2020, company=华为) Resume(name=张三, sex=男, age=25, timeArea=2020-至今, company=中兴)

十二、状态模式(state)

在状态模式(State Pattern)中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。

介绍

意图

允许一个对象在其内部状态改变时改变其行为,看起来就像是改变了其类一样。

主要解决的问题

  • 状态模式解决对象行为依赖于其状态的问题,使得对象可以在状态变化时切换行为。

使用场景

  • 当代码中存在大量条件语句,且这些条件语句依赖于对象的状态时。

实现方式

  • 定义状态接口:声明一个或多个方法,用于封装具体状态的行为。

  • 创建具体状态类:实现状态接口,根据状态的不同实现具体的行为。

  • 定义上下文类:包含一个状态对象的引用,并在状态改变时更新其行为。

关键代码

  • 状态接口:声明行为方法。

  • 具体状态类:实现状态接口,封装具体行为。

  • 上下文类:维护一个状态对象,并提供方法以改变其状态。

应用实例

  • 篮球运动员状态:运动员可以有正常、不正常和超常等状态。

  • 曾侯乙编钟:编钟作为上下文,不同的钟(状态)有不同的演奏效果。

优点

  • 封装状态转换规则:将状态转换逻辑封装在状态对象内部。

  • 易于扩展:增加新的状态类不会影响现有代码。

  • 集中状态相关行为:将所有与特定状态相关的行为集中到一个类中。

  • 简化条件语句:避免使用大量的条件语句来切换行为。

  • 状态共享:允许多个上下文对象共享同一个状态对象。

缺点

  • 增加类和对象数量:每个状态都需要一个具体的状态类。

  • 实现复杂:模式结构和实现相对复杂。

  • 开闭原则支持不足:增加新状态或修改状态行为可能需要修改现有代码。

使用建议

  • 当对象的行为随状态改变而变化时,考虑使用状态模式。

  • 状态模式适用于替代复杂的条件或分支语句。

注意事项

  • 状态模式适用于状态数量有限(通常不超过5个)的情况。

  • 谨慎使用,以避免系统变得过于复杂。

结构

状态模式包含以下几个主要角色:

  • 上下文(Context):定义了客户感兴趣的接口,并维护一个当前状态对象的引用。上下文可以通过状态对象来委托处理状态相关的行为。

  • 状态(State):定义了一个接口,用于封装与上下文相关的一个状态的行为。

  • 具体状态(Concrete State):实现了状态接口,负责处理与该状态相关的行为。具体状态对象通常会在内部维护一个对上下文对象的引用,以便根据不同的条件切换到不同的状态。

实现

我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。

StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。

package designmode.state;

import lombok.Data;

/**
 * 上下文类
 */
@Data
public class Context {

    private State state;
}


package designmode.state;

/**
 * 状态类
 */
public abstract class State {
    public abstract void doAction(Context context);
}

package designmode.state;

/**
 * 开始状态类
 */
public class StartState extends State{
    @Override
    public void doAction(Context context) {
        context.setState(this);
    }

    public String toString(){
        return "开始状态";
    }
}

package designmode.state;

/**
 * 停止状态类
 */
public class StopState extends State{
    @Override
    public void doAction(Context context) {
        context.setState(this);
    }

    @Override
    public String toString() {
        return "停止状态";
    }
}
package designmode.state;

/**
 * 调用者
 */
public class Invoker {
    public static void main(String[] args) {
        Context context = new Context();

        StartState startState = new StartState();
        startState.doAction(context);
        System.out.println(context.getState().toString());

        StopState stopState = new StopState();
        stopState.doAction(context);
        System.out.println(context.getState().toString());
    }
}

结果: 开始状态 停止状态

十三、适配器模式(adapter)

适配器模式(Adapter Pattern)充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个类的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

假设有一个音频播放器,它只能播放 MP3 文件。现在,我们需要播放 VLC 和 MP4 文件,可以通过创建一个适配器来实现:

  • 目标接口:定义一个可以播放多种格式文件的音频播放器接口。

  • 适配者类:现有的音频播放器,只能播放 MP3 文件。

  • 适配器类:创建一个新的类,实现目标接口,并在内部使用适配者类来播放 MP3 文件,同时添加对 VLC 和 MP4 文件的支持。

概述

适配器模式是一种软件设计模式,旨在解决不同接口之间的兼容性问题。

目的:将一个类的接口转换为另一个接口,使得原本不兼容的类可以协同工作。

主要解决的问题:在软件系统中,需要将现有的对象放入新环境,而新环境要求的接口与现有对象不匹配。

使用场景

  • 需要使用现有类,但其接口不符合系统需求。

  • 希望创建一个可复用的类,与多个不相关的类(包括未来可能引入的类)一起工作,这些类可能没有统一的接口。

  • 通过接口转换,将一个类集成到另一个类系中。

实现方式

  • 继承或依赖:推荐使用依赖关系,而不是继承,以保持灵活性。

关键代码

适配器通过继承或依赖现有对象,并实现所需的目标接口。

应用实例

  • 电压适配器:将 110V 电压转换为 220V,以适配不同国家的电器标准。

  • 接口转换:例如,将 Java JDK 1.1 的 Enumeration 接口转换为 1.2 的 Iterator 接口。

  • 跨平台运行:在Linux上运行Windows程序。

  • 数据库连接:Java 中的 JDBC 通过适配器模式与不同类型的数据库进行交互。

优点

  • 促进了类之间的协同工作,即使它们没有直接的关联。

  • 提高了类的复用性。

  • 增加了类的透明度。

  • 提供了良好的灵活性。

缺点

  • 过度使用适配器可能导致系统结构混乱,难以理解和维护。

  • 在Java中,由于只能继承一个类,因此只能适配一个类,且目标类必须是抽象的。

使用建议

  • 适配器模式应谨慎使用,特别是在详细设计阶段,它更多地用于解决现有系统的问题。

  • 在考虑修改一个正常运行的系统接口时,适配器模式是一个合适的选择。

通过这种方式,适配器模式可以清晰地表达其核心概念和应用,同时避免了不必要的复杂性。

结构

适配器模式包含以下几个主要角色:

  • 目标接口(Target):定义客户需要的接口。

  • 适配者类(Adaptee):定义一个已经存在的接口,这个接口需要适配。

  • 适配器类(Adapter):实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口。

实现

我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。

我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。

我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。

AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。

public interface MediaPlayer {
   public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer { 
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);      
   }
 
   @Override
   public void playMp4(String fileName) {
      //什么也不做
   }
}
public class Mp4Player implements AdvancedMediaPlayer{
 
   @Override
   public void playVlc(String fileName) {
      //什么也不做
   }
 
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);      
   }
}
public class MediaAdapter implements MediaPlayer {
 
   AdvancedMediaPlayer advancedMusicPlayer;
 
   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();       
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }  
   }
 
   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 
 
   @Override
   public void play(String audioType, String fileName) {    
 
      //播放 mp3 音乐文件的内置支持
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);         
      } 
      //mediaAdapter 提供了播放其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+
            audioType + " format not supported");
      }
   }   
}
public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
 
      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

结果:

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name:alone.mp4
Playing vlc fiel. Name:far far away.vlc
Invalid media. avi format not supported

十四、备忘录模式(memento)

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象,备忘录模式属于行为型模式。

备忘录模式允许在不破坏封装性的前提下,捕获和恢复对象的内部状态。

介绍

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并允许在对象之外保存和恢复这些状态。

主要解决的问题

  • 允许捕获并保存一个对象的内部状态,以便在将来可以恢复到该状态,实现撤销和回滚操作。

使用场景

  • 当需要提供一种撤销机制,允许用户回退到之前的状态时。

实现方式

  • 创建备忘录类:用于存储和封装对象的状态。

  • 创建发起人角色:负责创建备忘录,并根据需要恢复状态。

  • 创建备忘录管理类(可选):负责管理所有备忘录对象。

关键代码

  • 备忘录:存储发起人的状态信息。

  • 发起人:创建备忘录,并根据备忘录恢复状态。

应用实例

  • 后悔药:提供一种撤销操作的功能。

  • 游戏存档:保存游戏进度,允许玩家加载之前的存档。

  • Windows中的Ctrl+Z:实现撤销操作。

  • IE浏览器的后退:允许用户回退到之前的页面。

  • 数据库事务管理:通过事务日志保存状态,实现回滚。

优点

  • 提供状态恢复机制:允许用户方便地回到历史状态。

  • 封装状态信息:用户不需要关心状态的保存细节。

缺点

  • 资源消耗:如果对象的状态复杂,保存状态可能会占用较多资源。

使用建议

  • 在需要保存和恢复数据状态的场景中使用备忘录模式。

  • 考虑使用原型模式结合备忘录模式,以节约内存。

注意事项

  • 为了降低耦合度,应通过备忘录管理类间接管理备忘录对象。

  • 备忘录模式应谨慎使用,避免过度消耗系统资源。

结构

备忘录模式包含以下几个主要角色:

  • 备忘录(Memento):负责存储原发器对象的内部状态。备忘录可以保持原发器的状态的一部分或全部信息。

  • 原发器(Originator):创建一个备忘录对象,并且可以使用备忘录对象恢复自身的内部状态。原发器通常会在需要保存状态的时候创建备忘录对象,并在需要恢复状态的时候使用备忘录对象。

  • 负责人(Caretaker):负责保存备忘录对象,但是不对备忘录对象进行操作或检查。负责人只能将备忘录传递给其他对象。

实现

备忘录模式使用三个类 MementoOriginatorCareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。

MementoPatternDemo,我们的演示类使用 CareTakerOriginator 对象来显示对象的状态恢复。

package designmode.memento;
​
import lombok.Data;
​
/**
 * 备忘录
 */
@Data
public class Memento {
​
    private String state;
}
    
package designmode.memento;
​
import lombok.Data;
​
/**
 * 发起人
 */
@Data
public class Originator {
​
    private String state;
​
    public void setMemento(Memento memento){
        state = memento.getState();
    }
​
    public void show(){
        System.out.println("state:"+state);
    }
​
    public Memento createMemento() {
        Memento memento = new Memento();
        memento.setState(state);
​
        return memento;
    }
}
​
package designmode.memento;
​
import lombok.Data;
​
/**
 * 管理者类
 */
@Data
public class Caretaker {
​
    private Memento memento;
}
​
package designmode.memento;
​
/**
 * 调用者
 */
public class Invoker {
    public static void main(String[] args) {
​
        // 创建发起人,并设置初始状态
        Originator originator = new Originator();
        originator.setState("On");
        originator.show();
​
        // 创建备忘录,并保存当前状态
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.createMemento());
​
        // 改变发起人的状态
        originator.setState("Off");
        originator.show();
​
        // 恢复发起人的状态
        originator.setMemento(caretaker.getMemento());
        originator.show();
    }
}

结果: state:On state:Off state:On

十五、组合模式(component)

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。

介绍

意图

将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

主要解决的问题

  • 简化树形结构中对象的处理,无论它们是单个对象还是组合对象。

  • 解耦客户端代码与复杂元素的内部结构,使得客户端可以统一处理所有类型的节点。

使用场景

  • 当需要表示对象的层次结构时,如文件系统或组织结构。

  • 当希望客户端代码能够以一致的方式处理树形结构中的所有对象时。

实现方式

  • 统一接口:定义一个接口,所有对象(树枝和叶子)都实现这个接口。

  • 组合结构:树枝对象包含一个接口的引用列表,这些引用可以是叶子或树枝。

关键代码

  • Component接口:定义了所有对象必须实现的操作。

  • Leaf类:实现Component接口,代表树中的叶子节点。

  • Composite类:也实现Component接口,并包含其他Component对象的集合。

应用实例

  1. 算术表达式:构建一个由操作数、操作符和子表达式组成的树形结构。

  2. GUI组件:在Java的AWT和Swing库中,容器(如Panel)可以包含其他组件(如按钮和复选框)。

优点

  1. 简化客户端代码:客户端可以统一处理所有类型的节点。

  2. 易于扩展:可以轻松添加新的叶子类型或树枝类型。

缺点

  • 违反依赖倒置原则:组件的声明是基于具体类而不是接口,这可能导致代码的灵活性降低。

使用建议

  • 在设计时,优先使用接口而非具体类,以提高系统的灵活性和可维护性。

  • 适用于需要处理复杂树形结构的场景,如文件系统、组织结构等。

注意事项

  • 在实现时,确保所有组件都遵循统一的接口,以保持一致性。

  • 考虑使用工厂模式来创建不同类型的组件,以进一步解耦组件的创建逻辑。

结构

组合模式的核心角色包括:

  • 组件(Component):

    • 定义了组合中所有对象的通用接口,可以是抽象类或接口。它声明了用于访问和管理子组件的方法,包括添加、删除、获取子组件等。

  • 叶子节点(Leaf):

    • 表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件。

  • 复合节点(Composite):

    • 表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。它实现了组件接口的方法,包括管理子组件的方法。

  • 客户端(Client):

    • 通过组件接口与组合结构进行交互,客户端不需要区分叶子节点和复合节点,可以一致地对待整体和部分。

实现

我们有一个类 Employee,该类被当作组合模型类。CompositePatternDemo 类使用 Employee 类来添加部门层次结构,并打印所有员工。

package designmode.component;
​
import lombok.Data;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * 员工
 */
@Data
public class Employee {
​
    private String name;
​
    private String dept;
​
    private int salary;
​
    private List<Employee> subEmployees;
​
    Employee(String name, String dept, int salary) {
        this.name = name;
        this.dept = dept;
        this.salary = salary;
        subEmployees = new ArrayList<>();
    }
​
    public void add(Employee employee){
        subEmployees.add(employee);
    }
​
    public void remove(Employee employee){
        subEmployees.remove(employee);
    }
​
}
​
package designmode.component;
​
/**
 * 调用类
 */
public class Invoker {
​
    public static void main(String[] args) {
        // CEO
        Employee ceo = new Employee("张三", "CEO", 50000);
        // 销售部门经理
        Employee headSales = new Employee("李四", "销售部门经理", 20000);
        // 超市部门经理
        Employee headMarketing = new Employee("王五", "超市部门经理", 20000);
​
        //专员
        Employee clerk1 = new Employee("Laura","专员", 10000);
        Employee clerk2 = new Employee("Bob","专员", 10000);
​
        //销售
        Employee salesExecutive1 = new Employee("Richard","销售", 10000);
        Employee salesExecutive2 = new Employee("Rob","销售", 10000);
​
        ceo.add(headSales);
        ceo.add(headMarketing);
        headSales.add(salesExecutive1);
        headSales.add(salesExecutive2);
​
        headMarketing.add(clerk1);
        headMarketing.add(clerk2);
​
        System.out.println("=====打印所有该组织的员工=====");
        System.out.println(ceo);
        for (Employee subEmployee : ceo.getSubEmployees()) {
            System.out.println(subEmployee);
            for (Employee subEmployee1 : subEmployee.getSubEmployees()) {
                System.out.println(subEmployee1);
            }
        }
    }
}

结果:

=====打印所有该组织的员工===== Employee(name=张三, dept=CEO, salary=50000, subEmployees=[Employee(name=李四, dept=销售部门经理, salary=20000, subEmployees=[Employee(name=Richard, dept=销售, salary=10000, subEmployees=[]), Employee(name=Rob, dept=销售, salary=10000, subEmployees=[])]), Employee(name=王五, dept=超市部门经理, salary=20000, subEmployees=[Employee(name=Laura, dept=专员, salary=10000, subEmployees=[]), Employee(name=Bob, dept=专员, salary=10000, subEmployees=[])])]) Employee(name=李四, dept=销售部门经理, salary=20000, subEmployees=[Employee(name=Richard, dept=销售, salary=10000, subEmployees=[]), Employee(name=Rob, dept=销售, salary=10000, subEmployees=[])]) Employee(name=Richard, dept=销售, salary=10000, subEmployees=[]) Employee(name=Rob, dept=销售, salary=10000, subEmployees=[]) Employee(name=王五, dept=超市部门经理, salary=20000, subEmployees=[Employee(name=Laura, dept=专员, salary=10000, subEmployees=[]), Employee(name=Bob, dept=专员, salary=10000, subEmployees=[])]) Employee(name=Laura, dept=专员, salary=10000, subEmployees=[]) Employee(name=Bob, dept=专员, salary=10000, subEmployees=[])

十六、迭代器模式(Iterator )

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

迭代器模式属于行为型模式。

介绍

意图

允许顺序访问一个聚合对象中的元素,同时不暴露对象的内部表示。

主要解决的问题

  • 提供一种统一的方法来遍历不同的聚合对象。

使用场景

  • 当需要遍历一个聚合对象,而又不希望暴露其内部结构时。

实现方式

  • 定义迭代器接口:包含hasNext()next()等方法,用于遍历元素。

  • 创建具体迭代器:实现迭代器接口,定义如何遍历特定的聚合对象。

  • 聚合类:定义一个接口用于返回一个迭代器对象。

关键代码

  • 迭代器接口:规定了遍历元素的方法。

  • 具体迭代器:实现了迭代器接口,包含遍历逻辑。

应用实例

  • Java中的Iterator:Java集合框架中的迭代器用于遍历集合元素。

优点

  1. 支持多种遍历方式:不同的迭代器可以定义不同的遍历方式。

  2. 简化聚合类:聚合类不需要关心遍历逻辑。

  3. 多遍历支持:可以同时对同一个聚合对象进行多次遍历。

  4. 扩展性:增加新的聚合类和迭代器类都很方便,无需修改现有代码。

缺点

  • 系统复杂性:每增加一个聚合类,就需要增加一个对应的迭代器类,增加了类的数量。

使用建议

  • 当需要访问聚合对象内容而不暴露其内部表示时,使用迭代器模式。

  • 当需要为聚合对象提供多种遍历方式时,考虑使用迭代器模式。

注意事项

  • 迭代器模式通过分离集合对象的遍历行为,使得外部代码可以透明地访问集合内部数据,同时不暴露集合的内部结构。

结构

迭代器模式包含以下几个主要角色:

  • 迭代器接口(Iterator):定义了访问和遍历聚合对象中各个元素的方法,通常包括获取下一个元素、判断是否还有元素、获取当前位置等方法。

  • 具体迭代器(Concrete Iterator):实现了迭代器接口,负责对聚合对象进行遍历和访问,同时记录遍历的当前位置。

  • 聚合对象接口(Aggregate):定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。

  • 具体聚合对象(Concrete Aggregate):实现了聚合对象接口,负责创建具体的迭代器对象,并提供需要遍历的数据。

实现

我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。

IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names

package com.example.designmodestudy.iterator;
​
public interface Container {
    Iterator getIterator();
}
​
package com.example.designmodestudy.iterator;
​
public class NameRepository implements Container{
​
    public String[] names = {"张三" , "李四" ,"王五" , "赵六"};
​
​
    @Override
    public Iterator getIterator() {
        return new NameIterator();
    }
​
    private class NameIterator implements Iterator {
​
        int index;
​
        @Override
        public boolean hasNext() {
            if(index < names.length){
                return true;
            }
            return false;
        }
​
        @Override
        public Object next() {
            if(this.hasNext()){
                return names[index++];
            }
            return null;
        }
    }
}
​
package com.example.designmodestudy.iterator;
​
import java.util.Objects;
​
public class Invoker {
​
    public static void main(String[] args) {
        NameRepository namesRepository = new NameRepository();
        Iterator iter = namesRepository.getIterator();
​
        do {
            String name = (String)iter.next();
            System.out.println("Name : " + name);
        } while (Objects.nonNull(iter) && iter.hasNext());
​
    }
}

结果: Name : 张三 Name : 李四 Name : 王五 Name : 赵六

十七、单例模式(Singleton )

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

注意:

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例。

概要

单例模式(Singleton Pattern)

意图

确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

主要解决

频繁创建和销毁全局使用的类实例的问题。

何时使用

当需要控制实例数目,节省系统资源时。

如何解决

检查系统是否已经存在该单例,如果存在则返回该实例;如果不存在则创建一个新实例。

关键代码

构造函数是私有的。

应用实例

  • 一个班级只有一个班主任。

  • Windows 在多进程多线程环境下操作文件时,避免多个进程或线程同时操作一个文件,需要通过唯一实例进行处理。

  • 设备管理器设计为单例模式,例如电脑有两台打印机,避免同时打印同一个文件。

优点

  • 内存中只有一个实例,减少内存开销,尤其是频繁创建和销毁实例时(如管理学院首页页面缓存)。

  • 避免资源的多重占用(如写文件操作)。

缺点

  • 没有接口,不能继承。

  • 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心实例化方式。

使用场景

  • 生成唯一序列号。

  • WEB 中的计数器,避免每次刷新都在数据库中增加计数,先缓存起来。

  • 创建消耗资源过多的对象,如 I/O 与数据库连接等。

注意事项

  • 线程安全getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成实例被多次创建。

  • 延迟初始化:实例在第一次调用 getInstance() 方法时创建。

  • 序列化和反序列化:重写 readResolve 方法以确保反序列化时不会创建新的实例。

  • 反射攻击:在构造函数中添加防护代码,防止通过反射创建新实例。

  • 类加载器问题:注意复杂类加载环境可能导致的多个实例问题。

  • 静态变量‌:被static修饰的变量称为静态变量或类变量,它属于类本身,不属于任何对象实例。静态变量在类加载时初始化,并且所有对象共享同一个静态变量实例。例如,一个类中的计数器变量通常定义为静态变量,因为它需要在多个对象之间共享‌。

  • 静态方法‌:被static修饰的方法称为静态方法或类方法。静态方法不需要创建对象即可调用,直接通过类名访问。静态方法不能访问非静态成员(即实例变量和实例方法),也不能使用this关键字‌。

结构

单例模式包含以下几个主要角色:

  • 单例类:包含单例实例的类,通常将构造函数声明为私有。

  • 静态成员变量:用于存储单例实例的静态成员变量。

  • 获取实例方法:静态方法,用于获取单例实例。

  • 私有构造函数:防止外部直接实例化单例类。

  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。

实现

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。

单例模式的几种实现方式

单例模式的实现有多种方式,如下所示:

1、懒汉式,线程不安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
            //return new Singleton(); 错误应用
        }  
        return instance;  
    }  
}

2、懒汉式,线程安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。 优点:第一次调用才初始化,避免内存浪费。 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。 getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

package com.example.designmodestudy.singleton.lazy;
​
import com.example.designmodestudy.singleton.User;
​
public class SingleObject {
​
    private static User instance;
​
    private SingleObject(){}
​
    public static synchronized User getInstance(){
        if(instance == null){
            System.out.println("懒汉式 loading...");
            instance = new User();
            //return new User(); 错误应用
        }
        return instance;
    }
}

2、饿汉式

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。 优点:没有加锁,执行效率会提高。 缺点:类加载时就初始化,浪费内存。 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

package com.example.designmodestudy.singleton.hungry;
​
import com.example.designmodestudy.singleton.User;
​
public class SingleObject {
​
    private static User instance = new User();
​
    private  SingleObject(){}
​
    public static User getInstance(){
        System.out.println("饿汉式 loading...");
        return instance;
    }
}
​
package com.example.designmodestudy.singleton.hungry;
​
import com.example.designmodestudy.singleton.User;
​
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class Invoker {
    public static void main(String[] args) throws InterruptedException {
​
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(20));
        List<User> userList = new CopyOnWriteArrayList<>();
​
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPoolExecutor.execute(()->{
​
                User user = SingleObject.getInstance();
                user.setAge(25);
                user.setName("张三_" + finalI);
                userList.add(user);
                System.out.println(Thread.currentThread().getName() + ", 循环" + finalI);
            });
        }
​
​
        Thread.sleep(100);
​
        System.out.println("==============循环打印集合里的用户数据=========================");
        for (User user : userList) {
            System.out.println(user);
        }
​
        threadPoolExecutor.shutdown();
    }
}

结果: 饿汉式 loading... 饿汉式 loading... 饿汉式 loading... 饿汉式 loading... pool-1-thread-1, 循环0 饿汉式 loading... 饿汉式 loading... pool-1-thread-2, 循环1 pool-1-thread-5, 循环4 pool-1-thread-4, 循环3 饿汉式 loading... 饿汉式 loading... pool-1-thread-1, 循环5 pool-1-thread-3, 循环2 饿汉式 loading... pool-1-thread-2, 循环6 pool-1-thread-5, 循环7 饿汉式 loading... pool-1-thread-1, 循环9 pool-1-thread-4, 循环8 ==============循环打印集合里的用户数据========================= User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25) User(name=张三8, age=25)

4、双检锁/双重校验锁(DCL,即 double-checked locking)

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 getInstance() 的性能对应用程序很关键。

package com.example.designmodestudy.singleton.dcl;
​
import com.example.designmodestudy.singleton.User;
​
public class SingleObject {
​
    private static volatile User instance;
​
    private SingleObject(){}
​
    public static User getInstance(){
        if(instance == null){
            System.out.println("dcl 懒汉式 first check loading...");
            synchronized (User.class){
                System.out.println("dcl 懒汉式 second check loading...");
                if(instance == null){
                    instance = new User();
                }
            }
​
        }
        return instance;
    }
}
​
package com.example.designmodestudy.singleton.dcl;
​
import com.example.designmodestudy.singleton.User;
​
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class Invoker {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(20));
        List<User> userList = new CopyOnWriteArrayList<>();
​
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            threadPoolExecutor.execute(()->{
​
                User user = SingleObject.getInstance();
                user.setAge(25);
                user.setName("张三_" + finalI);
                userList.add(user);
                System.out.println(Thread.currentThread().getName() + ", 循环" + finalI);
            });
        }
​
​
        Thread.sleep(100);
​
        System.out.println("==============循环打印集合里的用户数据=========================");
        for (User user : userList) {
            System.out.println(user);
        }
​
        threadPoolExecutor.shutdown();
    }
}

结果: dcl 懒汉式 first check loading... dcl 懒汉式 first check loading... dcl 懒汉式 first check loading... dcl 懒汉式 first check loading... dcl 懒汉式 first check loading... dcl 懒汉式 second check loading... dcl 懒汉式 second check loading... dcl 懒汉式 second check loading... dcl 懒汉式 second check loading... pool-1-thread-2, 循环1 pool-1-thread-4, 循环3 pool-1-thread-2, 循环5 pool-1-thread-4, 循环6 dcl 懒汉式 second check loading... pool-1-thread-5, 循环4 pool-1-thread-1, 循环0 pool-1-thread-5, 循环9 pool-1-thread-3, 循环2 pool-1-thread-4, 循环8 pool-1-thread-2, 循环7 ==============循环打印集合里的用户数据========================= User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25) User(name=张三9, age=25)

十八、桥接模式(bridge)

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影响。

桥接模式的目的是将抽象与实现分离,使它们可以独立地变化,该模式通过将一个对象的抽象部分与它的实现部分分离,使它们可以独立地改变。它通过组合的方式,而不是继承的方式,将抽象和实现的部分连接起来。

我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。

介绍

意图

用于将抽象部分与实现部分分离,使得它们可以独立地变化。。

主要解决的问题

避免使用继承导致的类爆炸问题,提供更灵活的扩展方式。

使用场景

当系统可能从多个角度进行分类,且每个角度都可能独立变化时,桥接模式是合适的。

实现方式

  • 分离多角度分类:将不同角度的分类逻辑分离,允许它们独立变化。

  • 减少耦合:降低抽象与实现之间的耦合度。

关键代码

  • 抽象类:定义一个抽象类,作为系统的一部分。

  • 实现类:定义一个或多个实现类,与抽象类通过聚合(而非继承)关联。

应用实例

  • 转世投胎:灵魂(抽象)与肉体(实现)的分离,允许灵魂选择不同的肉体。

  • 墙上的开关:开关(抽象)与内部实现(实现)的分离,用户无需关心开关的内部工作机制。

优点

  • 抽象与实现分离:提高了系统的灵活性和可维护性。

  • 扩展能力强:可以独立地扩展抽象和实现。

  • 实现细节透明:用户不需要了解实现细节。

缺点

  • 理解与设计难度:桥接模式增加了系统的理解与设计难度。

  • 聚合关联:要求开发者在抽象层进行设计与编程。

使用建议

  • 当系统需要在抽象化角色和具体化角色之间增加灵活性时,考虑使用桥接模式。

  • 对于不希望使用继承或因多层次继承导致类数量急剧增加的系统,桥接模式特别适用。

  • 当一个类存在两个独立变化的维度,且这两个维度都需要扩展时,使用桥接模式。

注意事项

  • 桥接模式适用于两个独立变化的维度,确保它们可以独立地扩展和变化。

结构

以下是桥接模式的几个关键角色:

  • 抽象(Abstraction):定义抽象接口,通常包含对实现接口的引用。

  • 扩展抽象(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体实现类。

  • 实现(Implementor):定义实现接口,提供基本操作的接口。

  • 具体实现(Concrete Implementor):实现实现接口的具体类。

实现

package com.example.designmodestudy.bridge;
​
public interface DrawAPI {
    public void drawCircle(int radius, int x, int y);
​
}
​
​
package com.example.designmodestudy.bridge;
​
public class GreenCircle implements DrawAPI{
    @Override
    public void drawCircle(int radius, int x, int y) {
        System.out.println("Drawing Circle[ color: green, radius: "
                + radius +", x: " +x+", "+ y +"]");
    }
}
​
package com.example.designmodestudy.bridge;
​
public class RedCircle implements DrawAPI{
    @Override
    public void drawCircle(int radius, int x, int y) {
        System.out.println("Drawing Circle[ color: red, radius: "
                + radius +", x: " +x+", "+ y +"]");
    }
}
​
package com.example.designmodestudy.bridge;
​
public abstract class Shape {
​
    protected DrawAPI drawAPI;
​
    public Shape(DrawAPI drawAPI){
        this.drawAPI = drawAPI;
    }
​
    abstract void draw();
}
​
​
package com.example.designmodestudy.bridge;
​
public class Circle extends Shape{
​
    private int x, y, radius;
​
    public Circle(int x, int y, int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
​
    @Override
    void draw() {
        drawAPI.drawCircle(radius, x, y);
    }
}
​
​
package com.example.designmodestudy.bridge;
​
public class Invoker {
    public static void main(String[] args) {
        Shape redCircle = new Circle(50, 50 , 10, new RedCircle());
        Shape greenCircle = new Circle(50, 50, 10, new GreenCircle());
​
        redCircle.draw();
        greenCircle.draw();
    }
}

结果: Drawing Circle[ color: red, radius: 10, x: 50, 50] Drawing Circle[ color: green, radius: 10, x: 50, 50]

十九、责任链模式(Chain of Responsibility)

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

责任链模式通过将多个处理器(处理对象)以链式结构连接起来,使得请求沿着这条链传递,直到有一个处理器处理该请求为止。

责任链模式允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求。

介绍

意图

允许将请求沿着处理者链传递,直到请求被处理为止。

主要解决的问题

  • 解耦请求发送者和接收者,使多个对象都有可能接收请求,而发送者不需要知道哪个对象会处理它。

使用场景

  • 当有多个对象可以处理请求,且具体由哪个对象处理由运行时决定时。

  • 当需要向多个对象中的一个提交请求,而不想明确指定接收者时。

实现方式

  • 定义处理者接口:所有处理者必须实现同一个接口。

  • 创建具体处理者:实现接口的具体类,包含请求处理逻辑和指向链中下一个处理者的引用。

关键代码

  • Handler接口:定义一个方法用于处理请求。

  • ConcreteHandler类:实现Handler接口,包含请求处理逻辑和对下一个处理者的引用。

应用实例

  1. 击鼓传花:游戏中的传递行为,直到音乐停止。

  2. 事件冒泡:在JavaScript中,事件从最具体的元素开始,逐级向上传播。

  3. Web服务器:如Apache Tomcat处理字符编码,Struts2的拦截器,以及Servlet的Filter。

优点

  1. 降低耦合度:发送者和接收者之间解耦。

  2. 简化对象:对象不需要知道链的结构。

  3. 灵活性:通过改变链的成员或顺序,动态地新增或删除责任。

  4. 易于扩展:增加新的请求处理类很方便。

缺点

  1. 请求未被处理:不能保证请求一定会被链中的某个处理者接收。

  2. 性能影响:可能影响系统性能,且调试困难,可能导致循环调用。

  3. 难以观察:运行时特征不明显,可能妨碍除错。

使用建议

  • 在处理请求时,如果有多个潜在的处理者,考虑使用责任链模式。

  • 确保链中的每个处理者都明确知道如何传递请求到链的下一个环节。

注意事项

  • 在Java Web开发中,责任链模式有广泛应用,如过滤器链、拦截器等。

结构

主要涉及到以下几个核心角色:

  • 抽象处理者(Handler):

    • 定义一个处理请求的接口,通常包含一个处理请求的方法(如 handleRequest)和一个指向下一个处理者的引用(后继者)。

  • 具体处理者(ConcreteHandler):

    • 实现了抽象处理者接口,负责处理请求。如果能够处理该请求,则直接处理;否则,将请求传递给下一个处理者。

  • 客户端(Client):

    • 创建处理者对象,并将它们连接成一条责任链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程。

实现

我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

package com.example.designmodestudy.chain;
​
public abstract class AbstractLogger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;
​
    protected int level;
​
    //责任链中的下一个元素
    protected AbstractLogger nextLogger;
​
    public void setNextLogger(AbstractLogger nextLogger){
        this.nextLogger = nextLogger;
    }
​
    public void logMessage(int level ,String message){
        if(this.level <= level){
            write(message);
        }
        if(nextLogger != null){
            nextLogger.logMessage(level, message);
        }
    }
​
    abstract void write(String message);
}
​
package com.example.designmodestudy.chain;
​
public class ConsoleLogger extends AbstractLogger{
​
    public ConsoleLogger(int level){
        this.level = level;
    }
    @Override
    void write(String message) {
        System.out.println("Standard console::Logger:" + message);
    }
}
​
package com.example.designmodestudy.chain;
​
public class ErrorLogger extends AbstractLogger{
    public ErrorLogger(int level){
        this.level = level;
    }
    @Override
    void write(String message) {
        System.out.println("Error Console::Logger:" + message);
    }
}
​
package com.example.designmodestudy.chain;
​
public class FileLogger extends AbstractLogger{
    public FileLogger(int level){
        this.level = level;
    }
    @Override
    void write(String message) {
        System.out.println("Normal   file::Logger:  " + message);
    }
}
​
package com.example.designmodestudy.chain;
​
public class Invoker {
​
    private static AbstractLogger getChainOfLoggers(){
        AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
        AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
        AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
​
        errorLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(consoleLogger);
​
        return errorLogger;
    }
​
    public static void main(String[] args) {
        AbstractLogger loggerChain = getChainOfLoggers();
        loggerChain.logMessage(AbstractLogger.INFO, AbstractLogger.INFO + "这是一条正常信息");
        loggerChain.logMessage(AbstractLogger.DEBUG, AbstractLogger.DEBUG + "这是一个debug信息");
        loggerChain.logMessage(AbstractLogger.ERROR, AbstractLogger.ERROR + "这是一个error信息");
    }
}
​

结果: Standard console::Logger:1这是一条正常消息 Normal file::Logger: 2这是一个debug消息 Standard console::Logger:2这是一个debug消息 Error Console::Logger:3这是一个error消息 Normal file::Logger: 3这是一个error消息 Standard console::Logger:3这是一个error消息

二十、中介者模式

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性,属于行为型模式。

中介者模式定义了一个中介对象来封装一系列对象之间的交互。中介者使各对象之间不需要显式地相互引用,从而使其耦合松散,且可以独立地改变它们之间的交互。

介绍

意图

通过引入一个中介者对象来封装和协调多个对象之间的交互,从而降低对象间的耦合度。

主要解决的问题

  • 解决对象间复杂的一对多关联问题,避免对象之间的高度耦合,简化系统结构。

使用场景

  • 当系统中多个类相互耦合,形成网状结构时。

实现方式

  • 定义中介者接口:规定中介者必须实现的接口。

  • 创建具体中介者:实现中介者接口,包含协调各同事对象交互的逻辑。

  • 定义同事类:各个对象不需要显式地相互引用,而是通过中介者来进行交互。

关键代码

  • 中介者:封装了对象间的交互逻辑。

  • 同事类:通过中介者进行通信。

应用实例

  1. WTO:中国加入WTO后,各国通过WTO进行贸易,简化了双边关系。

  2. 机场调度系统:协调飞机起降、跑道使用等。

  3. MVC框架:控制器作为模型和视图的中介者。

优点

  1. 降低复杂度:将多个对象间的一对多关系转换为一对一关系。

  2. 解耦:对象之间不再直接引用,通过中介者进行交互。

  3. 符合迪米特原则:对象只需知道中介者,不需要知道其他对象。

缺点

  • 中介者复杂性:中介者可能会变得庞大和复杂,难以维护。

使用建议

  • 当系统中对象间存在复杂的引用关系时,考虑使用中介者模式。

  • 通过中介者封装多个类的行为,避免生成过多的子类。

注意事项

  • 避免在职责不明确或混乱的情况下使用中介者模式,这可能导致中介者承担过多职责。

结构

中介者模式包含以下几个主要角色:

  • 中介者(Mediator):定义了一个接口用于与各个同事对象通信,并管理各个同事对象之间的关系。通常包括一个或多个事件处理方法,用于处理各种交互事件。

  • 具体中介者(Concrete Mediator):实现了中介者接口,负责实现各个同事对象之间的通信逻辑。它会维护一个对各个同事对象的引用,并协调它们的交互。

  • 同事对象(Colleague):定义了一个接口,用于与中介者进行通信。通常包括一个发送消息的方法,以及一个接收消息的方法。

  • 具体同事对象(Concrete Colleague):实现了同事对象接口,是真正参与到交互中的对象。它会将自己的消息发送给中介者,由中介者转发给其他同事对象。

实现

package com.example.designmodestudy.mediator;
​
/**
 * 同事类
 */
public abstract class Colleague {
​
    //中介者
    protected Mediator mediator;
​
    //同事名称
    protected String name;
​
    public Colleague(Mediator mediator){
        this.mediator = mediator;
        mediator.addColleague(this);
    }
​
    //发送消息
    public abstract void sendMsg(String message);
​
    //接收消息
    public abstract void receiveMsg(String msg, Colleague sender);
​
}
package com.example.designmodestudy.mediator;
​
/**
 * 同事A
 */
public class ConcreteColleagueA extends Colleague{
​
    public ConcreteColleagueA(Mediator mediator, String name) {
        super(mediator);
        this.name = name;
    }
​
    @Override
    public void sendMsg(String message) {
        System.out.println(name + "发送消息:" + message);
        mediator.sendMsg(message, this);
    }
​
    @Override
    public void receiveMsg(String msg, Colleague sender) {
        System.out.println("同事A收到来自 " + sender.name + " 消息:" + msg);
    }
}
​
​
package com.example.designmodestudy.mediator;
​
/**
 * 同事B
 */
public class ConcreteColleagueB extends Colleague{
​
    public ConcreteColleagueB(Mediator mediator, String name) {
        super(mediator);
        this.name = name;
    }
​
    @Override
    public void sendMsg(String message) {
        System.out.println(name + "发送消息:" + message);
        mediator.sendMsg(message, this);
    }
​
    @Override
    public void receiveMsg(String msg, Colleague sender) {
        System.out.println("同事B收到来自 " + sender.name + " 消息:" + msg);
    }
}
package com.example.designmodestudy.mediator;
​
import java.util.ArrayList;
import java.util.List;
​
/**
 * 中介者抽象类
 */
public abstract class Mediator {
​
    protected List<Colleague> colleagueList = new ArrayList<>();
​
    //添加同事
    public void addColleague(Colleague colleague){
        colleagueList.add(colleague);
    }
​
    //移除同事
    public void removeColleague(Colleague colleague){
        colleagueList.remove(colleague);
    }
​
    //发送消息
    abstract void sendMsg(String message, Colleague colleague);
}
​
​
package com.example.designmodestudy.mediator;
​
/**
 * 中介者实现类
 */
public class ConcreteMediator extends Mediator{
    @Override
    void sendMsg(String message, Colleague sender) {
        for (Colleague colleague : colleagueList) {
            if(colleague != sender){
                colleague.receiveMsg(message, sender);
            }
        }
    }
}
​
package com.example.designmodestudy.mediator;
​
public class Invoker {
    public static void main(String[] args) {
        Mediator mediator = new ConcreteMediator();
        Colleague colleagueA = new ConcreteColleagueA(mediator, "同事A");
        Colleague colleagueB = new ConcreteColleagueB(mediator, "同事B");
​
        colleagueA.sendMsg("你好!同事B!");
        colleagueB.sendMsg("你好!同事A!");
    }
}

结果: 同事A发送消息:你好!同事B! 同事B收到来自 同事A 消息:你好!同事B! 同事B发送消息:你好!同事A! 同事A收到来自 同事B 消息:你好!同事A!

二十一、享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。

概要

意图

通过共享对象来减少创建大量相似对象时的内存消耗。。

主要解决的问题

  • 避免因创建大量对象而导致的内存溢出问题。

  • 通过共享对象,提高内存使用效率。

使用场景

  • 当系统中存在大量相似或相同的对象。

  • 对象的创建和销毁成本较高。

  • 对象的状态可以外部化,即对象的部分状态可以独立于对象本身存在。

实现方式

  • 定义享元接口:创建一个享元接口,规定可以共享的状态。

  • 创建具体享元类:实现该接口的具体类,包含内部状态。

  • 使用享元工厂:创建一个工厂类,用于管理享元对象的创建和复用。

关键代码

  • HashMap:使用哈希表存储已经创建的享元对象,以便快速检索。

应用实例

  1. Java中的String对象:字符串常量池中已经存在的字符串会被复用。

  2. 数据库连接池:数据库连接被复用,避免频繁创建和销毁连接。

优点

  • 减少内存消耗:通过共享对象,减少了内存中对象的数量。

  • 提高效率:减少了对象创建的时间,提高了系统效率。

缺点

  • 增加系统复杂度:需要分离内部状态和外部状态,增加了设计和实现的复杂性。

  • 线程安全问题:如果外部状态处理不当,可能会引起线程安全问题。

使用建议

  • 在创建大量相似对象时考虑使用享元模式。

  • 确保享元对象的内部状态是共享的,而外部状态是独立于对象的。

注意事项

  • 状态分离:明确区分内部状态和外部状态,避免混淆。

  • 享元工厂:使用享元工厂来控制对象的创建和复用,确保对象的一致性和完整性。

结构

享元模式包含以下几个核心角色:

  • 享元工厂(Flyweight Factory):

    • 负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。

  • 具体享元(Concrete Flyweight):

    • 实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。

  • 抽象享元(Flyweight):

    • 定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。

  • 客户端(Client):

    • 使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。

实现

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类 Circle。下一步是定义工厂类 ShapeFactory

ShapeFactory 有一个 CircleHashMap,其中键名为 Circle 对象的颜色。无论何时接收到请求,都会创建一个特定颜色的圆。ShapeFactory 检查它的 HashMap 中的 circle 对象,如果找到 Circle 对象,则返回该对象,否则将创建一个存储在 hashmap 中以备后续使用的新对象,并把该对象返回到客户端。

FlyWeightPatternDemo 类使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(red / green / blue/ black / white),以便获取它所需对象的颜色。

package com.example.designmodestudy.flyweight;
​
public interface Shape {
​
    void draw();
}
​
package com.example.designmodestudy.flyweight;
​
import lombok.Data;
​
@Data
public class Circle implements Shape{
​
    private String color;
    private int x;
    private int y;
    private int radius;
​
    public Circle(String color){
        this.color = color;
    }
​
    @Override
    public void draw() {
        System.out.println(this.toString());
    }
}
​
package com.example.designmodestudy.flyweight;
​
import java.util.HashMap;
​
public class ShapeFactory {
​
    private static final HashMap<String ,Circle> circleMap = new HashMap<>();
​
    public static Circle getCircle(String color){
        Circle circle = circleMap.get(color);
        if(circle == null){
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("Creating circle of color:" + color);
        }
​
        return circle;
    }
}
​
package com.example.designmodestudy.flyweight;
​
public class Invoke {
​
    private static final String colors[]={"Red", "Green", "Blue", "White", "Black" };
​
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Circle circle = ShapeFactory.getCircle(getRandomColor());
            circle.setX(getRandomX());
            circle.setY(getRandomY());
            circle.setRadius(100);
            circle.draw();
        }
    }
​
    private static String getRandomColor() {
        return colors[(int) (Math.random() * colors.length)];
    }
​
    private static int getRandomX(){
        return (int) (Math.random()*10);
    }
​
    private static int getRandomY(){
        return (int) (Math.random() * 10);
    }
}

结果: Creating circle of color:Black Circle(color=Black, x=5, y=0, radius=100) Creating circle of color:Red Circle(color=Red, x=2, y=3, radius=100) Creating circle of color:Blue Circle(color=Blue, x=1, y=3, radius=100) Circle(color=Blue, x=6, y=0, radius=100) Circle(color=Black, x=4, y=9, radius=100) Circle(color=Red, x=7, y=8, radius=100) Circle(color=Red, x=5, y=7, radius=100) Creating circle of color:Green Circle(color=Green, x=9, y=0, radius=100) Circle(color=Red, x=8, y=0, radius=100) Circle(color=Black, x=1, y=4, radius=100)

二十二、解释器模式

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。

解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

这种模式被用在 SQL 解析、符号处理引擎等。

介绍

意图

定义一种语言的文法表示,并创建一个解释器,该解释器能够解释该语言中的句子。

主要解决的问题

  • 解释器模式用于构建一个能够解释特定语言或文法的句子的解释器。

使用场景

  • 当某一特定类型的问题频繁出现,并且可以通过一种简单的语言来表达这些问题的实例时。

实现方式

  • 定义文法:明确语言的终结符和非终结符。

  • 构建语法树:根据语言的句子构建对应的语法树结构。

  • 创建环境类:包含解释过程中所需的全局信息,通常是一个HashMap。

关键代码

  • 终结符与非终结符:定义语言的文法结构。

  • 环境类:存储解释过程中需要的外部环境信息。

应用实例

  • 编译器:解释器模式可以用于编译器设计,将源代码解释为目标代码。

  • 正则表达式:解释器模式可以用于解析和执行正则表达式。

  • SQL解析:解释器模式可以用于解析和执行SQL语句。

优点

  • 可扩展性好:容易添加新的解释表达式的方式。

  • 灵活性:可以根据需要轻松扩展或修改文法。

  • 易于实现简单文法:对于简单的语言,实现起来相对容易。

缺点

  1. 使用场景有限:只适用于适合使用解释的简单文法。

  2. 维护困难:对于复杂的文法,维护和扩展变得困难。

  3. 类膨胀:可能会产生很多类,每个文法规则对应一个类。

  4. 递归调用:解释器模式通常使用递归调用,这可能难以理解和跟踪。

使用建议

  • 在需要解释执行语言中的句子时,考虑使用解释器模式。

  • 确保文法简单,以避免系统变得过于复杂。

注意事项

  • 解释器模式在 Java 中可能不是首选,如果遇到适用场景,可以考虑使用如expression4J之类的库来代替。

结构

解释器模式包含以下几个主要角色:

  • 抽象表达式(Abstract Expression):定义了解释器的抽象接口,声明了解释操作的方法,通常是一个抽象类或接口。

  • 终结符表达式(Terminal Expression):实现了抽象表达式接口的终结符表达式类,用于表示语言中的终结符(如变量、常量等),并实现了对应的解释操作。

  • 非终结符表达式(Non-terminal Expression):实现了抽象表达式接口的非终结符表达式类,用于表示语言中的非终结符(如句子、表达式等),并实现了对应的解释操作。

  • 上下文(Context):包含解释器之外的一些全局信息,在解释过程中提供给解释器使用,通常用于存储变量的值、保存解释器的状态等。

  • 客户端(Client):创建并配置具体的解释器对象,并将需要解释的表达式传递给解释器进行解释。

实现

我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpressionAndExpression 用于创建组合式表达式。

InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

解释器模式的 UML 图

package com.example.designmodestudy.interpreter;
​
public interface Expression {
    public boolean interpret(String context);
​
}
​
package com.example.designmodestudy.interpreter;
​
public class TerminalExpression implements Expression{
​
    private String data;
​
    public TerminalExpression(String data){
        this.data = data;
    }
​
    @Override
    public boolean interpret(String context) {
        if(context.contains(data)){
            return true;
        }
        return false;
    }
}
​
package com.example.designmodestudy.interpreter;
​
public class OrExpression implements Expression{
​
    private Expression expression1 = null;
    private Expression expression2 = null;
​
    public OrExpression(Expression expression1, Expression expression2){
        this.expression1 = expression1;
        this.expression2 = expression2;
    }
​
    @Override
    public boolean interpret(String context) {
        return expression1.interpret(context) || expression2.interpret(context);
    }
}
​
package com.example.designmodestudy.interpreter;
​
public class AndExpression implements Expression{
​
    private Expression expression1 = null;
    private Expression expression2 = null;
​
    public AndExpression(Expression expression1, Expression expression2){
        this.expression1 = expression1;
        this.expression2 = expression2;
    }
​
    @Override
    public boolean interpret(String context) {
        return expression1.interpret(context) && expression2.interpret(context);
    }
}
​
package com.example.designmodestudy.interpreter;
​
public class Invoker {
​
    //规则: robert和john是男性
    public static Expression getMaleExpression(){
        Expression robert = new TerminalExpression("robert");
        Expression john = new TerminalExpression("John");
        return new OrExpression(robert, john);
    }
​
    //规则:julie是一个已婚的女性
    public static Expression getMarriedWomanExpression(){
        Expression julie = new TerminalExpression("Julie");
        Expression married = new TerminalExpression("Married");
        return new AndExpression(julie, married);
    }
​
    public static void main(String[] args) {
        Expression maleExpression = getMaleExpression();
        Expression marriedWomanExpression = getMarriedWomanExpression();
​
        String name = "John";
        System.out.println(name + "是男性吗?" + maleExpression.interpret(name));
        name = "Julie";
        System.out.println(name + "是一个结婚的女性吗?" + marriedWomanExpression.interpret("Married " + name));
    }
}

结果: John是男性吗?true Julie是一个结婚的女性吗?true

二十三、访问者模式

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

介绍

意图

旨在将数据结构与在该数据结构上执行的操作分离,从而使得添加新的操作变得更容易,而无需修改数据结构本身。

主要解决的问题

  • 解决在稳定数据结构和易变操作之间的耦合问题,使得操作可以独立于数据结构变化。

使用场景

  • 当需要对一个对象结构中的对象执行多种不同的且不相关的操作时,尤其是这些操作需要避免"污染"对象类本身。

实现方式

  • 定义访问者接口:声明一系列访问方法,一个访问方法对应数据结构中的一个元素类。

  • 创建具体访问者:实现访问者接口,为每个访问方法提供具体实现。

  • 定义元素接口:声明一个接受访问者的方法。

  • 创建具体元素:实现元素接口,每个具体元素类对应数据结构中的一个具体对象。

关键代码

  • 访问者接口:包含访问不同元素的方法。

  • 具体访问者:实现了访问者接口,包含对每个元素类的访问逻辑。

  • 元素接口:包含一个接受访问者的方法。

  • 具体元素:实现了元素接口,提供给访问者访问的入口。

应用实例

  • 做客场景:访问者(如您)访问朋友家,朋友作为元素提供信息,访问者根据信息做出判断。

优点

  • 单一职责原则:访问者模式符合单一职责原则,每个类只负责一项职责。

  • 扩展性:容易为数据结构添加新的操作。

  • 灵活性:访问者可以独立于数据结构变化。

缺点

  • 违反迪米特原则:元素需要向访问者公开其内部信息。

  • 元素类难以变更:元素类需要维持与访问者的兼容。

  • 依赖具体类:访问者模式依赖于具体类而不是接口,违反了依赖倒置原则。

使用建议

  • 当对象结构稳定,但需要在其上定义多种新操作时,考虑使用访问者模式。

  • 当需要避免操作"污染"对象类时,使用访问者模式封装操作。

注意事项

  • 访问者模式可以用于功能统一,如报表生成、用户界面显示、拦截器和过滤器等。

包含的几个主要角色

  • 访问者(Visitor)

    • 定义了访问元素的接口。

  • 具体访问者(Concrete Visitor)

    • 实现访问者接口,提供对每个具体元素类的访问和相应操作。

  • 元素(Element)

    • 定义了一个接受访问者的方法。

  • 具体元素(Concrete Element)

    • 实现元素接口,提供一个accept方法,允许访问者访问并操作。

  • 对象结构(Object Structure)(可选)

    • 定义了如何组装具体元素,如一个组合类。

  • 客户端(Client)(可选)

    • 使用访问者模式对对象结构进行操作。

实现

我们将创建一个定义接受操作的 ComputerPart 接口。KeyboardMouseMonitorComputer 是实现了 ComputerPart 接口的实体类。我们将定义另一个接口 ComputerPartVisitor,它定义了访问者类的操作。Computer 使用实体访问者来执行相应的动作。

VisitorPatternDemo,我们的演示类使用 ComputerComputerPartVisitor 类来演示访问者模式的用法。

访问者模式的 UML 图

package com.example.designmodestudy.visitor;
​
public interface ComputerPart {
    public void accept(ComputerPartVisitor computerPartVisitor);
}
​
package com.example.designmodestudy.visitor;
​
public class Keyboard implements ComputerPart{
    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}
​
package com.example.designmodestudy.visitor;
​
public class Monitor implements ComputerPart{
    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}
​
package com.example.designmodestudy.visitor;
​
public class Mouse implements ComputerPart{
    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}
​
package com.example.designmodestudy.visitor;
​
public class Computer implements ComputerPart{
​
    ComputerPart[] parts;
​
    public Computer(){
        parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()};
    }
​
    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        for (int i = 0; i < parts.length; i++) {
            parts[i].accept(computerPartVisitor);
        }
        computerPartVisitor.visit(this);
    }
}
​
package com.example.designmodestudy.visitor;
​
public interface ComputerPartVisitor {
    public void visit(Computer computer);
    public void visit(Mouse mouse);
    public void visit(Keyboard keyboard);
    public void visit(Monitor monitor);
}
​
package com.example.designmodestudy.visitor;
​
public class ComputerPartDisplayVisitor implements ComputerPartVisitor{
    @Override
    public void visit(Computer computer) {
        System.out.println("显示电脑");
    }
​
    @Override
    public void visit(Mouse mouse) {
        System.out.println("显示鼠标");
    }
​
    @Override
    public void visit(Keyboard keyboard) {
        System.out.println("显示键盘");
    }
​
    @Override
    public void visit(Monitor monitor) {
        System.out.println("显示监控");
    }
}
​
package com.example.designmodestudy.visitor;
​
public class Invoker {
    public static void main(String[] args) {
        ComputerPart computerPart = new Computer();
        computerPart.accept(new ComputerPartDisplayVisitor());
    }
}

结果: 显示鼠标 显示键盘 显示监控 显示电脑

参考文献

1、设计模式 | 菜鸟教程

2、《大话设计模式》书籍


网站公告

今日签到

点亮在社区的每一天
去签到