OK!用大白话说清楚设计模式(一)

之前都是不间断的看 "设计模式" 的各种教程和文章,但都是临时起意,最后也没有吸收成自己的实力,这次用大白话把自己的理解和看法写下来。

供自己之后复习用,也希望能帮到大家。

一、前言

概念:设计模式(Design Patterns)是软件开发中用来解决常见问题的通用、可重用的解决方案。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式并不是直接可用的代码,而是模板或指导原则,开发者可以根据具体需求加以实现。

1.1、六大原则(SOLID)

  1. 单一职责原则(Single Responsibility Principle, SRP)
    • 啥意思:一个类或者模块只负责一件事。就像一个厨师专心炒菜,不让他一边炒菜一边洗碗。
    • 为啥重要:如果一个类只干一件事,当需求变了,你只需要改这个类,而且改的原因只有一个。这样出错的机会少了,维护起来也简单。
  2. 开放-封闭原则(Open-Closed Principle, OCP)
    • 啥意思:软件应该欢迎扩展,但拒绝修改。就像玩乐高积木,你可以加新零件拼出新形状(扩展),但不用拆掉原来的积木(修改)。
    • 为啥重要:需要加新功能时,不用动老代码,直接加新代码就行。这样不会搞乱原来的东西,系统也能保持稳定。
  3. 里氏替换原则(Liskov Substitution Principle, LSP)
    • 啥意思:子类可以代替父类用,而且不会搞砸程序。就像你可以用鸭子代替鸟类,因为鸭子是鸟的一种,但不能用企鹅代替鸟类去飞。
    • 为啥重要:保证子类继承父类时不会出乱子,程序用起来还是靠谱的。
  4. 依赖倒置原则(Dependency Inversion Principle, DIP)
    • 啥意思:高层模块和低层模块都应该依赖抽象,而不是直接依赖对方。就像你用插头插电器,不用管电咋来的,只要插头插座匹配就行。
    • 为啥重要:靠抽象(比如接口)联系起来,模块之间就不那么死绑在一起了,改起来灵活,扩展也方便。
  5. 接口隔离原则(Interface Segregation Principle, ISP)
    • 啥意思:别逼一个类去实现它用不上的接口。就像去饭店点菜,菜单只给你想吃的,别把所有菜都塞给你挑。
    • 为啥重要:类不用干多余的活,代码不乱,维护起来也轻松。
  6. 迪米特法则(Law of Demeter, LoD)
    • 啥意思:一个对象尽量少跟其他对象直接打交道。就像你跟朋友的朋友联系,最好通过你朋友,而不是直接找陌生人。
    • 为啥重要:对象之间联系少了,耦合度低,改一个地方不会牵扯一大堆,系统更好维护。

1.2、分类

  1. 创建型模式(Creational Patterns)
    • 啥意思:关注对象的创建过程,旨在提高对象实例化的灵活性和效率。
    • 包括:单例模式、建造者模式、原型模式、工厂方法模式、抽象工厂模式。
  1. 结构型模式(Structural Patterns)
    • 啥意思:关注类和对象之间的组合与关系,帮助构建清晰的系统结构。
    • 包括:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  1. 行为型模式(Behavioral Patterns)
    • 啥意思:关注对象之间的通信和职责分配,优化对象间的协作。
    • 包括:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

二、详解

2.1.创建型模式(Creational Patterns)

2.1.1、单例模式

一台办公室的打印机,大家都用这同一台,不会每人买一台浪费资源;或者一个游戏里的"上帝视角"控制器,全局就一个,负责协调所有操作。

通俗讲:保证一个类在整个系统里只有一个实例,并且提供一个全局的访问点让大家都能拿到这个实例。避免重复创建对象,节省资源。

分类
  1. 饿汉式(Eager Initialization)

特点:就像一个急性子,程序一启动(类加载时),就立刻把这个唯一的实例造好等着用。

优点:简单直接,天生线程安全(因为创建是在类加载时完成的,JVM保证线程安全)。

缺点:不管你用不用,它都先占着资源,可能有点浪费。

java 复制代码
public class EagerSingleton {
    // 一加载类就创建好实例,final确保不变
    private static final EagerSingleton instance = new EagerSingleton();

    // 构造函数私有,外面没法new,只能用我给的实例
    private EagerSingleton() {
        System.out.println("饿汉式单例已创建!");
    }

    // 全局访问点,随时拿这个唯一实例
    public static EagerSingleton getInstance() {
        return instance;
    }

