23种经典设计模式思想与原理

在设计模式领域,我通常将学习者和使用者分为三个层次。初级阶段的学习者对于设计模式的概念和存在可能并不清楚,或者仅仅知道有这么个东西叫做设计模式;中级阶段的学习者具备了对设计模式的基本了解,能够运用设计模式进行代码设计和优化;高级阶段的学习者则是设计模式的熟练掌握者,能够像写"Hello World"一样轻松地运用各种设计模式解决问题。

本篇文章的初衷是帮助大家了解和学习设计模式,特别是针对即将步入社会的大学生和准程序员,了解23种设计模式可以为他们的简历增添亮点。然而,我们并不需要完全掌握这23种设计模式。对于初学者和即将进入社会的大学生们来说,掌握其中的3-6种常用设计模式已经足够,并且可以通过实际项目经验逐步扩展自己的知识。对于那些希望深入了解设计模式的人来说,掌握常用的十多种设计模式将为他们提供广泛的应用场景和解决方案,通过深入学习和实践这些设计模式,他们可以提升自己的设计能力,并在开发过程中更有效地解决问题。

总之,设计模式是软件开发中重要的工具和思维方式,对于程序员的成长和职业发展都有积极的影响。但请记住,学习设计模式不仅仅是为了掌握设计模式本身,更重要的是理解它们的原理和适用场景,并能够将其运用到实际开发中,以提高代码的可维护性、扩展性和复用性。

什么是设计模式

设计模式:Design Pattern,是解决特定问题的一套成功或有效的解决方案。它们提供了在软件设计和开发过程中可重复使用的模板,帮助开发人员解决常见的设计问题并提高代码的可读性、可维护性和可扩展性。

设计模式并不仅仅是代码优化的一种方法,它们更是一种编程思想和方法论,通过提供经过验证的解决方案,帮助开发人员在面对复杂的软件设计问题时做出明智的决策。设计模式通过将问题分解为更小的、可重用的部分,并定义它们之间的关系和交互方式,提供了一种结构化的方法来组织代码。

在实际开发中,我们经常会使用条件判断语句(如if-else)来处理各种场景。然而,当条件判断过多时,代码的可读性、可维护性和可扩展性会降低。设计模式可以在这种情况下发挥作用,通过引入适当的设计模式,如策略模式或状态模式,来优化代码结构并降低耦合度。

设计模式与编程语言是相对独立的,几乎可以应用于任何编程语言。无论您使用哪种编程语言,设计模式都可以被应用。在本文中,我们以Java为主要示例,但设计模式在其他编程语言中同样适用。

需要注意的是,设计模式并非万能解决方案,也不应为了使用而使用。选择适当的设计模式应基于具体问题和需求,并在实践中不断积累经验和理解。熟悉各种设计模式可以扩展开发人员的工具箱,帮助他们更好地应对各种设计挑战。

设计模式的内容

本文一共包括23种设计模式,分为三类:

创建型设计模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式;

结构型设计模式:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式;

行为型设计模式:观察者模式、模版模式、策略模式、责任链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

除了23种设计模式之外,还有6大设计原则:开闭原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、迪米特法则。除此之外,有人认为合成复用原则也应该作为设计原则中的一种,本文也会有讲解。

本文中的内容,由我从《大话设计模式》、《图解设计模式》以及B站《尚硅谷Java设计模式(图解+框架源码剖析)》的视频学习整理得出,有误与不足之处,请大家指出更正。对于时间比较充裕,或者觉得文字描述不够直观的朋友,可以观看B站的视频学习。

设计原则

开闭原则

开闭原则:设计原则中最基础、最重要、最有用的原则,对扩展开放,对修改关闭。如何理解“对扩展开放,对修改关闭”,我的观点是对于一个新的功能,应该是在现有的代码上进入扩展,而不是修改原有的代码。

举一个我工作中遇到的例子:我在工作中,写过一个文件适配的功能,根据不同类型的文件,解析内容。由于一开始需求比较简单,只需要适配CSV和EXCEL文件,所以我直接在FileContent类中,用了if-else判断语句,所有的处理逻辑全部放在判断语句内。理论上,当我写好CSV和EXCEL文件的适配代码后,这部分代码就应该被封装好,不再拥有被外部修改的可能。

但是,后来由于使用的人员较多,文件也从最开始的CSV和EXCEL文件,演变成WORD、TXT、JSON等多种文件适配。这时候每当适配一个新文件时,都需要在原先写好的代码中,增加一条新的if判断语句,实现相应的逻辑。这样咋看上去没有问题,其实存在很大的风险,这就意味着,当有人修改FileContent类代码时,同样可以修改已经写好的CSV和EXCEL文件的适配代码,这就违背了开闭原则。具体的优化方案在后面具体的设计模式中会有提及。

单一职责原则

单一职责原则:对类和模块来说,即一个类或一个模块只负责一项职责。比如类A负责了两个不同的职责:职责1和职责2,当职责1变更时,可能会造成职责2执行错误,因此需要将类A细分为A1、A2。

这一原则让我想到了数据库中的第三范式,类或模块中不能存在间接依赖。可以看下面的例子:

public class User {
 private int id;
 private String name; // 姓名
 private int age; // 年龄
 private String email;  // 邮箱
 private String telephone;  // 电话
 private String province;  // 省份
 private String city;  // 城市
 private String region;  // 小区
}

上述例子定义了一个User类,这么定义明细违背了单一职责原则,它的属性信息高度依赖了province、city、region,这么做有什么危害呢?例如多个User实例,其province、city、region属性完全相同,即这些User实例属于一个小区,假设小区改名了,那么与之相关的所有对象实例都要全部修改一遍。因此需要将User类进一步拆分为User和Address。

接口隔离原则

接口隔离原则:客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖,应该建立在最小接口上。举个我们生活中的例子:比如我们想设计一个鸟类,它的行为应该有飞翔、行走、捕食等,针对这一描述设计相应的接口:

public interface Birds {
    public void flying();
    public void walking();
    public void eating();
}

上述接口看起来是没问题的,但如果要根据上述接口生成一个企鹅类,就有问题了,总所周知,企鹅不会飞;而且企鹅还会游泳,上述接口就不适用了,因为接口中根本没有游泳方法。重新设计后的接口如下所示:

public interface Birds {
    public void walking();
    public void eating();
}

public interface FlyingBirds {
    public void flying();
}

public interface SwimmingBirds {
    public void swimming();
}

这时想生成一个企鹅类就很简单了:

public class Penguin implements Birds, SwimmingBirds{
    @Override
    public void walking() {
    }
    @Override
    public void eating() {
    }
    @Override
    public void swimming() {
    }
}

依赖倒转原则

依赖倒转原则:也称依赖倒置原则,其中心思想是面向接口编程。抽象不应该依赖细节,细节应该依赖于抽象。高层模块不应该依赖底层模块,两者应该依赖抽象。

里氏替换原则

里氏替换原则:所有引用基类的地方都必须能透明地使用其子类对象,即子类型能替换替换掉父类型。就这要求子类尽量不要重写父类方法,否则子类在替换父类时,就可能改变原有程序的逻辑,破坏原有程序的正确性。仍然以企鹅为例,当Penguin类直接继承Birds类,则Penguin类的对象就有flying()方法了,这显然是不正确的。

public class Birds {
    public void flying(){
        
    }
    public void walking(){
        
    }
    public void eating(){
        
    }
}
public class Penguin extends Birds{

}
public class Test {
    public static void main(String[] args) {
        Penguin penguin = new Penguin();
        penguin.flying();  // 企鹅会飞
    }
}

迪米特法则

迪米特法则:强调类直接的低耦合、高内聚。一个类或对象,应该对其他类或对象保持最少的了解,即一个类或对象,对自己依赖的类知道的越少越好。在类的结构设计上,每一个类都应当降低成员的访问权限,将逻辑封装在类的内部,只对外提供public方法。这样当类被修改时,不会对有依赖关系的类造成影响。

合成复用原则

合成复用原则:尽量使用组合/聚合的方式,而不使用继承。

设计模式

单例模式(常用)

单例模式:一个类只能创建一个实例。单例模式个人感觉是设计模式中最基础的一个,也是面试中最容易被问到的一个。

饿汉式:写法简单,在类装载时完成对象实例化,避免了线程同步问题。但也因为类装载时就完成实例化,不知道懒加载(lazy loading),会造成内存浪费。

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){};
    public static Singleton getInstance(){
        return singleton;
    }
}

懒汉式:在需要时才创建对象,有两种写法,一种是常规写法,一种是同步代码块写法,解决了懒加载的问题。但都由于synchronized关键字的存在,多线程访问时需要保证线程安全问题,效率低。当然,如果是单线程下,可以不需要synchronized,不用考虑线程安全的问题。

