# DesignPattern **Repository Path**: soria391206/DesignPattern ## Basic Information - **Project Name**: DesignPattern - **Description**: Java常见的八种设计模式:单例模式、工厂模式、抽象工厂模式、建造者模式、策略模式、装饰器模式、适配器模式和观察者模式。 - **Primary Language**: Java - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-22 - **Last Updated**: 2025-04-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 设计模式共有23种,分为创建型、结构型和行为型。此处仅仅介绍常见的八种设计模式:**单例模式、工厂模式、抽象工厂模式、建造者模式、策略模式、装饰器模式、适配器模式和观察者模式**。 # 设计模式原则 ### 单一职责原则 **一个对象只包含单一的职责,并且该职责被完整地封装在一个类中。** --- ```java public class Person { public void teach() { System.out.println("我可以教学生学习"); } public void takeaway() { System.out.println("我要去送外卖"); } public void screw() { System.out.println("我在大厂打螺丝"); } } ``` 上述 Person 类中,包含不同的功能太多,根据单一职责原则,我们应该进行更详细的划分,保证一个对象只包含一种功能。 ```java class Teacher { public void teach() { System.out.println("我可以教学生学习"); } } class TakeawayClerk { public void takeaway() { System.out.println("我要去送外卖"); } } class ScrewWorker { public void screw() { System.out.println("我在大厂打螺丝"); } } ``` ### 开闭原则 **对扩展开放,对修改关闭。** --- ```java interface Teacher { void teach(); } class ChineseTeacher implements Teacher { @Override public void teach() { System.out.println("我是语文老师,我可以教学生中文"); } } class MathTeacher implements Teacher { @Override public void teach() { System.out.println("我是数学老师,我可以教学生数学"); } } class EnglishTeacher implements Teacher { @Override public void teach() { System.out.println("我是英语老师,我可以教学生英语"); } } ``` 不同的老师要做的工作都是教学生,但具体怎么教由各科老师决定。因此可以将 teach 抽象为一个接口或抽象类,让不同的 Teacher 都实现这个接口,并自由决定自己如何对学生进行教学。这就是**对扩展开放**。而每个 Teacher 具体干什么都是自己在负责,不会被其他 Teacher 干涉。这就是**对修改关闭**。 ### 里氏代换原则 **子类可以扩展父类的功能,但不能改变父类原有的功能。任何父类可以出现的地方,子类一定可以出现。** --- ```java class Coder { public void code() { System.out.println("我会写代码"); } class JavaCoder extends Coder { public void game() { System.out.println("我会玩游戏"); } @Override public void code() { System.out.println("我不会代码"); } } } ``` **子类可以实现父类的方法,但不能覆盖父类的方法。** 上述子类 JavaCoder 对父类 Coder 的 code 方法进行了重写,不具备父类原来的行为,因此不满足里氏代换原则。 **子类可以扩展自己特有的方法。** 上述子类扩展了属于自己的 game 方法,如果不对 code 方法进行重写,那么符合里氏代换原则。 ### 依赖倒转原则 顶层模块不依赖底层模块,都依赖抽象。抽象不依赖细节,细节应该依赖于抽象。总结后就是**对接口编程,依赖于抽象而不依赖于具体。** --- ```java class UserMapper { // 基础CRUD ... } class UserService { // 实现业务代码 UserMapper userMapper = new UserMapper(); ... } class UserController { // 使用业务代码 UserService userService = new UserService(); ... } ``` 现有上述实现业务的模块。我们发现,假设对 User 的相关数据库操作更换为新的实现,我们不仅仅需要更改 UserService,还需要更改 UserController。上面的模块具有很高的关联性,虽然逻辑清晰,但底层模块一旦修改,大量的顶层模块都会因此而改动。 ```java interface UserMapper { // 定义CRUD } class UserMapperImpl implements UserMapper { // 实现CRUD } interface UserService { // 定义业务代码 } class UserServiceImpl implements UserService { // 实现业务代码 @Resource private UserMapper userMapper; } class UserController { // 使用业务代码 @Resource private UserService userService; } ``` 通过接口弱化原来的关联,我们只需要知道接口中定义了什么方法然后去使用即可,而具体的操作由接口的实现类来完成。即使底层模块被修改,顶层模块只需要使用底层模块接口中的方法即可,不需要修改具体的代码。 ### 接口隔离原则 **使用多个隔离的接口,而不是使用不需要的单个接口。** --- ```java interface Device { String cpu(); String gpu(); String type(); } class Computer implements Device { @Override public String cpu() { return "AMD 9950X3D"; } @Override public String gpu() { return "Geforce RTX 5090"; } @Override public String type() { return "计算机"; } } class Earphone implements Device { @Override public String cpu() { return null; } @Override public String gpu() { return null; } @Override public String type() { return "耳机"; } } ``` 上述我们可以发现,虽然 Computer 和 Earphone 都属于 Device,但是 Device 中有很多 Earphone不需要实现的方法,因此我们应该对上述接口再进行细分。 ```java interface BaseDevice { String type(); } interface SmartDevice { String cpu(); String gpu(); } class Computer implements SmartDevice, BaseDevice { @Override public String type() { return "计算机"; } @Override public String cpu() { return "AMD Threadripper 5995WX"; } @Override public String gpu() { return "NVIDIA A200"; } } class Earphone implements BaseDevice { @Override public String type() { return "耳机"; } } ``` 通过设计多个接口,让类根据需要去实现需要的接口,不仅能够降低耦合度,还能更好的进行维护。 ### 合成复用原则 **优先使用合成,而不是继承。** --- ```java class Mother { public void eat() { System.out.println("喝粥"); } } class Baby extends Mother { public void needEat() { System.out.println("看见妈妈吃我也想吃"); eat(); } } ``` 如果未来 eat 不再由 Mother 负责,而是由 Father 负责,那么我们就需要在 Baby 中对所有复用 Mother 方法的地方进行修改。并且由于是继承,会将父类中的实现细节暴露给子类,这是不安全的。 我们可以采用如下方式: ```java class Mother { public void eat() { System.out.println("吃饭"); } } class Baby { public void needEat(Mother mother) { System.out.println("宝宝需要吃饭"); mother.eat(); } } ``` 在 Baby 需要的时候去使用 Mother 中的方法,可以很大的降低类和类之间的耦合度。如果使用接口的话,将会更加灵活。 ### 迪米特法则 **一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。** --- 类与类或者模块与模块之间,交互越少越好。(还是降低耦合度) ```java class Goods { private double price; public Goods(double price) { this.price = price; } public double getPrice() { return price; } } class Customer { public void purchase(Goods goods) { System.out.println("Purchasing " + goods.getPrice()); } } public class Person { public static void main(String[] args) { Customer customer = new Customer(); customer.purchase(new Goods(12.99)); } } ``` 上述设计没有问题,但是违背了迪米特法则。因为 purchase() 方法仅仅只需要 price,而不是整个 Goods 类,我们只需要将 price 传过去即可。 ```java ... class Customer { public void purchase(double price) { System.out.println("Purchasing " + price); } } public class Person { public static void main(String[] args) { Customer customer = new Customer(); customer.purchase(new Goods(12.99).getPrice()); } } ``` # 八大设计模式 ## 单例模式(Singleton) 单例模式是Java中最常见的设计模式。单例模式仅有一个实例化对象,并提供一个全局访问点。单例模式的核心是**将类的构造方法私有化,以防止外部直接通过构造函数创建实例。同时,类内部需要提供一个静态方法或变量来获取该类的唯一实例。** 单例模式分为饿汉式和懒汉式。饿汉式在一开始就创建好了实例化对象,懒汉式在需要获取实例化对象时才进行创建。 ```java // 饿汉式 class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } // 懒汉式 class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 由于懒汉式在方法内部进行实例化对象,因此其在多线程中并不安全,使用`synchronized`关键字又会导致在高并发下效率低。我们可以利用**静态内部类**的特性实现懒汉式。 ```java class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 如果我们仅仅使用 Singleton 类而没有使用 SingletonHolder 类,那么 SingletonHolder 类便不会被初始化。只有在真正使用它时,例如上述的 getInstance,才会进行类初始化。 ## 工厂模式(Factory) ### 简单工厂模式 我们一般使用`new`去创建对象。当我们的程序中大量使用这种方案创建对象时,如果该对象的构造方法或者类名发生了修改,那我们每一处使用了该对象的地方都需要修改。因此我们可以将频繁使用的对象封装为一个工厂类,当我们需要对象时,直接调用工厂类中的方法来为我们生成对象。如果某个类出现了变动,我们也只需要修改工厂中的代码,而不是大面积地进行修改。 ```java // 产品抽象类 abstract class Product { private String name; public Product(String name) { this.name = name; } } // 产品A class ProductA extends Product { public ProductA() { super("ProductA"); } } // 产品B class ProductB extends Product { public ProductB() { super("ProductB"); } } // 产品工厂 public class Factory { public static Product createProduct(String name) { switch (name){ case "ProductA": return new ProductA(); case "ProductB": return new ProductB(); default: return null; } }} ``` 上面定义了一个产品工厂,现在我们可以直接向 Factory 索取对象: ```java Product product = Factory.createProduct("ProductA"); ``` ### 普通工厂模式 但是我们能够发现,如果此时新增一个 ProductC,就需要去修改 Factory 中的 createFactory 方法,很显然不符合开闭原则。 ```java public abstract class Factory { abstract T getProduct(); } public class FactoryA extends Factory { @Override ProductA getProduct() { return new ProductA(); } } public class FactoryB extends Factory { @Override ProductB getProduct() { return new ProductB(); } } ``` 我们可以利用泛型,利用子类确定产品类型。这样如果我们需要新增一个 ProductC,直接新建一个工厂 FactoryC 即可,不需要修改工厂类,符合开闭原则。工厂还屏蔽了对象的创建细节,我们只需要关注如何去使用对象即可。 ## 抽象工厂模式(Abstract Factory) 如果产品类型有很多,例如 Food,按照上述普通工厂模式,就要新建大量的工厂。因此我们可以按照产品类型划分,将相同的产品类型划分到同一个工厂中。 ```java public class FoodA { ... } public class FoodB { ... } public class FoodC { ... } public abstract class AbstractFactory { abstract FoodA getFoodA(); abstract FoodB getFoodB(); abstract FoodC getFoodC(); } ``` 但是,如果某个产品类别新增了产品,就需要对产品工厂新增生产方法。因此,**抽象工厂模式违背了开闭原则。** 但一个工厂可以生产同一个产品类的所有产品,按类别进行分类,显然比之前的普通厂模式更好。 ## 建造者模式(Builder) 建造者模式也是非常常见的设计模式。例如,Java中的 StringBuilder就是建造者模式。在使用过 StringBuilder 之后,我们可以发现,**建造者模式就是不断配置参数或内容,直到配置完成后,再进行对象的构建。** ```java class Employee { int id; String name; int managerId; Date hiredate; double salary; int departmentId; String departmentName; public Employee(int id, String name, int managerId, Date hiredate, double salary, int departmentId, String departmentName) { } } ``` 如果我们的类属性非常多,直接通过 new 的方式去填充参数就会非常麻烦。我们可以用建造者模式去修改这个类,变成逐步填充参数: ```java class Employee { // 私有构造函数 private Employee(int id, String name, int managerId, Date hiredate, double salary, int departmentId, String departmentName) { } public static EmployeeBuilder builder() { return new EmployeeBuilder(); } public static class EmployeeBuilder { private int id; private String name; private int managerId; private Date hiredate; private double salary; private int departmentId; private String departmentName; // 对属性赋值,并返回自身,形成链式调用 public EmployeeBuilder id(int id) { this.id = id; return this; } public EmployeeBuilder name(String name) { this.name = name; return this; } ... // 最后调用建造者提供的 build 方法完成对象的构建 public Employee build() { return new Employee(id, name, managerId, hiredate, salary, departmentId, departmentName); } } } ``` 现在我们就可以使用建造者模式构建这个对象了: ```java Employee employee = Employee.builder() .id(1) .name("John") . ... .build(); ``` ## 策略模式(Strategy) 策略模式是**我们手动设定一种策略,这样对象之后的行为将由我们所指定的策略而决定。** 策略模式主要由三部分组成:策略接口、策略实现类和策略上下文类。策略接口定义了某种方法的统一接口,策略实现类负责实现策略接口中定义的方法,而策略上下文类持有一个策略接口的引用,负责调用具体策略类中的方法。 ```java // 策略接口 interface Strategy { void execute(); } // 策略实现 class ConcreteStrategyA implements Strategy { @Override public void execute() { System.out.println("Executing Strategy A"); } } // 策略实现 class ConcreteStrategyB implements Strategy { @Override public void execute() { System.out.println("Executing Strategy B"); } } // 策略上下文 class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } } ``` 定义好上述策略模式后,我们就可以手动设置策略,确定要执行哪一个类的 execute 方法了。 ```java Context context = new Context(new ConcreteStrategyA()); context.executeStrategy(); // ---------- Context context = new Context(new ConcreteStrategyB()); context.executeStrategy(); ``` ## 装饰器模式(Decorator) 装饰器模式用于**向一个对象添加额外的行为而不需要修改原来的类,并且通过组合的形式完成,而不是传统的继承。** ```java interface Service { // 业务方法 void test(); } class ServiceImpl implements Service { // 业务方法的实现 @Override public void test() { } } class Decorator implements Service { private Service service; public Decorator(Service service) { this.service = service; } @Override public void test() { // 调用被装饰对象的业务方法 service.test(); } } class DecoratorImpl extends Decorator { public DecoratorImpl(Service service) { super(service); } @Override public void test() { System.out.println("装饰方法,在原有功能之前"); super.test(); System.out.println("装饰方法,在原有功能之后"); } } ``` 定义好装饰器类后,我们就可以对原有的业务方法进行装饰: ```java Service service = new ServiceImpl(); Decorator decorator = new DecoratorImpl(service); // service.test(); decorator.test(); ``` ## 适配器模式(Adapter) 现实中常见的适配器有:设备接口不适配时使用接口扩展坞来转化为适配的接口,电源适配器将高电压交流电转换成设备需要的低电压直流电。在程序中,适配器模式用于**将一个已有类的接口转换成需要的接口形式,适配器让一个类的接口与另一个类的接口相适应。** 适配器分为类适配器和对象适配器。 ### 类适配器 ```java interface Fruit { void eat(); } class Apple { public void eat() { System.out.println("吃苹果"); } } public class Test { public static void main(String[] args) { Apple apple = new Apple(); test(???); // 不适用于 Apple } public static void test(Fruit fruit) { fruit.eat(); } } ``` 现有一个 test 方法需要 Fruit 类型的对象,但我们的 Apple 显然不适用,因此就需要一个类适配器,让 Apple 能够适用于 test 方法,并且能够成功执行 Apple 中的 eat 方法。 ```java class Adapter extends Apple implements Fruit { @Override public void eat() { super.eat(); } } public class Test { public static void main(String[] args) { Fruit fruit = new Adapter(); test(fruit); } public static void test(Fruit fruit) { fruit.eat(); } } ``` ### 对象适配器 类适配器中占用了继承的位置,如果 Fruit 不是接口而是抽象类的话,由于Java不支持多继承,那么上述方法将不再适用,根据合成复用原则,我们应该优先使用合成的方式,而不是继承。因此我们可以使用对象适配器: ```java class Adapter implements Fruit { private Apple apple; public Adapter(Apple apple) { this.apple = apple; } @Override public void eat() { apple.eat(); } } ``` 我们将对象以组合的形式存放在 Adapter 中,这样就能通过存放的对象完成具体实现。 ## 观察者模式(Observer) 在观察者模式中,**当一个对象状态改变时,所有依赖它的对象都能收到通知并自动更新。** 观察者模式有两种对象:观察者和被观察者(称之为主题)。观察者在主题中被注册,在主题的状态发生改变时能够接收通知。主题会维护一组观察者,并在自身状态改变时给观察者发送通知。主题通过调用观察者的统一接口来通知它们状态的变化。 ```java // 观察者接口 interface Observer { void update(); } // 具体观察者A class ConcreteObserverA implements Observer { @Override public void update() { } } // 具体观察者B class ConcreteObserverB implements Observer { @Override public void update() { } } // 主题接口 interface Subject { // 注册观察者 void registerObserver(Observer observer); // 移除观察者 void removeObserver(Observer observer); // 通知观察者 void notifyObserver(); } // 具体主题 class ConcreteSubject implements Subject { Set observerSet = new HashSet<>(); @Override public void registerObserver(Observer observer) { observerSet.add(observer); } @Override public void removeObserver(Observer observer) { observerSet.remove(observer); } @Override public void notifyObserver() { for (Observer observer : observerSet) { observer.update(); } } } ``` 这样我们就实现了一个简单的观察者模式: ```java public class Test { public static void main(String[] args) { // 创建主题 ConcreteSubject subject = new ConcreteSubject(); // 创建具体观察者 ConcreteObserverA observerA = new ConcreteObserverA(); ConcreteObserverB observerB = new ConcreteObserverB(); // 注册观察者 subject.registerObserver(observerA); subject.registerObserver(observerB); // 状态发生改变时通知观察者 subject.notifyObserver(); } } ```