    // 测试用:随便加个方法
    public void sayHello() {
        System.out.println("Hello, 我是饿汉式单例!");
    }
}

// 测试代码
class Test {
    public static void main(String[] args) {
        EagerSingleton singleton1 = EagerSingleton.getInstance();
        EagerSingleton singleton2 = EagerSingleton.getInstance();
        singleton1.sayHello();
        System.out.println(singleton1 == singleton2); // true,证明是同一个实例
    }
}
  1. 懒汉式(Lazy Initialization)

特点:像个懒人,用到的时候才动手创建实例,不用就不造。

优点:节省资源,只有在需要时才占用内存。

缺点:如果多个线程同时访问,可能会有麻烦(比如创建多个实例),所以需要额外处理线程安全问题。

java 复制代码
public class LazySingleton {
    // 用volatile防止指令重排,确保线程安全
    private static volatile LazySingleton instance;

    // 构造函数私有,外面没法new
    private LazySingleton() {
        System.out.println("懒汉式单例已创建!");
    }

    // 全局访问点,双重检查锁定保证线程安全
    public static LazySingleton getInstance() {
        if (instance == null) { // 第一次检查,避免每次都加锁
            synchronized (LazySingleton.class) { // 加锁,保证只有一个线程能创建
                if (instance == null) { // 第二次检查,防止重复创建
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

    // 测试用:随便加个方法
    public void sayHello() {
        System.out.println("Hello, 我是懒汉式单例!");
    }
}

// 测试代码
class Test {
    public static void main(String[] args) {
        LazySingleton singleton1 = LazySingleton.getInstance();
        LazySingleton singleton2 = LazySingleton.getInstance();
        singleton1.sayHello();
        System.out.println(singleton1 == singleton2); // true,证明是同一个实例
    }
}

2.1.2、建造者模式

建造者模式将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。通过使用建造者模式,开发者可以更灵活地创建复杂对象,并且可以更容易地维护和扩展这些对象的构建过程。

通俗讲:想象你去一家汉堡店点餐。你跟服务员说:"我要一个牛肉汉堡,加奶酪、生菜,再配个可乐和薯条。" 服务员点点头,按照你的要求一步步把汉堡、饮料、配餐组装好,最后交给你一个完整的套餐。这个过程就很像建造者模式。

几种情况:
  1. 对象创建很复杂

如果一个对象需要好几步才能创建完成,或者有好多可选的配置(比如电脑的CPU、内存、硬盘等),用建造者模式可以把过程拆开,变得更清晰。

  1. 对象有多种变体

比如你想要一台游戏电脑、一台办公电脑,它们都是"电脑",但配置不同。建造者模式能让你用同一个建造过程,创建不同配置的对象。

  1. 需要不可变对象

如果你希望对象一旦创建就不能改(像 Java 的 String 类),但创建时又需要设置很多属性,建造者模式是个好选择。

  1. 代码更易读

相比一大堆构造函数参数,或者 setter 方法满天飞,建造者模式用链式调用(一步步设置属性),让代码看起来更直观、更优雅。

java 复制代码
public class Computer {
    private String CPU;           // 处理器
    private String RAM;           // 内存
    private String storage;       // 硬盘
    private String graphicsCard;  // 显卡
    private String operatingSystem; // 操作系统

    // 私有构造函数,只能通过建造者创建
    private Computer(Builder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.storage = builder.storage;
        this.graphicsCard = builder.graphicsCard;
        this.operatingSystem = builder.operatingSystem;
    }

    // 方便打印结果
    @Override
    public String toString() {
        return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", storage=" + storage +
        ", graphicsCard=" + graphicsCard + ", operatingSystem=" + operatingSystem + "]";
    }

    // 静态内部建造者类
    public static class Builder {
        private String CPU;
        private String RAM;
        private String storage;
        private String graphicsCard;
        private String operatingSystem;

        // 设置 CPU
        public Builder setCPU(String CPU) {
            this.CPU = CPU;
            return this; // 返回自身,支持链式调用
        }

        // 设置内存
        public Builder setRAM(String RAM) {
            this.RAM = RAM;
            return this;
        }

        // 设置硬盘
        public Builder setStorage(String storage) {
            this.storage = storage;
            return this;
        }

        // 设置显卡
        public Builder setGraphicsCard(String graphicsCard) {
            this.graphicsCard = graphicsCard;
            return this;
        }

        // 设置操作系统
        public Builder setOperatingSystem(String operatingSystem) {
            this.operatingSystem = operatingSystem;
            return this;
        }

        // 完成构建,返回 Computer 对象
        public Computer build() {
            return new Computer(this);
        }
    }
}


// 真正使用
public class Client {
    public static void main(String[] args) {
        // 用建造者模式创建一台电脑
        Computer computer = new Computer.Builder()
        .setCPU("Intel i7")           // 配置 CPU
        .setRAM("16GB")               // 配置内存
        .setStorage("512GB SSD")      // 配置硬盘
        .setGraphicsCard("NVIDIA RTX 3080") // 配置显卡
        .setOperatingSystem("Windows 11")   // 配置操作系统
        .build();                    // 完成构建

        System.out.println(computer);
    }
}

2.1.3、原型模式

通俗想:你有一张特别好看的照片,想再弄一张一模一样的。你有两个选择:

  1. 重新拍一张:找相机、摆姿势、调光线,费时费力。
  2. 直接复印:把这张照片拿去复印机,咔嚓一下,几秒钟搞定。

通过"复制"现有的对象来创建新对象,而不是从零开始构造

原型模式就是"复印"这个思路:在程序里,我们已经有了一个对象(比如照片),想要一个新对象时,直接"克隆"它,而不是重新从头构造。这样既快又省资源。

在软件开发中,原型模式的好处是:

  • 你不用关心对象是怎么创建的,只要会复制就行。
  • 对于创建成本高的对象(比如需要查数据库、复杂计算),克隆能大大提高效率。
分类
  1. 浅克隆(Shallow Clone)
  • 特点:只复制对象本身和它的基本属性(比如数字、字符串),但如果对象里有复杂的东西(比如另一个对象),就只复制个"地址",不复制内容。
  • 通俗比喻:就像复印照片时,只复印了表面,照片上的标签还是指向原来的标签。
  • 结果:改了克隆对象的标签,原对象也会跟着变,因为它们共用同一个标签。
java 复制代码
// 原型接口
interface Prototype {
    Prototype clone();
}

// 地址类(复杂对象)
class Address {
    public String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address [city=" + city + "]";
    }
}

// 具体原型类
class ShallowClone implements Prototype {
    private String name;
    private int age;
    private Address address; // 复杂对象

    public ShallowClone(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone(); // 调用 Object 的 clone 方法
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "ShallowClone [name=" + name + ", age=" + age + ", address=" + address + "]";
    }
}

// 测试代码
public class ShallowCloneTest {
    public static void main(String[] args) {
        Address address = new Address("北京");
        ShallowClone original = new ShallowClone("小明", 20, address);
        ShallowClone clone = (ShallowClone) original.clone();

        System.out.println("原始对象: " + original);
        System.out.println("克隆对象: " + clone);

        // 修改克隆对象的地址
        clone.address.city = "上海";
        System.out.println("修改后:");
        System.out.println("原始对象: " + original);
        System.out.println("克隆对象: " + clone);
    }
}

运行结果

makefile 复制代码
原始对象: ShallowClone [name=小明, age=20, address=Address [city=北京]]
克隆对象: ShallowClone [name=小明, age=20, address=Address [city=北京]]
修改后:
原始对象: ShallowClone [name=小明, age=20, address=Address [city=上海]]
克隆对象: ShallowClone [name=小明, age=20, address=Address [city=上海]]

解释

  • 浅克隆只复制了 name 和 age,而 address 是共享的。
  • 修改克隆对象的 address.city,原对象的 address 也变了,因为它们指向同一个地址对象
  1. 深克隆(Deep Clone)
  • 特点:不仅复制对象本身,连它里面所有的复杂东西(比如嵌套的对象)也一起复制一份,彻彻底底独立。
  • 通俗比喻:复印照片时,不光复印表面,连照片上的标签和内容都重新做了一份。
  • 结果:改了克隆对象,原对象完全不受影响,因为它们没有任何共享的部分。
java 复制代码
// 原型接口
interface DeepPrototype {
    DeepPrototype deepClone();
}

// 地址类(复杂对象)
class Address {
    public String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address [city=" + city + "]";
    }
}

// 具体原型类
class DeepClone implements DeepPrototype {
    private String name;
    private int age;
    private Address address; // 复杂对象

    public DeepClone(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @Override
    public DeepPrototype deepClone() {
        // 手动复制 address,确保独立
        Address clonedAddress = new Address(this.address.city);
        return new DeepClone(this.name, this.age, clonedAddress);
    }

    @Override
    public String toString() {
        return "DeepClone [name=" + name + ", age=" + age + ", address=" + address + "]";
    }
}

// 测试代码
public class DeepCloneTest {
    public static void main(String[] args) {
        Address address = new Address("北京");
        DeepClone original = new DeepClone("小明", 20, address);
        DeepClone clone = (DeepClone) original.deepClone();

        System.out.println("原始对象: " + original);
        System.out.println("克隆对象: " + clone);

        // 修改克隆对象的地址
        clone.address.city = "上海";
        System.out.println("修改后:");
        System.out.println("原始对象: " + original);
        System.out.println("克隆对象: " + clone);
    }
}

运行结果

makefile 复制代码
原始对象: DeepClone [name=小明, age=20, address=Address [city=北京]]
克隆对象: DeepClone [name=小明, age=20, address=Address [city=北京]]
修改后:
原始对象: DeepClone [name=小明, age=20, address=Address [city=北京]]
克隆对象: DeepClone [name=小明, age=20, address=Address [city=上海]]

解释

  • 深克隆不仅复制了 name 和 age,还复制了 address,两者完全独立。
  • 修改克隆对象的 address.city,原对象一点没受影响。

2.1.4、工厂模式

分类
  1. 简单工厂(Simple Factory)
    • 啥意思:简单工厂就像一个万能小作坊,你告诉它"我想要个啥",它就给你做啥。比如,你去披萨店说"我要个海鲜披萨",店员直接给你烤一个出来。
    • 特点:就一个工厂,啥都能干,你给个参数(比如"海鲜"或"芝士"),它就知道造啥。
  1. 工厂方法(Factory Method)
    • 啥意思:工厂方法就像把大工厂拆成几个专业车间,每个车间只干一件事。比如汽车厂里,轿车车间只造轿车,SUV车间只造SUV,你想要啥就去对应的车间。
    • 特点:每个工厂只管一种产品,工厂和产品一对一。
  1. 抽象工厂(Abstract Factory)
    • 啥意思:抽象工厂是个"大管家",能给你整一套东西。比如一个家具厂,你说"我要现代风格的家具",它就给你做一套现代风格的桌子、椅子和沙发,全都搭配好。
    • 特点:一个工厂能造一组相关的产品,强调的是"产品家族"。
使用场景
  1. 简单工厂
    • 啥时候用
      • 你需要根据条件(比如用户输入)造不同的东西。
      • 东西种类不多,逻辑简单。
      • 小项目或刚开始开发时用着方便。

咋回事:简单工厂就一个类SimplePhoneFactory,你告诉它类型(android或ios),它就给你造对应的手机。

java 复制代码
// 手机接口
interface Phone {
    void call();
}

// Android手机
class AndroidPhone implements Phone {
    @Override
    public void call() {
        System.out.println("用Android手机打电话");
    }
}

// iOS手机
class IosPhone implements Phone {
    @Override
    public void call() {
        System.out.println("用iOS手机打电话");
    }
}

// 简单工厂
class SimplePhoneFactory {
    public static Phone createPhone(String type) {
        if ("android".equals(type)) {
            return new AndroidPhone();
        } else if ("ios".equals(type)) {
            return new IosPhone();
        }
        return null;
    }
}

// 测试
public class Client {
    public static void main(String[] args) {
        Phone phone = SimplePhoneFactory.createPhone("android");
        phone.call();  // 输出: 用Android手机打电话
    }
}
  1. 工厂方法
    • 啥时候用
      • 你想把"造东西"和"用东西"分开,让子类决定造啥。
      • 以后可能会加新东西,种类会变多。
      • 需要灵活扩展的时候。

咋回事:工厂方法给每种手机配了个专属工厂(AndroidPhoneFactory和IosPhoneFactory),你挑工厂,它就给你造对应的手机。

java 复制代码
// 手机接口
interface Phone {
    void call();
}

// Android手机
class AndroidPhone implements Phone {
    @Override
    public void call() {
        System.out.println("用Android手机打电话");
    }
}

// iOS手机
class IosPhone implements Phone {
    @Override
    public void call() {
        System.out.println("用iOS手机打电话");
    }
}

// 工厂接口
interface PhoneFactory {
    Phone createPhone();
}

// Android工厂
class AndroidPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new AndroidPhone();
    }
}

// iOS工厂
class IosPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new IosPhone();
    }
}