// 常规写法
public class Singleton {
    private static Singleton singleton;
    private Singleton(){};
    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

// 同步代码块 
public class Singleton {
    private static Singleton singleton;
    private Singleton(){};
    public static Singleton getInstance(){
        if(singleton==null){
            synchronized (Singleton.class){
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

双重检查:针对懒汉式两种写法的改进,既解决了线程安全问题,又解决了懒加载的问题。使用两层if判断,外层if保证效率,内层if保证安全性。

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

静态内部类:外部类的加载,不会导致内部类的加载,可以实现懒加载;同时,由于类的静态属性只会在第一次加载时初始化,因此是线程安全的。

public class Singleton {
    private Singleton(){}
    private static class SingleHolder{
        private static final Singleton singleton = new Singleton();
    }
    public static Singleton getInstance(){
        return SingleHolder.singleton;
    }
}

枚举:定义一个枚举类,内部只有一个属性,访问时直接使用Singleton.SINGLETON。

public enum Singleton{
    SINGLETON;
}

到此单例模式设计介绍完成,最后留个小问题,如何实现双例或三例设计模式呢?

工厂模式(常用)

工厂模式又分为简单工厂模式和工厂方法模式,除此之外,还有一个抽象工厂模式。这三个模式由于名称和设计思路相似,有人将这三个都统称为工厂模式。但个人觉得抽象还是特殊一点,原理较为复杂,而且不常用,所以抽象工厂模式仍作为一个独立的设计模式,在下一节中讲解。

简单工厂模式:由一个工厂对象决定创建处哪一种产品类的实例,即:定义了一个创建对象的工厂,并由这个工厂类封装实例化对象的行为。

前文在开闭原则一节中提到,我直接在FileContent类中,用了if-else判断语句来适配CSV和EXCEL文件,以下是我最开始的代码:

public class FileContent {
    public String fileContentToString(File file){
        String fileName = file.getName();
        String result = null;
        if(fileName.endsWith(".xls")){
            // xls文件处理逻辑(省略)
        } else if (fileName.endsWith(".xlsx")) {
            // xlsx文件处理逻辑(省略)
        } else if (fileName.endsWith(".csv")) {
            // csv文件处理逻辑(省略)
        }
        return result;
    }
}

后来,由于文件类型增加,该方式并不利于代码维护与修改,所以我对这部分代码做了优化,将FileContent类作为一个父类,新增了XlsFileContent、XlsxFileContent、CsvFileContent等子类去继承它,并新增了一个FileFactory判断该生成哪一个实例,代码如下所示:

public class FileContent {
    public String fileContentToString(File file){
        return null;
    }
}

public class XlsFileContent extends FileContent{
    @Override
    public String fileContentToString(File file){
        String result = null;
        // xls文件处理逻辑(省略)
        return result;
    }
}

public class XlsxFileContent extends FileContent{
    @Override
    public String fileContentToString(File file){
        String result = null;
        // xlsx文件处理逻辑(省略)
        return result;
    }
}

public class CsvFileContent extends FileContent{
    @Override
    public String fileContentToString(File file){
        String result = null;
        // csv文件处理逻辑(省略)
        return result;
    }
}

public class FileFactory {
    public static FileContent fileContentToString(File file){
        String fileName = file.getName();
        FileContent fileContent = null;
        if(fileName.endsWith(".xls")){
            fileContent = new XlsFileContent();
        } else if (fileName.endsWith(".xlsx")) {
            fileContent = new XlsxFileContent();
        } else if (fileName.endsWith(".csv")) {
            fileContent = new CsvFileContent();
        }
        return fileContent;
    }
}

如此一来,当我需要新增WORD文件适配时,我就只需要新增一个DocFileContent去继承FileContent,并重写fileContentToString方法;在FileFactory中只用新增一个else分支。这样做并不会对XlsFileContent等类的实现产生任何影响。

工厂方法模式:定义一个创建对象的抽象方法,由子类决定要实例化的类,即工厂类本身不创建实例,而是将实例化推迟到子类创建。

还是以上述代码为例:

public interface IFileFactory {
    FileContent createFileContent();
}

public class XlsFileFactory implements IFileFactory{
    @Override
    public FileContent createFileContent() {
        return new XlsFileContent();
    }
}

public class XlsxFileFactory implements IFileFactory{
    @Override
    public FileContent createFileContent() {
        return new XlsxFileContent();
    }
}

public class CsvFileFactory implements IFileFactory{
    @Override
    public FileContent createFileContent() {
        return new CsvFileContent();
    }
}

public class FileFactoryHandle {
    public static FileContent fileContentToString(File file){
        String fileName = file.getName();
        IFileFactory fileFactory = null;
        if(fileName.endsWith(".xls")){
            fileFactory = new XlsFileFactory();
        } else if (fileName.endsWith(".xlsx")) {
            fileFactory = new XlsxFileFactory();
        } else if (fileName.endsWith(".csv")) {
            fileFactory = new XlsFileFactory();
        }
        FileContent fileContent = fileFactory.createFileContent();
        return fileContent;
    }
}

上述代码中可以看到,和简单工厂方法相比,原本在if-else语句中直接创建FileContent对象,改为创建FileFactory对象,再由FileFactory对象去创建相应的FileContent对象。

抽象工厂模式

抽象工厂模式:定义一个接口interface用于创建相关或由依赖关系的对象簇(一系列相关对象),而无需指明具体的类。个人理解,抽象工厂模式和工厂方法模式类似,唯一不同的是在接口中,工厂方法模式只能生成一种类实例,而抽象工厂模式可以生成多个类示例,如以下代码:

public interface IFileFactory {
    FileContent createFileContent();
    EamilContent createEmailContent();
    // 此处可以扩展新的类
}

原型模式

原型模式:用原型实例指定创建对象的种类,并通过拷贝这些原型,创建新的对象,即通过一个对象,复制创建另一个对象(简而言之,就是克隆)。它的实现方式有两种:浅拷贝和深拷贝。

浅拷贝:浅拷贝只会拷贝基本数据类型。如果需要拷贝的数据是引用数据类型,比如数组,那么拷贝时,只会拷贝引用数据的地址值。实际上,浅拷贝生成的对象和原型对象都是指向了同一实例,当一个对象的成员变量被修改时,另一个对象的成员变量也会随之更改。

深拷贝:不同于浅拷贝,深拷贝不仅会复制引用数据的地址值,还会复制数据本身,因此,深拷贝得到的是一个完全独立的对象。当一个对象的成员变量被修改时,另一个对象的成员变量并不会随之更改,即原型对象和拷贝对象完成独立。

本文以克隆羊为例,分别实现克隆羊的浅拷贝与深拷贝:

克隆羊的浅拷贝:

// 定义一个Color类
public class Color {
    private String color;
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Color(String color){
        this.color = color;
    }
    public String toString(){
        return "color=" + color;
    }
}

// Sheep类:通过实现Cloneable接口,重写clone方法,来达到浅拷贝的目的
public class Sheep implements  Cloneable{
    private String name;
    private int age;
    private Color color;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Color getColor() {
        return color;
    }
    public void setColor(Color color) {
        this.color = color;
    }
    public Sheep(String name, int age, Color color){
        this.name = name;
        this.age = age;
        this.color = color;
    }
    @Override
    public String toString(){
        return "[name=" + name + "; age=" + age + "; " + color + "]";
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        return sheep;
    }
}

public class Main{
    public static void main(String[] args) {
        Color color = new Color("灰色");
        Sheep sheep = new Sheep("多莉", 6, color);
        try {
            Sheep sheep2 = (Sheep)sheep.clone();
            Sheep sheep3 = (Sheep)sheep.clone();
            System.out.println("sheep:" + sheep);
            System.out.println("sheep2:" + sheep2);
            System.out.println("sheep3:" + sheep3);
            // 修改sheep2克隆羊的brother属性
            color.setColor("白色");
            System.out.println("------------修改后的对象属性-----------------");
            System.out.println("sheep:" + sheep);
            System.out.println("sheep2:" + sheep2);
            System.out.println("sheep3:" + sheep3);
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

上述代码的打印结果如下所示:

sheep:[name=多莉; age=6; color=灰色]
sheep2:[name=多莉; age=6; color=灰色]
sheep3:[name=多莉; age=6; color=灰色]
------------修改后的对象属性-----------------
sheep:[name=多莉; age=6; color=白色]
sheep2:[name=多莉; age=6; color=白色]
sheep3:[name=多莉; age=6; color=白色]

从打印结果可以看出,当color对象发生变化时,所有sheep对象的color信息都会随之发生改变,这就是浅拷贝。那么要如何实现深拷贝呢?其实方法也很简单,我们只需要在重写的clone方法中将需要深拷贝的Color属性单独进行一次拷贝即可,该拷贝即为深拷贝:

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep = (Sheep)super.clone();
        sheep.color = new Color(this.color.toString());  // 深拷贝
        return sheep;
    }

除此之外,也可以将原对象类型Sheep及其成员对象类型Color,都实现Serializable接口,也可以达到深拷贝的目的。

建造者模式(常用)

建造者模式:也称生成器模式,将一个复杂对象的构建与它的表示层分离,使得同样的构建过程可以创建不同的表示。即用户只需要指定需要的类型,而不需要知道具体构建的细节和过程。建造者模式中主要包括3类角色:

Director:指挥者,创建一个使用Builder接口的对象。其作用有两个:一是隔离客户端与对象直接的生产过程;二是负责控制对象的生产过程。

Builder:抽象建造者,创建对象的各个部件指定的接口/抽象类,控制建造流程。

XXXBuilder:具体的建造类。

说起建造者,很多人喜欢用建房子打比方,我没建过房子。但我现在正好遇上家里装修,正好以此为例,搭建一个建造者模式:装修需要测量房屋面积,地板/瓷砖选材。

// 材料类
public class Materials {
    private int area; // 面积
    private String floor; // 地板品牌
    private String tile; // 瓷砖品牌
    public int getArea() {
        return area;
    }
    public void setArea(int area) {
        this.area = area;
    }
    public String getFloor() {
        return floor;
    }
    public void setFloor(String floor) {
        this.floor = floor;
    }
    public String getTile() {
        return tile;
    }
    public void setTile(String tile) {
        this.tile = tile;
    }
    public String toString(){
        return "[面积=" + area + " ;地板品牌=" + floor + " ;瓷砖品牌=" + tile + "]";
    }
}
// Builder类,最后返回Materials
public interface Builder {
    public void measuringArea(); // 测量面积
    public void chooseFloor(); // 地板选材
    public void chooseTile(); // 瓷砖选材
    public Materials getResult(); // 返回Materials结果
}
// 具体的Builder类
public class CommonMaterialsBuilder implements Builder{
    private Materials materials = new Materials();
    @Override
    public void measuringArea() {
        materials.setArea(70);
    }
    @Override
    public void chooseFloor() {
        materials.setFloor("大自然");
    }
    @Override
    public void chooseTile() {
        materials.setTile("马可波罗");
    }
    @Override
    public Materials getResult() {
        return materials;
    }
}
// 指挥者
public class Director {
    private Builder builder;
    public Director(Builder builder){  // 构造器传入Builder
        this.builder = builder;
    }
    // public void setBuilder(Builder builder) {  // setter方法传入Builder
        // this.builder = builder;
    // }
    // 装修材料确认
    public Materials materialsConstruct(){
        builder.measuringArea();
        builder.chooseFloor();
        builder.chooseTile();
        return builder.getResult();
    }
}
// Main调用
public class Main {
    public static void main(String[] args) {
        CommonMaterialsBuilder commonMaterialsBuilder = new CommonMaterialsBuilder();
        Director director = new Director(commonMaterialsBuilder);
        Materials materials = director.materialsConstruct();
        System.out.println(materials.toString());
    }
}

上述示例,只构建了一个CommonMaterialsBuilder类,如果需要构建其他Builder类,只需要新增一个对应的类实现Builder接口即可。

适配器模式(常用)

适配器模式:将一个类的接口转换为客户希望的另一个接口,让原本接口不匹配、不兼容而不能工作的两个类,可以协同工作。适配器模式主要分为三种:类适配器模式、接口适配器模式、接口适配器模式。适配器模式主要包括三类角色: 所需要的目标类Target、被适配的类或对象Adaptee、适配器Adapter。

生活中,我们经常需要给手机充电,这其实就是一个适配的过程。家用电压220V就是被适配者Adaptee,手机充电需要电压5V就是目标类Target,充电器就是适配器Adapter。下面将通过适配器的三种模式,分别用代码实现这一例子。

类适配器模式

// 适配接口 target
public interface Voltage5V {
    public int output5V();
}
// 被适配的类 adaptee
public class Voltage220V {
    // 原电压220V
    public int output220(){
        int sourceVoltage = 220;
        System.out.println("原电压:" + sourceVoltage + "V");
        return sourceVoltage;
    }
}
// 电压适配器 adapter
public class VoltageAdepter extends Voltage220V implements Voltage5V{
    @Override
    public int output5V() {
        // 获取220V的电压
        int sourceVoltage = output220();
        int targetVoltage = sourceVoltage/44;  // 电压转伏
        return targetVoltage;
    }
}
public class Phone {
    // 充电
    public void charging(Voltage5V voltage5V){
        if(voltage5V.output5V()==5){
            System.out.println("电压为5V,可以充电");
        }else {
            System.out.println("电压不匹配");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("--------------类适配器模式-----------------");
        Phone phone = new Phone();
        phone.charging(new VoltageAdepter());
    }
}

上述代码就是一个类适配器模式的例子,从示例中我们可以看出:

public class VoltageAdepter extends Voltage220V implements Voltage5V

适配器VoltageAdepter必须继承Voltage220V 类,同时实现Voltage5V接口,这是由于Java单继承的机制导致的,所以目标类Voltage5V必须是一个接口,有一定的局限性。同时VoltageAdepter 继承了Voltage220V 类,会导致Voltage220V 类中所有的方法都被暴露出来,会增加一定的使用成本。

对象适配器模式

为了解决类适配器中继承的问题,对继承的逻辑做了调整,只需要修改Adepter类,不再是继承Adeptee,而是持有Adeptee的实体对象。同时,根据“合成复用原则”,也推荐在系统中尽量使用关联关系来替代继承关系。具体代码如下所示:

// 电压适配器 adapter
public class VoltageAdepter implements Voltage5V {
    private Voltage220V voltage220V;
    // 通过构造器,传入Voltage220V实例
    public VoltageAdepter(Voltage220V voltage220V){
        this.voltage220V = voltage220V;
    }
    @Override
    public int output5V() {
        int targetVoltage = 0;
        if(voltage220V!=null){
            int sourceVoltage220V = voltage220V.output220(); // 获取220V电压
            targetVoltage = sourceVoltage220V/44;
        }
        return targetVoltage;
    }
}

由以上代码可以看出,对象适配器和类适配器其实是同一种思想,只是实现方式不同,其使用成本更低、更灵活。

接口适配器模式

也称为缺省适配器模式,当不需要全部实现接口提供的方法时,可以设计一个抽象类实现接口,并为该接口中的每个方法提供一个默认的实现(空方法)。随后,该抽象类的子类,就可以有选择的重写父类中的某些方法来实现需求。接口适配器模式,适用于一个接口不想使用其他方法的情况。(Mybatis Plus就使用了接口适配器模式。)还是以电压为例,只是现在Voltage5V类中多了一个断电保护的方法protect:

public interface Voltage5V {
    public int output5V(); // 要使用的方法
    public void protect(); // 其他方法
}
// 抽象类 实现Voltage5V
public abstract class AbsVoltageAdepter implements Voltage5V{
    @Override
    public int output5V() {
        return 0;
    }
    @Override
    public void protect() {

    }
}
// 电压适配器 adapter
public class VoltageAdepter extends AbsVoltageAdepter {
    private Voltage220V voltage220V;
    // 通过构造器,传入Voltage220V实例
    public VoltageAdepter(Voltage220V voltage220V){
        this.voltage220V = voltage220V;
    }
    @Override
    public int output5V() {
        int targetVoltage = 0;
        if(voltage220V!=null){
            int sourceVoltage220V = voltage220V.output220(); // 获取220V电压
            targetVoltage = sourceVoltage220V/44;
        }
        return targetVoltage;
    }
}

桥接模式(常用)

桥接模式:用于将抽象和实现分离,并使它们能够独立地变化。桥接模式通过创建抽象类和实现类之间的桥接,使它们可以独立地进行变化,而不会相互影响,它基于接口隔离原则。其目的是解决以下两个问题场景:

一、类的功能和实现的多维度变化:当一个类具有多个变化维度时,桥接模式可以将这些维度分离,使它们能够独立地进行扩展和变化。例如,考虑一个图形绘制应用程序,其中有多种形状(如圆形、矩形)和多种颜色(如红色、蓝色)可供选择。使用桥接模式,可以将形状和颜色作为两个独立的维度进行设计,并通过桥接将它们连接起来。这样,如果需要添加新的形状或颜色,只需扩展相应的维度,而不会影响到其他维度。

二、抽象和实现的解耦:桥接模式可以将抽象和实现分离,使它们可以独立地变化。这对于系统的灵活性和可扩展性非常重要。通过桥接模式,可以在不影响客户端代码的情况下,独立地修改抽象和实现的部分。这种解耦有助于管理复杂系统,并支持系统的演化和维护。

桥接模式主要包括4类角色:

Implementor:行为实现类的接口;

ConcreteImplementor:Implementor的子类,行为的具体实现类;

Abstraction:抽象类,维护了Implementor及其子类,二者是聚合关系,充当桥接类;

RefinedAbstraction:Abstraction抽象类的子类。

这里我举一个手机的例子来实现该模式:我们想要设计一个手机,分为Android和iOS两种系统,每种系统的手机又对应开机和关机功能:

// 手机父类
public class Phone {
    private PhoneSys phoneSys;
}
// 操作系统父类
public class PhoneSys {
    public void run(){

    }
}
public class Android extends PhoneSys{
    @Override
    public void run(){
        System.out.println("安卓系统运行了");
    }
    public void open()
    }
}
public class HuaWei extends Android{
    public void open(){
        System.out.println("华为手机开机了");
    }
    public void close(){
        System.out.println("华为手机关机了");
    }
}

以上代码,当我们使用了多层继承,类与类之间高度耦合在一起,桥接模式设计如下:

// 操作系统接口
public interface PhoneSys {
    public void run();
}
public class Android implements PhoneSys{
    @Override
    public void run() {
        System.out.println("安卓系统运行了");
    }
}
// 手机类 组合操作系统
public class Phone {
    private PhoneSys phoneSys;
    public Phone(PhoneSys phoneSys){
        this.phoneSys = phoneSys;
    }
    protected void run(){
        this.phoneSys.run();
    }
}
public class HuaWei extends Phone{
    public HuaWei(PhoneSys phoneSys){  // 调用父类的构造器
        super(phoneSys);
    }
    public void run(){
        super.run();
        System.out.println("华为手机开机了");
    }
}

装饰器模式

装饰器模式:在不改变已有对象的情况下,动态地向对象添加新的行为和功能。装饰器模式通过将对象包装在一个装饰器类中,从而在运行时动态地修改其行为。

装饰器模式主要包括4类角色:

Component:抽象类,定义一个抽象接口,用于被具体组件和装饰器实现。它是被装饰的对象的基础接口。

ConcreteComponent:实现抽象接口Component的具体类。它是被装饰的对象,也是装饰器所包装的初始对象。

Decorator:抽象装饰器,继承自抽象接口Component,给具体组件增加额外的行为。抽象装饰器通常是一个抽象类,它可以包含一些默认实现。

ConcreteDecorator:继承自抽象装饰器Decorator,实现具体的装饰行为。具体装饰器可以在装饰对象前后进行一些额外的操作,以实现对被装饰对象的功能扩展。

这里我以人穿衣为例,来实现该模式的代码:

// Component
public interface Person {
    public void show();
}
// ConcreteComponent
public class ConcretePerson implements Person{
    private String name;
    public ConcretePerson(String name) {
        this.name = name;
    }
    @Override
    public void show() {
        System.out.println(name + "准备穿衣服:");
    }
}
// 服装类Decorator
public class FineryDecorator implements Person{
    protected Person person;

    public FineryDecorator(Person person) {
        this.person = person;
    }
    @Override
    public void show(){
        person.show();
    }
}
// 具体服装类TShirts ConcreteDecoratorA
public class TShirts extends FineryDecorator {
    public TShirts(Person person) {
        super(person);
    }
    @Override
    public void show(){
        super.show();
        System.out.println("穿上T恤");
    }
}
// 具体服装类Pants ConcreteDecoratorB
public class Pants extends FineryDecorator {
    public Pants(Person person) {
        super(person);
    }
    @Override
    public void show(){
        super.show();
        System.out.println("穿上裤子");
    }
}
public class Main {
    public static void main(String[] args) {
        Person person = new ConcretePerson("张三");
        person = new TShirts(person);
        person = new Pants(person);
        person.show();
    }
}

从上述代码中可以看出,如果我们想穿鞋,只需要增加一个Shoes类继承FineryDecorator即可。当然,装饰器模式也存在几个问题:

  • 需要注意装饰器的顺序,因为装饰器是按照一定的顺序进行包装的,比如得先穿外套再装内衣。

  • 过多的装饰器层级可能会导致代码复杂性增加。

  • 装饰器模式增加了许多小对象,可能会增加内存消耗。

组合模式(常用)

组合模式:又称为部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得客户对单个对象和组合对象的使用具有一致性,即组合模式可以让客户以一致的方式处理单个对象和组合对象。当我们要处理的对象可以生成一颗树形结构,且我们要对树上的节点和叶节点进行操作时,就可以考虑使用组合模式了。

组合模式主要包括3类角色:

Component:组合中的抽象父类或接口,声明了组合对象和叶对象的通用操作方法。

Leaf:组合中的叶节点,它没有子节点,实现了组件的操作方法。

Composite:是组合中的非叶子节点,用于存储子构件。实现Component的相关方法,并且提供了管理子构件的方法。

这里我以公司结构为例,来实现该模式的代码:

// Component
public abstract class CompanyComponent {
    private String name; // 公司名称
    private String des; // 描述
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDes() {
        return des;
    }
    public void setDes(String des) {
        this.des = des;
    }
    public CompanyComponent(String name, String des){
        this.name = name;
        this.des = des;
    }
    public void add(CompanyComponent companyComponent){  // 不声明为abstract,是因为叶子节点不需要实现该方法
        throw new UnsupportedOperationException();  // 不支持操作异常
    }
    public void remove(CompanyComponent companyComponent){  // 不声明为abstract,是因为叶子节点不需要实现该方法
        throw new UnsupportedOperationException();  // 不支持操作异常
    }
    protected abstract void print();
}
// 具体公司类 Composite
public class Company extends CompanyComponent{
    List<CompanyComponent> companyComponentList = new ArrayList<>();

    public Company(String name, String des) {
        super(name, des);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDes() {
        return super.getDes();
    }
    @Override
    public void add(CompanyComponent companyComponent){
        companyComponentList.add(companyComponent);
    }
    @Override
    public void remove(CompanyComponent companyComponent){
        companyComponentList.remove(companyComponent);
    }
    @Override
    protected void print() {
        System.out.println("-------------" + getName() + "--------------------");
        for(CompanyComponent companyComponent: companyComponentList){
            companyComponent.print();
        }
    }
}
// 具体子公司类 Composite
public class SubCompany extends CompanyComponent{
    List<CompanyComponent> companyComponentList = new ArrayList<>();

    public SubCompany(String name, String des) {
        super(name, des);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDes() {
        return super.getDes();
    }
    @Override
    public void add(CompanyComponent companyComponent){
        // 实际业务中,SubCompany和Company的add逻辑可能不一样
        companyComponentList.add(companyComponent);
    }
    @Override
    public void remove(CompanyComponent companyComponent){
        companyComponentList.remove(companyComponent);
    }
    @Override
    protected void print() {
        System.out.println("-------------" + getName() + "--------------------");
        for(CompanyComponent companyComponent: companyComponentList){
            companyComponent.print();
        }
    }
}
// 部门类 Leaf
public class Department extends CompanyComponent{
    public Department(String name, String des) {
        super(name, des);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDes() {
        return super.getDes();
    }
    @Override
    protected void print() {
        System.out.println(getName());
    }
}
public class Main {
    public static void main(String[] args) {
        // 从大到小创建对象
        // 公司
        CompanyComponent component = new Company("总公司", "总公司,位于上海");
        // 子公司
        CompanyComponent hbComponent = new SubCompany("湖北分公司", "湖北分公司,位于湖北武汉");
        CompanyComponent hnComponent = new SubCompany("湖南分公司", "湖北分公司,位于湖南长沙");
        // 部门
        hbComponent.add(new Department("技术部", "技术部"));
        hbComponent.add(new Department("人力部", "人力部"));
        hnComponent.add(new Department("技术研发部", "技术研发部"));
        hnComponent.add(new Department("人力资源部", "人力资源部"));
        // 将子公司添加到总公司
        component.add(hbComponent);
        component.add(hnComponent);
        component.print();
    }
}

外观模式

外观模式:也称过程模式、门面模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。调用端只用和这个接口产出调用,而无需关心这个子系统的内部细节。外观模式就是用来解决多个负责接口带来的使用困难,起到简化用户操作的作用。

外观模式主要包括2类角色:

Facade:为调用端提供统一的调用接口,用于访问子系统中的一组接口。它知道哪些子系统类负责处理请求,将调用端的请求转发给适当的子系统对象。

SubSystem:实现了子系统的功能,处理Facade接口指派的任务,它是功能的实际提供者和实现者。

这里我以家庭影院来实现该代码:包括屏幕升降,投影仪开关机,音响开关和音量调节,其中屏幕、投影仪、音响等,全都设计为单例模式:

// 屏幕类
public class Screen {
    private static Screen instance;
    private Screen() {}
    public static Screen getInstance() {
        if (instance == null) {
            instance = new Screen();
        }
        return instance;
    }
    public void up() {
        System.out.println("屏幕升起");
    }
    public void down() {
        System.out.println("屏幕下降");
    }
}
//投影仪类
public class Projector {
    private static Projector instance;
    private Projector() {}
    public static Projector getInstance() {
        if (instance == null) {
            instance = new Projector();
        }
        return instance;
    }
    public void on() {
        System.out.println("投影仪开启");
    }
    public void off() {
        System.out.println("投影仪关闭");
    }
}
// 音响类
public class AudioSystem {
    private static AudioSystem instance;
    private int volume;
    private AudioSystem() {}
    public static AudioSystem getInstance() {
        if (instance == null) {
            instance = new AudioSystem();
        }
        return instance;
    }
    public void on() {
        System.out.println("音响开启");
    }
    public void off() {
        System.out.println("音响关闭");
    }
    public void setVolume(int volume) {
        this.volume = volume;
        System.out.println("音量调节为:" + volume);
    }
}
// 外观类
public class HomeTheaterFacade {
    private Screen screen;
    private Projector projector;
    private AudioSystem audioSystem;
    private static HomeTheaterFacade instance;
    private HomeTheaterFacade() {
        screen = Screen.getInstance();
        projector = Projector.getInstance();
        audioSystem = AudioSystem.getInstance();
    }
    public static HomeTheaterFacade getInstance() {
        if (instance == null) {
            instance = new HomeTheaterFacade();
        }
        return instance;
    }
    public void watchMovie() {
        System.out.println("准备观看电影...");
        screen.up();
        projector.on();
        audioSystem.on();
        audioSystem.setVolume(70);
    }
    public void endMovie() {
        System.out.println("结束电影...");
        screen.down();
        projector.off();
        audioSystem.off();
    }
}
public class Main {
    public static void main(String[] args) {
        HomeTheaterFacade homeTheater = HomeTheaterFacade.getInstance();
        homeTheater.watchMovie();
        homeTheater.endMovie();
    }
}

:不能过多的或者不合理的使用外观模式,是否要使用外观模式,要以让系统有层次、利于维护为目的。

享元模式

享元模式:旨在有效地支持大量细粒度对象的共享,通过共享对象来减少内存使用和提高性能。常用于系统底层开发,比如数据库连接池、缓存池、String常量池等。

享元模式主要包括2类角色:

Flyweight:所有具体享元类的父类或接口,定义了对象内部状态和外部状态,这些状态在对象创建时确定,并且在对象的整个生命周期内保持不变。

FlyweightFactory:享元工厂,用来创建并管理Flyweight,维护了一个对象池或缓存,用于存储已经创建的享元对象。在获取享元对象时,首先检查对象池中是否存在对应的享元对象,如果存在则直接返回,否则创建新的享元对象并添加到对象池中。享元工厂可以确保系统中只有一个共享对象的实例,避免创建过多的对象,节省了内存开销。

享元模式中提到了两点要求:细粒度和共享对象。这里涉及到了内部状态和外部状态:

内部状态:对象共享的部分,它存储在享元对象内部,不会随着外部环境的变化而改变。

外部状态:对象依赖的一个标记,使用享元对象时通过参数传递进来,并且会随着外部环境的变化而改变。

这里我以书中的网站共享为例:不用使用者对应不同类型的网站类型:

// FlyweightFactory
public class WebSiteFactory {
    // 集合 充当池的作用
    private HashMap<String , ConcreteWebsite> pool = new HashMap<>();
    // 根据网站类型,返回一个网站,如果没有则新建并放入池中
    public WebSite getWebSiteType(String type){
        if(!pool.containsKey(type)){
            pool.put(type, new ConcreteWebsite(type));
        }
        return (WebSite) pool.get(type);
    }
    // 获取网站类型的总数(池中有多少网站类型)
    public int getWebSiteCount(){
        return pool.size();
    }
}
// Flyweight
public abstract class WebSite {
    public abstract void use(User user);
}
// 具体的网站
public class ConcreteWebsite extends WebSite{
    private String type = ""; // 网站类型
    public ConcreteWebsite(String type) {
        this.type = type;
    }
    @Override
    public void use(User user) {
        System.out.println("网站的发布形式为:" + type + "在使用中,使用者为:" +     
        user.getName());
    }
}
public class User {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public User(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) {
        WebSiteFactory webSiteFactory = new WebSiteFactory();
        WebSite webSite1 = webSiteFactory.getWebSiteType("新闻");
        webSite1.use(new User("张三"));
        WebSite webSite2 = webSiteFactory.getWebSiteType("博客");
        webSite2.use(new User("李四"));
        WebSite webSite3 = webSiteFactory.getWebSiteType("博客");
        webSite3.use(new User("王五"));
        System.out.println("网站分类:" + webSiteFactory.getWebSiteCount());
    }
}

代理模式(常用)

代理模式:为其他对象提供一种代理,以控制对这个对象的访问,即提供了一种间接访问对象的方式。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。代理模式主要有三种:静态代理、动态代理(JDK代理、接口代理)和Cglib代理(在内存中动态的创建对象,而不需要实现接口)。

代理模式主要包括3类角色:

Subject:抽象对象接口,定义了目标对象和代理对象共同实现的接口,通过该接口可以访问目标对象。

RealSubject:目标对象,定义了代理对象所代表的真实对象,是最终需要被访问的对象。

Proxy:代理对象,实现了抽象对象接口,包含一个指向真实对象的引用,可以在访问真实对象前后进行一些额外的操作。

在我上大学的时候,购票渠道并不像现在这样丰富,每次回家都得亲自去火车站排队抢票。后来我发现学校门口有一家小商店专门帮忙购票,从那时起就让商店老板代为购票。这里就以购票为例,实现代理模式。

静态代理

// Subject
public interface TicketService {
    public void buyTicket(String destination);  // 买票
    public int getTicketCount();  // 查询剩余车票数量
}
// RealSubject
public class RealTicketService implements TicketService {
    private int ticketCount;

    public RealTicketService(int ticketCount) {
        this.ticketCount = ticketCount;
    }
    @Override
    public void buyTicket(String destination) {
        if (ticketCount > 0) {
            ticketCount--;
            System.out.println("购票成功,目的地:" + destination);
        } else {
            System.out.println("购票失败,余票不足");
        }
    }
    @Override
    public int getTicketCount() {
        return ticketCount;
    }
}
// Proxy
public class TicketProxy implements TicketService {
    private RealTicketService realTicketService;
    public TicketProxy(RealTicketService realTicketService) {
        this.realTicketService = realTicketService;
    }
    @Override
    public void buyTicket(String destination) {
        // 购票
        realTicketService.buyTicket(destination);
    }
    @Override
    public int getTicketCount() {
        // 查询余票
        return realTicketService.getTicketCount();
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建真实的票务服务对象
        RealTicketService realTicketService = new RealTicketService(10);
        // 创建代理对象
        TicketProxy proxyTicketService = new TicketProxy(realTicketService);
        // 学生通过代理渠道购票
        proxyTicketService.buyTicket("北京");
        // 查询余票数量
        int ticketCount = proxyTicketService.getTicketCount();
        System.out.println("余票数量:" + ticketCount);
    }
}

动态代理

// Subject
public interface TicketService {
    public void buyTicket(String destination);  // 买票
    public int getTicketCount();  // 查询剩余车票数量
}
// RealSubject
public class RealTicketService implements TicketService {
    private int ticketCount;
    public RealTicketService(int ticketCount) {
        this.ticketCount = ticketCount;
    }
    @Override
    public void buyTicket(String destination) {
        if (ticketCount > 0) {
            ticketCount--;
            System.out.println("购票成功,目的地:" + destination);
        } else {
            System.out.println("购票失败,余票不足");
        }
    }
    @Override
    public int getTicketCount() {
        return ticketCount;
    }
}
public class ProxyFactory implements InvocationHandler {
    // 维护一个目标对象
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    // 给目标对象生成一个代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用真实的票务服务对象
        Object result = method.invoke(target, args);
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建真实的票务服务对象
        RealTicketService realTicketService = new RealTicketService(10);
        // 创建动态代理对象
        TicketService proxyTicketService = (TicketService) Proxy.newProxyInstance(
                TicketService.class.getClassLoader(),
                new Class[]{TicketService.class},
                new ProxyFactory(realTicketService)
        );
        // 学生通过代理渠道购票
        proxyTicketService.buyTicket("北京");
        // 查询余票数量
        int ticketCount = proxyTicketService.getTicketCount();
        System.out.println("余票数量:" + ticketCount);
    }
}

Cglib代理

Cglib代理:静态代理和动态代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这时候就需要目标对象的子类来实现代理,这就是Cglib代理,也叫做子类代理。它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。也有将Cglib归属到动态代理一类的。

// RealSubject
public class RealTicketService {
    private int ticketCount;
    public RealTicketService(int ticketCount) {
        this.ticketCount = ticketCount;
    }
    public void buyTicket(String destination) {
        if (ticketCount > 0) {
            ticketCount--;
            System.out.println("购票成功,目的地:" + destination);
        } else {
            System.out.println("购票失败,余票不足");
        }
    }
    public int getTicketCount() {
        return ticketCount;
    }
}
// Proxy
public class TicketProxy implements MethodInterceptor {
    private Object target;
    public TicketProxy(Object target) {
        this.target = target;
    }
    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 调用真实的目标对象的方法
        Object result = proxy.invokeSuper(obj, args);

        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建真实的目标对象
        RealTicketService realTicketService = new RealTicketService(10);
        // 创建 CGLIB 代理对象
        TicketProxy ticketProxy = new TicketProxy(realTicketService);
        RealTicketService proxy = (RealTicketService) ticketProxy.getProxyInstance();
        // 学生通过代理渠道购票
        proxy.buyTicket("北京");
        // 查询余票数量
        int ticketCount = proxy.getTicketCount();
        System.out.println("余票数量:" + ticketCount);
    }
}

模板模式(常用)

模板模式:定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现,使得子类可以不改变一个算法的结构,就可以重新定义算法中的某些步骤。即在一个抽象类公开定义了执行方法的模板,它的子类可以按需重写方法实现,但调用仍以抽象类中定义的方法进行。

这里我以咖啡制作为例,实现该模式:咖啡类型由多种,如美式咖啡、拿铁咖啡和卡布奇诺咖啡。它们在制作过程中有一些共同的步骤,比如磨咖啡豆、冲泡咖啡和加入调味品,但每种咖啡的具体步骤和配料有所不同。

// 咖啡模板
public abstract class CoffeeTemplate {
    public void makeCoffee() {
        grindCoffeeBeans();
        brewCoffee();
        addIngredients();
        serveCoffee();
    }

    protected void grindCoffeeBeans() {
        System.out.println("磨咖啡豆");
    }

    protected abstract void brewCoffee();

    protected abstract void addIngredients();

    protected void serveCoffee() {
        System.out.println("上菜");
    }
}
// 美式咖啡
public class AmericanoCoffee  extends CoffeeTemplate {
    @Override
    protected void brewCoffee() {
        System.out.println("冲泡美式咖啡");
    }

    @Override
    protected void addIngredients() {
        System.out.println("加入糖和牛奶");
    }
}
// 拿铁咖啡
public class LatteCoffee extends CoffeeTemplate {
    @Override
    protected void brewCoffee() {
        System.out.println("冲泡拿铁咖啡");
    }

    @Override
    protected void addIngredients() {
        System.out.println("加入牛奶泡沫");
    }
}
// 卡布奇诺咖啡
public class CappuccinoCoffee extends CoffeeTemplate {
    @Override
    protected void brewCoffee() {
        System.out.println("冲泡卡布奇诺咖啡");
    }

    @Override
    protected void addIngredients() {
        System.out.println("加入可可粉和奶泡");
    }
}
public class Main {
    public static void main(String[] args) {
        CoffeeTemplate americano = new AmericanoCoffee();
        americano.makeCoffee();

        CoffeeTemplate latte = new LatteCoffee();
        latte.makeCoffee();

        CoffeeTemplate cappuccino = new CappuccinoCoffee();
        cappuccino.makeCoffee();
    }
}

命令模式(常用)

命令模式:将一个请求封装为一个对象,从而使不同的请求能够被参数化。同时对请求排队或记录请求日志,并且能够支持可撤销的操作。命令模式消除了请求发送者和请求接收者之间的耦合,让对象之间的调用关系更加灵活。

命令模式主要包括4类角色:

Command:命令角色,定义了执行操作的接口或抽象类,需要执行的所有命令都会封装在这里,通常包括一个执行方法(execute)。

ConcreteCommand:具体的命令类,实现了Command中的方法,持有对接收者对象的引用,并实现了具体的操作方法。

Receiver:接收者,执行实际操作的对象。具体命令对象通常会将请求委托给接收者来执行。

Invoker:调用者,负责调用命令对象执行请求的对象。通常会持有一个命令对象的引用,并通过调用命令对象的执行方法来触发操作。

这里我以书中烧烤为例,实现该模式:由服务员(invoker)记录客人的点餐(command):烤韭菜和烤羊肉串,并交由厨师(receiver)制作:

public interface Command {
    // 执行命令
    public  void execute();
}
public class MuttonCommand implements Command{
    ChefReceiver chefReceiver = new ChefReceiver();
    @Override
    public void execute() {
        chefReceiver.MakeMutton();
    }
}
public class LeekCommand implements Command{
    ChefReceiver chefReceiver = new ChefReceiver();
    @Override
    public void execute() {
        chefReceiver.MakeLeek();
    }
}
public class WaiterInvoker {
    // 命令列表
    private List<Command> commandList = new ArrayList<>();
    public void setOrder(Command command){
        commandList.add(command);
        System.out.println("增加订单:" + command.getClass());
    }
    public void cancelOrder(Command command){
        commandList.remove(command);
        System.out.println("取消订单:" + command.getClass());
    }
    public void notifyChef(){
        for(Command cmd: commandList){
            cmd.execute();
        }
    }
}
public class ChefReceiver {
    public void MakeMutton(){
        System.out.println("烤羊肉串");
    }
    public void MakeLeek(){
        System.out.println("烤韭菜");
    }
}
public class Main {
    public static void main(String[] args) {
        ChefReceiver chef = new ChefReceiver();
        Command muttonCommand1 = new MuttonCommand();
        Command muttonCommand2 = new MuttonCommand();
        Command leekCommand = new LeekCommand();
        WaiterInvoker waiter = new WaiterInvoker();

        waiter.setOrder(muttonCommand1);
        waiter.setOrder(muttonCommand2);
        waiter.setOrder(leekCommand);

        waiter.cancelOrder(muttonCommand2);

        waiter.notifyChef();
    }
}

访问者模式

访问者模式:封装一些作用于某种对象中的各元素的操作,可以在不改变已有对象结构的前提下,定义对对象结构中元素的新操作。主要用于将数据结构和数据操作分离,解决数据结构和操作耦合性的问题。该模式将操作封装在一个称为访问者的对象中,访问者可以通过对象结构中的元素进行遍历,并对每个元素执行特定的操作。

访问者模式主要包括5类角色:

Visitor:抽象访问者,定义了对各种具体元素进行访问的抽象方法,每个方法对应一种具体元素类型,即为对象结构中的每一个ConcreteElement类声明一个访问方法。

ConcreteVisitor:具体访问者,实现了Visitor定义的方法。

Element:抽象元素,定义了接收访问者对象的接口,通常包括一个接收访问者的方法accept()。

ConcreteElement:具体元素类,实现了抽象元素定义的接口,接收一个访问者对象。

ObjectStructure:对象结构,表示元素的集合,可以是一个容器或集合类,用来允许访问者访问元素。

这里我使用访问博物馆的例子来实现访问者模式:这里的藏品我们暂定为2类,图画类Painting和雕塑类Sculpture,博物馆中不同的人有不同的方法:收藏者Collector能收藏图画和雕塑,讲解员Expostor能讲解图画和雕塑,游客Viewer能参观图画和雕塑:

public interface ExhibitElement {
    public void accept(Visitor visitor);
}
// ConcreteElement  图画类
public class Painting implements ExhibitElement {
    private String title;

    public Painting(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitPainting(this);
    }
}
// ConcreteElement 雕像类
public class Sculpture implements ExhibitElement {
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitSculpture(this);
    }
}
// 访问者接口
public interface Visitor {
    void visitPainting(Painting painting);

    void visitSculpture(Sculpture sculpture);
}
// ConcreteVisitor 收藏者类
class Collector implements Visitor {
    @Override
    public void visitPainting(Painting painting) {
        System.out.println("收藏绘画:" + painting.getTitle());
    }

    @Override
    public void visitSculpture(Sculpture sculpture) {
        System.out.println("收藏雕塑:" + sculpture.getName());
    }
}
// ConcreteVisitor 游客类
class Viewer implements Visitor {
    @Override
    public void visitPainting(Painting painting) {
        System.out.println("浏览绘画:" + painting.getTitle());
    }

    @Override
    public void visitSculpture(Sculpture sculpture) {
        System.out.println("浏览雕塑:" + sculpture.getName());
    }
}
// ConcreteVisitor 讲解员
public class Expostor implements Visitor {
    @Override
    public void visitPainting(Painting painting) {
        System.out.println("讲解绘画:" + painting.getTitle());
    }

    @Override
    public void visitSculpture(Sculpture sculpture) {
        System.out.println("讲解雕塑:" + sculpture.getName());
    }
}
// ObjectStructure 博物馆类
class Museum {
    private List<ExhibitElement> exhibits = new ArrayList<>();

    public void addExhibit(ExhibitElement exhibitElement) {
        exhibits.add(exhibitElement);
    }

    public void removeExhibit(ExhibitElement exhibitElement) {
        exhibits.remove(exhibitElement);
    }

    public void accept(Visitor visitor) {
        for (ExhibitElement exhibitElement : exhibits) {
            exhibitElement.accept(visitor);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Museum museum = new Museum();
        museum.addExhibit(new Painting("《星空》"));
        museum.addExhibit(new Sculpture("思想者"));

        Visitor collector = new Collector();
        museum.accept(collector);

        Visitor viewer = new Viewer();
        museum.accept(viewer);

        Visitor expostor = new Expostor();
        museum.accept(expostor);
    }
}

迭代器模式

迭代器模式:提供一种顺序访问聚合对象(对象中元素是用不同的方式实现的)中各个元素的方法,而不需要暴露该对象的内部表示,即用一致的方法遍历聚合对象中的元素,而不需要知道元素的底层表示。通过使用迭代器模式,我们可以在不知道聚合对象的具体结构的情况下,遍历聚合对象中的元素。

访问者模式主要包括4类角色:

Iterator:迭代器接口,定义了访问和遍历元素的接口,包括获取下一个元素的方法、判断是否还有元素的方法等。

ConcreteIterator:具体的迭代器类,管理迭代。

Aggregate:一个统一的聚合接口,将客户端和具体聚合对象解耦。

ConcreteAggregate:具体的聚合,持有对象集合。

这里我使用书架的例子来实现该功能:

public class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}
public class Stationery {
    private String name;

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

    public String getName() {
        return name;
    }
}
public class Bookshelf implements Iterable<Object> {
    private List<Object> items;

    public Bookshelf() {
        this.items = new ArrayList<>();
    }

    public void addBook(Book book) {
        items.add(book);
    }

    public void addStationery(Stationery stationery) {
        items.add(stationery);
    }

    public Iterator<Object> iterator() {
        return items.iterator();
    }
}
public class Main {
    public static void main(String[] args) {
        Bookshelf bookshelf = new Bookshelf();
        bookshelf.addBook(new Book("Book 1"));
        bookshelf.addBook(new Book("Book 2"));
        bookshelf.addBook(new Book("Book 3"));

        Stationery[] stationeries = {
                new Stationery("Pen"),
                new Stationery("Pencil"),
                new Stationery("Eraser")
        };
        bookshelf.addStationery(stationeries[0]);
        bookshelf.addStationery(stationeries[1]);
        bookshelf.addStationery(stationeries[2]);

        // 使用迭代器遍历书架中的物品
        Iterator<Object> iterator = bookshelf.iterator();
        while (iterator.hasNext()) {
            Object item = iterator.next();
            if (item instanceof Book) {
                Book book = (Book) item;
                System.out.println("Book: " + book.getTitle());
            } else if (item instanceof Stationery) {
                Stationery stationery = (Stationery) item;
                System.out.println("Stationery: " + stationery.getName());
            }
        }
    }
}

观察者模式(常用)

观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,即:当一个对象的状态发生变化时,所有依赖于它的其他对象都会收到通知,并自动更新。

观察者模式主要包括2类角色:

Subject:具有状态的主题对象,当其状态发生变化时,会通知所有的观察者。

Observer:观察者对象,依赖于主题,它们会注册自己以接收主题的通知,并在接收到通知时执行相应的操作。

这里我使用订阅牛奶的例子来实现该模式:

// 观察者接口
public interface Subscriber {
    void update(String milkType);
}
// 主题接口
public interface MilkProvider {
    void registerSubscriber(Subscriber subscriber);
    void removeSubscriber(Subscriber subscriber);
    void notifySubscribers();
}
// 具体主题类
class MilkDeliveryService implements MilkProvider {
    private List<Subscriber> subscribers = new ArrayList<>();
    private String milkType;

    @Override
    public void registerSubscriber(Subscriber subscriber) {
        subscribers.add(subscriber);
    }

    @Override
    public void removeSubscriber(Subscriber subscriber) {
        subscribers.remove(subscriber);
    }

    @Override
    public void notifySubscribers() {
        for (Subscriber subscriber : subscribers) {
            subscriber.update(milkType);
        }
    }

    public void setMilkType(String milkType) {
        this.milkType = milkType;
        notifySubscribers();
    }
}
// 具体观察者类 - 小区住户
class ResidentialSubscriber implements Subscriber {
    private String name;

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

    @Override
    public void update(String milkType) {
        System.out.println("【小区住户】" + name + " 收到新鲜的牛奶:" + milkType);
    }
}
// 具体观察者类 - 医院
class HospitalSubscriber implements Subscriber {
    private String name;

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

    @Override
    public void update(String milkType) {
        System.out.println("【医院】" + name + " 收到新鲜的牛奶:" + milkType);
    }
}
// 具体观察者类 - 学校
class SchoolSubscriber implements Subscriber {
    private String name;

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

    @Override
    public void update(String milkType) {
        System.out.println("【学校】" + name + " 收到新鲜的牛奶:" + milkType);
    }
}
public class Main {
    public static void main(String[] args) {
        MilkDeliveryService milkDeliveryService = new MilkDeliveryService();

        // 创建订阅者
        Subscriber residentialSubscriber1 = new ResidentialSubscriber("小区住户1");
        Subscriber residentialSubscriber2 = new ResidentialSubscriber("小区住户2");
        Subscriber hospitalSubscriber = new HospitalSubscriber("医院");
        Subscriber schoolSubscriber = new SchoolSubscriber("学校");

        // 注册订阅者
        milkDeliveryService.registerSubscriber(residentialSubscriber1);
        milkDeliveryService.registerSubscriber(residentialSubscriber2);
        milkDeliveryService.registerSubscriber(hospitalSubscriber);
        milkDeliveryService.registerSubscriber(schoolSubscriber);

        // 发送牛奶类型更新通知
        milkDeliveryService.setMilkType("鲜牛奶");

        // 取消订阅
        milkDeliveryService.removeSubscriber(residentialSubscriber2);

        // 发送牛奶类型更新通知
        milkDeliveryService.setMilkType("脱脂牛奶");
    }
}

中介者模式

中介者模式:用一个中介对象来封装一系列的对象交互,使对象之间不再直接相互引用,而是通过一个中介者对象进行通信和协调,从而米使其松耦合,而且可以独立地改变它们之间的交互。

中介者模式主要包括4类角色:

Mediator:抽象中介者,定义了同事对象到中介者对象的接口。

ConcreteMediator:具体的中介者对象,实现了中介者的抽象方法,它需要知道所有具体的同事类,即以一个集合来管理HsahMap,并接收某个同事对象消息完成相应的操作。

Colleague:抽象同事类,定义了与中介者对象交互的接口,在实际开发中,该类可以省略。

ConcreteColleague:具体的同事类,会有多个同事类,每个同事只知道自己的行为,而不了解其他同事类的行为,但它们都依赖中介者对象。

这里我以聊天室为例实现该模式:在一个聊天室中,有多个用户参与并进行交流。如果每个用户都直接与其他用户进行通信,那么系统的设计将变得复杂且难以维护。使用中介模式,引入一个聊天室的中介者对象,该负责接收来自用户的消息,并将消息传递给其他用户。每个用户只需将消息发送给中介者,而不需要直接与其他用户通信。

// 抽象同事类
abstract class AbstractColleague {
    protected ChatRoomMediator mediator;

    public AbstractColleague(ChatRoomMediator mediator) {
        this.mediator = mediator;
    }

    public abstract void sendMessage(String message);
    public abstract void receiveMessage(String message);
}
// 用户类
public class User extends AbstractColleague {
    private String name;

    public User(String name, ChatRoomMediator mediator) {
        super(mediator);
        this.name = name;
    }

    @Override
    public void sendMessage(String message) {
        mediator.broadcastMessage(message, this);
    }

    @Override
    public void receiveMessage(String message) {
        System.out.println(name + " 接收到消息: " + message);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
// 管理员类
public class Admin extends AbstractColleague {
    private String name;

    public Admin(String name, ChatRoomMediator mediator) {
        super(mediator);
        this.name = name;
    }

    @Override
    public void sendMessage(String message) {
        mediator.sendNotification(message);
    }

    @Override
    public void receiveMessage(String message) {
        // 管理员不接收消息
    }
}

// 抽象中介者
public interface ChatRoomMediator {
    void broadcastMessage(String message, AbstractColleague sender);
    void sendNotification(String message);
    void addColleague(AbstractColleague colleague);
}
// 具体中介者类
class ChatRoom implements ChatRoomMediator {
    private List<AbstractColleague> colleagues;

    public ChatRoom() {
        this.colleagues = new ArrayList<>();
    }

    @Override
    public void broadcastMessage(String message, AbstractColleague sender) {
        for (AbstractColleague colleague : colleagues) {
            if (colleague != sender) {
                colleague.receiveMessage(message);
            }
        }
    }

    @Override
    public void sendNotification(String message) {
        for (AbstractColleague colleague : colleagues) {
            if (colleague instanceof Admin) {
                colleague.receiveMessage("[Notification] " + message);
            }
        }
    }

    @Override
    public void addColleague(AbstractColleague colleague) {
        colleagues.add(colleague);
    }
}
public class Main {
    public static void main(String[] args) {
        // 创建中介者对象
        ChatRoomMediator chatRoom = new ChatRoom();

        // 创建用户对象并添加到中介者
        User user1 = new User("张三", chatRoom);
        User user2 = new User("李四", chatRoom);
        User user3 = new User("王五", chatRoom);
        chatRoom.addColleague(user1);
        chatRoom.addColleague(user2);
        chatRoom.addColleague(user3);

        // 创建管理员对象并添加到中介者
        AbstractColleague admin = new Admin("Admin", chatRoom);
        chatRoom.addColleague(admin);

        // 用户发送消息
        user1.sendMessage("大家好,我是:" + user1.getName());
        user2.sendMessage("大家好,我是:" + user2.getName());
        user3.sendMessage("大家好,我是:" + user3.getName());
        // 管理员发送消息
        admin.sendMessage("管理员来了");
    }
}

备忘录模式

备忘录模式:在不破坏封装性的前提下,捕获对象的内部状态,并在该对象之外保存这个状态,在需要时将对象恢复到之前保存的状态。该模式适用于需要记录和恢复对象状态的场景,例如撤销操作、历史记录或者实现回滚机制。

备忘录模式主要包括3类角色:

Originator:生成者角色,负责创建和保存备忘录对象,并可以通过备忘录对象恢复其内部状态。

Memento:备忘录角色,用于存储Originator对象的内部信息和状态。备忘录对象可以包含发起人的状态的一个快照或者增量信息。

Caretaker:管理者角色,负责保存和管理备忘录对象。它可以存储多个备忘录对象,提供对备忘录的访问和恢复。

这里我用大富翁的例子实现备忘录模式:骰子点数为1时,前进一步,获得金钱+100;骰子点数为2时,前进2步,损失金钱-200,;骰子点数为3时,前进3步,购买房产+1,损失金钱-500;骰子点数为4时,前进4步,暂停;骰子点数为5时,直接进入监狱;骰子点数为6时,直接回到起点。

// Originator
public class Player {
    private int position;
    private int money;
    private int properties;

    public void rollDice() {
        // 掷骰子,返回点数
        int diceResult = (int) (Math.random() * 6) + 1;
        System.out.println("骰子点数:" + diceResult);

        if (diceResult == 1) {
            moveForward(1);
            earnMoney(100);
        } else if (diceResult == 2) {
            moveForward(2);
            loseMoney(200);
        } else if (diceResult == 3) {
            moveForward(3);
            buyProperty();
            loseMoney(500);
        } else if (diceResult == 4) {
            moveForward(4);
        } else if (diceResult == 5) {
            goToJail();
        } else if (diceResult == 6) {
            goToStart();
        }
    }

    public void moveForward(int steps) {
        // 前进指定步数
        position += steps;
    }

    public void earnMoney(int amount) {
        // 获得金钱
        money += amount;
    }

    public void loseMoney(int amount) {
        // 损失金钱
        money -= amount;
    }

    public void buyProperty() {
        // 购买房产
        properties++;
    }

    public void goToJail() {
        // 直接进入监狱
        position = 10;
    }

    public void goToStart() {
        // 直接到达起点
        position = 0;
    }

    public PlayerMemento createMemento() {
        // 创建当前状态的备忘录
        return new PlayerMemento(position, money, properties);
    }

    public void restoreFromMemento(PlayerMemento memento) {
        // 恢复到指定备忘录的状态
        position = memento.getPosition();
        money = memento.getMoney();
        properties = memento.getProperties();
    }

    public void displayStatus() {
        // 显示当前状态
        System.out.println("位置:" + position);
        System.out.println("金钱:" + money);
        System.out.println("房产:" + properties);
    }
}
// Memento
public class PlayerMemento {
    private int position;
    private int money;
    private int properties;

    public PlayerMemento(int position, int money, int properties) {
        this.position = position;
        this.money = money;
        this.properties = properties;
    }

    public int getPosition() {
        return position;
    }

    public int getMoney() {
        return money;
    }

    public int getProperties() {
        return properties;
    }
}
// Caretaker
public class GameHistory {
    private List<PlayerMemento> mementos = new ArrayList<>();

    public void saveState(PlayerMemento memento) {
        // 保存备忘录对象
        mementos.add(memento);
    }

    public PlayerMemento getState(int index) {
        // 获取指定索引的备忘录对象
        return mementos.get(index);
    }
}
public class Main {
    public static void main(String[] args) {
        Player player = new Player();
        GameHistory history = new GameHistory();

        // 初始状态
        PlayerMemento initialState = player.createMemento();
        history.saveState(initialState);

        // 游戏开始
        player.rollDice();

        // 保存状态
        PlayerMemento currentState = player.createMemento();
        history.saveState(currentState);

        // 恢复到初始状态
        player.restoreFromMemento(history.getState(0));
        System.out.println("初始状态:");
        player.displayStatus();

        // 恢复到当前状态
        player.restoreFromMemento(history.getState(1));
        System.out.println("当前状态:");
        player.displayStatus();
    }
}

解释器模式

解释器模式:在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元通过语法分析器构成语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。解释器模式是指给定一个语言,定义它的文法的一种表示,并定义一个解释器,用来解释语言中的句子。

解释器模式主要包括4类角色:

AbstractExpression:抽象表达式,定义了解释器的抽象方法 interpret(),所有具体表达式类都需要实现该方法。

TerminalExpression:终结符表达式,表示语法规则中的终结符或基本元素。终结符表达式不能再进行解释,只能进行具体的操作。

Non-terminalExpression:非终结符表达式,表示语法规则中的非终结符或复合语法规则。非终结符表达式可以包含其他表达式作为子表达式,并且可以对子表达式进行递归解释。

Context:上下文角色,包含需要解释的语句或表达式,并且提供了解释器的环境和上下文信息。

这里我用解释器模式来实现四则运算:

public interface Expression {
    int interpret();
}
public class NumberExpression implements Expression{
    private int value;

    public NumberExpression(int value) {
        this.value = value;
    }
    @Override
    public int interpret() {
        return value;
    }
}
public class AddExpression implements Expression {
    private Expression left;
    private Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}
public class SubtractExpression implements Expression {
    private Expression left;
    private Expression right;

    public SubtractExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}
public class MultiplyExpression implements Expression {
    private Expression left;
    private Expression right;

    public MultiplyExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}
public class DivideExpression implements Expression {
    private Expression left;
    private Expression right;

    public DivideExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        int divisor = right.interpret();
        if (divisor != 0) {
            return left.interpret() / divisor;
        } else {
            throw new ArithmeticException("除数不能为0");
        }
    }
}
public class Main {
    public static void main(String[] args) {
        // 构建表达式:5 + 4 * (3 - 1)
        Expression expression = new AddExpression(
                new NumberExpression(5),
                new MultiplyExpression(
                        new NumberExpression(4),
                        new SubtractExpression(
                                new NumberExpression(3),
                                new NumberExpression(1)
                        )
                )
        );
        // 解释并计算结果
        int result = expression.interpret();
        System.out.println("Result: " + result);
    }
}

状态模式

状态模式:当一个对象的内在状态改变时,可以改变其行为。即:对象的行为是根据其内部状态而变化的,看起来好像对象在运行时改变了类。

状态模式主要包括3类角色:

Context:上下文角色,维护一个具体状态类的实例,用于定义当前状态。

State:状态角色,定义一个接口,用于封装与Context的特定状态相关的行为。

ConcreteState:实现State接口的具体状态类,封装与特定状态相关的行为。

这里我使用积分抽奖的例子来实现该模式:

public interface LotteryState {
    void deductPoints(); // 扣除积分
    boolean canLottery(); // 是否可以抽奖
    void lottery(); // 抽奖
}
public class LotteryContext {
    private int points; // 积分
    private LotteryState state; // 当前状态

    public LotteryContext(int points) {
        this.points = points;

        if (points < 10) {
            state = new NoLotteryState(this);
        } else {
            state = new CanLotteryState(this);
        }
    }

    public int getPoints() {
        return points;
    }

    public void setPoints(int points) {
        this.points = points;
    }

    public void deductPoints() {
        state.deductPoints();
    }

    public boolean canLottery() {
        return state.canLottery();
    }

    public void lottery() {
        state.lottery();
    }

    public void setState(LotteryState state) {
        this.state = state;
    }
}
public class NoLotteryState implements LotteryState {
    private LotteryContext context;

    public NoLotteryState(LotteryContext context) {
        this.context = context;
    }

    @Override
    public void deductPoints() {
        System.out.println("积分不足,无法抽奖");
    }

    @Override
    public boolean canLottery() {
        return false;
    }

    @Override
    public void lottery() {
        System.out.println("积分不足,无法抽奖");
    }
}
public class CanLotteryState implements LotteryState {
    private LotteryContext context;

    public CanLotteryState(LotteryContext context) {
        this.context = context;
    }

    @Override
    public void deductPoints() {
        System.out.println("扣除10积分");
        // 扣除10积分
        int points = context.getPoints() - 10;
        context.setPoints(points);

        if (points < 10) {
            context.setState(new NoLotteryState(context));
        }
    }

    @Override
    public boolean canLottery() {
        return true;
    }

    @Override
    public void lottery() {
        System.out.println("开始抽奖...");
        // 模拟抽奖逻辑
        double random = Math.random();

        if (random < 0.4) {
            System.out.println("恭喜您,抽中了谢谢参与!");
        } else if (random < 0.7) {
            System.out.println("恭喜您,抽中了三等奖!");
            context.setState(new HasPrizeState(context));
        } else if (random < 0.9) {
            System.out.println("恭喜您,抽中了二等奖!");
            context.setState(new HasPrizeState(context));
        } else {
            System.out.println("恭喜您,抽中了一等奖!");
            context.setState(new HasPrizeState(context));
        }
    }
}
public class HasPrizeState implements LotteryState {
    private LotteryContext context;

    public HasPrizeState(LotteryContext context) {
        this.context = context;
    }

    @Override
    public void deductPoints() {
        System.out.println("您已抽中奖品,无需再扣积分");
    }

    @Override
    public boolean canLottery() {
        return false;
    }

    @Override
    public void lottery() {
        System.out.println("您已抽中奖品,无需再次抽奖");
    }
}
public class Main {
    public static void main(String[] args) {
        LotteryContext context = new LotteryContext(100);

        for (int i = 0; i < 5; i++) {
            if (context.canLottery()) {
                context.lottery();
                context.deductPoints();
            } else {
                System.out.println("抽奖结束");
                break;
            }
        }
    }
}

策略模式(常用)

策略模式:定义了算法家族,将不同的算法封装成独立的策略类,并使它们可以互相替换,从而使算法的变化独立于使用算法的客户端。算法的变化并不会影响到使用算法的客户端。

策略模式主要包括3类角色:

Strategy:抽象策略类或接口,定义了所有具体策略必须遵循的方法。策略接口通常只包含一个方法,用于执行策略定义的算法。

ConcreteStrategy:具体策略类,实现了Strategy中的方法。

Context:上下文角色,保存ConcreteStrategy类的实例对象,并在运行时根据需要调用具体策略的算法。上下文角色通常提供了一个方法,用于设置和获取当前的策略。

这里我使用商场促销打折的例子实现策略模式:

public interface DiscountStrategy {
    double applyDiscount(int quantity, double price);
}
public class TwoItemsDiscountStrategy implements DiscountStrategy {
    @Override
    public double applyDiscount(int quantity, double price) {
        if (quantity >= 2) {
            return price * 0.8; // 打8折
        }
        return price;
    }
}
public class ThreeItemsDiscountStrategy implements DiscountStrategy {
    @Override
    public double applyDiscount(int quantity, double price) {
        if (quantity >= 3) {
            return price * 0.7; // 打7折
        }
        return price;
    }
}
public class ShoppingContext {
    private DiscountStrategy discountStrategy;

    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public double checkout(int quantity, double price) {
        if (discountStrategy != null) {
            return discountStrategy.applyDiscount(quantity, price);
        }
        return price;
    }
}
public class Main {
    public static void main(String[] args) {
        ShoppingContext shopping = new ShoppingContext();

        // 设置满两件打8折策略
        DiscountStrategy twoItemsDiscount = new TwoItemsDiscountStrategy();
        shopping.setDiscountStrategy(twoItemsDiscount);
        double amountAfterDiscount = shopping.checkout(2, 100*2); // 应用折扣策略,计算折扣后的金额
        System.out.println("Amount after two items discount: " + amountAfterDiscount);

        // 设置满三件打7折策略
        DiscountStrategy threeItemsDiscount = new ThreeItemsDiscountStrategy();
        shopping.setDiscountStrategy(threeItemsDiscount);
        amountAfterDiscount = shopping.checkout(3, 100*3); // 应用折扣策略,计算折扣后的金额
        System.out.println("Amount after three items discount: " + amountAfterDiscount);
    }
}

职责链模式(常用)

职责链模式:又称责任链模式,将请求沿着处理链进行传递,直到有一个处理者能够处理该请求。该模式中,存在一个处理者链,每个处理者都有机会处理请求。当一个请求进入责任链时,它会依次经过链中的处理者,直到有一个处理者能够处理该请求,或者请求到达链的末端。每个处理者都有一个对下一个处理者的引用,形成了链式结构。

职责链模式主要包括2类角色:

Handle:抽象的处理类或接口,定义了处理请求的方法接口,通常包含一个对下一个处理者的引用。

ConcreteHandle:具体的处理类,实现了Handle的接口,具体处理请求的逻辑,并可以选择是否将请求传递给下一个处理者。

每次看到职责链,都会想起网上盛传的价值一个亿的Python代码,我这里就使用该模式实现一个JAVA版的代码:

public abstract class Handler {
    protected Handler nextHandler;

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract String handleRequest(String input);
}
public class RemoveQuestionMarkHandler extends Handler {
    public String handleRequest(String input) {
        String result = input.replace("?", "!");
        if (nextHandler != null) {
            return nextHandler.handleRequest(result);
        }
        return result;
    }
}
public class RemoveMaHandler extends Handler {
    public String handleRequest(String input) {
        String result = input.replace("吗", "");
        if (nextHandler != null) {
            return nextHandler.handleRequest(result);
        }
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        Handler removeQuestionMarkHandler = new RemoveQuestionMarkHandler();
        Handler removeMaHandler = new RemoveMaHandler();

        removeQuestionMarkHandler.setNextHandler(removeMaHandler);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("请输入: ");
            String input = scanner.nextLine();

            String result = removeQuestionMarkHandler.handleRequest(input);
            System.out.println(result);
        }
    }
}

到此23种设计模式就全部介绍完了,后续有时间的话,会再根据每种设计模式,补充其类图和时序图;由于本人能力水平有限,文章中有误与不足之处,请大家指出更正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值