之前都是不间断的看 "设计模式" 的各种教程和文章,但都是临时起意,最后也没有吸收成自己的实力,这次用大白话把自己的理解和看法写下来。
供自己之后复习用,也希望能帮到大家。
一、前言
概念:设计模式(Design Patterns)是软件开发中用来解决常见问题的通用、可重用的解决方案。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式并不是直接可用的代码,而是模板或指导原则,开发者可以根据具体需求加以实现。
1.1、六大原则(SOLID)
- 单一职责原则(Single Responsibility Principle, SRP)
- 啥意思:一个类或者模块只负责一件事。就像一个厨师专心炒菜,不让他一边炒菜一边洗碗。
- 为啥重要:如果一个类只干一件事,当需求变了,你只需要改这个类,而且改的原因只有一个。这样出错的机会少了,维护起来也简单。
- 开放-封闭原则(Open-Closed Principle, OCP)
- 啥意思:软件应该欢迎扩展,但拒绝修改。就像玩乐高积木,你可以加新零件拼出新形状(扩展),但不用拆掉原来的积木(修改)。
- 为啥重要:需要加新功能时,不用动老代码,直接加新代码就行。这样不会搞乱原来的东西,系统也能保持稳定。
- 里氏替换原则(Liskov Substitution Principle, LSP)
- 啥意思:子类可以代替父类用,而且不会搞砸程序。就像你可以用鸭子代替鸟类,因为鸭子是鸟的一种,但不能用企鹅代替鸟类去飞。
- 为啥重要:保证子类继承父类时不会出乱子,程序用起来还是靠谱的。
- 依赖倒置原则(Dependency Inversion Principle, DIP)
- 啥意思:高层模块和低层模块都应该依赖抽象,而不是直接依赖对方。就像你用插头插电器,不用管电咋来的,只要插头插座匹配就行。
- 为啥重要:靠抽象(比如接口)联系起来,模块之间就不那么死绑在一起了,改起来灵活,扩展也方便。
- 接口隔离原则(Interface Segregation Principle, ISP)
- 啥意思:别逼一个类去实现它用不上的接口。就像去饭店点菜,菜单只给你想吃的,别把所有菜都塞给你挑。
- 为啥重要:类不用干多余的活,代码不乱,维护起来也轻松。
- 迪米特法则(Law of Demeter, LoD)
- 啥意思:一个对象尽量少跟其他对象直接打交道。就像你跟朋友的朋友联系,最好通过你朋友,而不是直接找陌生人。
- 为啥重要:对象之间联系少了,耦合度低,改一个地方不会牵扯一大堆,系统更好维护。
1.2、分类
- 创建型模式(Creational Patterns)
-
- 啥意思:关注对象的创建过程,旨在提高对象实例化的灵活性和效率。
- 包括:单例模式、建造者模式、原型模式、工厂方法模式、抽象工厂模式。
- 结构型模式(Structural Patterns)
-
- 啥意思:关注类和对象之间的组合与关系,帮助构建清晰的系统结构。
- 包括:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式(Behavioral Patterns)
-
- 啥意思:关注对象之间的通信和职责分配,优化对象间的协作。
- 包括:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
二、详解
2.1.创建型模式(Creational Patterns)
2.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,证明是同一个实例
}
}
- 懒汉式(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、建造者模式
建造者模式将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。通过使用建造者模式,开发者可以更灵活地创建复杂对象,并且可以更容易地维护和扩展这些对象的构建过程。
通俗讲:想象你去一家汉堡店点餐。你跟服务员说:"我要一个牛肉汉堡,加奶酪、生菜,再配个可乐和薯条。" 服务员点点头,按照你的要求一步步把汉堡、饮料、配餐组装好,最后交给你一个完整的套餐。这个过程就很像建造者模式。
几种情况:
- 对象创建很复杂
如果一个对象需要好几步才能创建完成,或者有好多可选的配置(比如电脑的CPU、内存、硬盘等),用建造者模式可以把过程拆开,变得更清晰。
- 对象有多种变体
比如你想要一台游戏电脑、一台办公电脑,它们都是"电脑",但配置不同。建造者模式能让你用同一个建造过程,创建不同配置的对象。
- 需要不可变对象
如果你希望对象一旦创建就不能改(像 Java 的 String 类),但创建时又需要设置很多属性,建造者模式是个好选择。
- 代码更易读
相比一大堆构造函数参数,或者 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、原型模式
通俗想:你有一张特别好看的照片,想再弄一张一模一样的。你有两个选择:
- 重新拍一张:找相机、摆姿势、调光线,费时费力。
- 直接复印:把这张照片拿去复印机,咔嚓一下,几秒钟搞定。
通过"复制"现有的对象来创建新对象,而不是从零开始构造
原型模式就是"复印"这个思路:在程序里,我们已经有了一个对象(比如照片),想要一个新对象时,直接"克隆"它,而不是重新从头构造。这样既快又省资源。
在软件开发中,原型模式的好处是:
- 你不用关心对象是怎么创建的,只要会复制就行。
- 对于创建成本高的对象(比如需要查数据库、复杂计算),克隆能大大提高效率。
分类
- 浅克隆(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 也变了,因为它们指向同一个地址对象
- 深克隆(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、工厂模式
分类
- 简单工厂(Simple Factory)
-
- 啥意思:简单工厂就像一个万能小作坊,你告诉它"我想要个啥",它就给你做啥。比如,你去披萨店说"我要个海鲜披萨",店员直接给你烤一个出来。
- 特点:就一个工厂,啥都能干,你给个参数(比如"海鲜"或"芝士"),它就知道造啥。
- 工厂方法(Factory Method)
-
- 啥意思:工厂方法就像把大工厂拆成几个专业车间,每个车间只干一件事。比如汽车厂里,轿车车间只造轿车,SUV车间只造SUV,你想要啥就去对应的车间。
- 特点:每个工厂只管一种产品,工厂和产品一对一。
- 抽象工厂(Abstract Factory)
-
- 啥意思:抽象工厂是个"大管家",能给你整一套东西。比如一个家具厂,你说"我要现代风格的家具",它就给你做一套现代风格的桌子、椅子和沙发,全都搭配好。
- 特点:一个工厂能造一组相关的产品,强调的是"产品家族"。
使用场景
- 简单工厂
-
- 啥时候用:
-
-
- 你需要根据条件(比如用户输入)造不同的东西。
- 东西种类不多,逻辑简单。
- 小项目或刚开始开发时用着方便。
-
咋回事:简单工厂就一个类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手机打电话
}
}
- 工厂方法
-
- 啥时候用:
-
-
- 你想把"造东西"和"用东西"分开,让子类决定造啥。
- 以后可能会加新东西,种类会变多。
- 需要灵活扩展的时候。
-
咋回事:工厂方法给每种手机配了个专属工厂(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手机打电话
}
}
- 抽象工厂
-
- 啥时候用:
-
-
- 你需要一整套搭配好的东西,比如一套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,暂时把大白话写到这里,尽情期待下一篇《大白话说清楚设计模式(二)》