// 测试
public class Client {
    public static void main(String[] args) {
        PhoneFactory factory = new AndroidPhoneFactory();
        Phone phone = factory.createPhone();
        phone.call();  // 输出: 用Android手机打电话
    }
}
  1. 抽象工厂
    • 啥时候用
      • 你需要一整套搭配好的东西,比如一套UI界面(按钮、输入框啥的),得风格统一。
      • 系统要支持好几个"家族"(比如Windows和Mac的界面)。
      • 产品族多,要整套换的时候。

咋回事:抽象工厂一个工厂能造一整套(操作系统+硬件),你选AndroidFactory就给你Android的全套,选IosFactory就给iOS的全套。

java 复制代码
// 操作系统接口
interface OperatingSystem {
    void run();
}

// Android操作系统
class AndroidOS implements OperatingSystem {
    @Override
    public void run() {
        System.out.println("运行Android操作系统");
    }
}

// iOS操作系统
class IosOS implements OperatingSystem {
    @Override
    public void run() {
        System.out.println("运行iOS操作系统");
    }
}

// 硬件接口
interface Hardware {
    void powerOn();
}

// Android硬件
class AndroidHardware implements Hardware {
    @Override
    public void powerOn() {
        System.out.println("启动Android硬件");
    }
}

// iOS硬件
class IosHardware implements Hardware {
    @Override
    public void powerOn() {
        System.out.println("启动iOS硬件");
    }
}

// 抽象工厂接口
interface PhoneFactory {
    OperatingSystem createOS();
    Hardware createHardware();
}

// Android工厂
class AndroidFactory implements PhoneFactory {
    @Override
    public OperatingSystem createOS() {
        return new AndroidOS();
    }

    @Override
    public Hardware createHardware() {
        return new AndroidHardware();
    }
}

// iOS工厂
class IosFactory implements PhoneFactory {
    @Override
    public OperatingSystem createOS() {
        return new IosOS();
    }

    @Override
    public Hardware createHardware() {
        return new IosHardware();
    }
}

// 测试
public class Client {
    public static void main(String[] args) {
        PhoneFactory factory = new AndroidFactory();
        OperatingSystem os = factory.createOS();
        Hardware hardware = factory.createHardware();
        os.run();         // 输出: 运行Android操作系统
        hardware.powerOn();  // 输出: 启动Android硬件
    }
}
对比
特性 简单工厂 工厂方法 抽象工厂
工厂数量 就一个 好几个,每个管一种 好几个,每个管一组
造啥 看参数定 一个厂一种产品 一个厂一组产品
扩展性 改工厂代码 加新厂和新产品 加新厂和新产品族
复杂度 简单 中等 复杂
用在哪 种类少,简单活 种类多,要扩展 产品族多,要整套
  • 简单工厂:最简单,但想加新东西得改工厂代码,不太灵活。
  • 工厂方法:扩展方便,加新产品就加新工厂和新产品类,不用动老代码。
  • 抽象工厂:适合整套产品,新增产品族简单,但加新种类得改接口。

三、持续更新........

OK,暂时把大白话写到这里,尽情期待下一篇《大白话说清楚设计模式(二)》

相关推荐
前端付豪2 分钟前
3、Node.js异步编程彻底吃透
前端·后端·node.js
lczdyx3 分钟前
从Flask到智能体:装饰器模式在AI系统中的架构迁移实践
人工智能·python·语言模型·架构·flask·装饰器模式
老胖闲聊4 分钟前
Flask 请求数据获取方法详解
后端·python·flask
孤鸿玉7 分钟前
[Flutter小试牛刀] 低配版signals,添加局部刷新
前端·flutter
舒一笑7 分钟前
一文简单记录打通K8s+Kibana流程如何启动(Windows下的Docker版本)
后端·elasticsearch·kibana
亦黑迷失8 分钟前
轻量级 Express 服务器:用 Pug 模板引擎实现动态参数传递
前端·javascript·后端
慧一居士17 分钟前
Kafka批量消费部分处理成功时的手动提交方案
分布式·后端·kafka
kill bert1 小时前
第33周JavaSpringCloud微服务 分布式综合应用
微服务·云原生·架构
命中的缘分1 小时前
SpringCloud原理和机制
后端·spring·spring cloud
ErizJ1 小时前
Golang|分布式索引架构
开发语言·分布式·后端·架构·golang