玄子Share-GOF设计模式全23种 + 七大设计原则
前言:
此文主要内容为
- 面向对象七大设计原则(OOD Principle)
- GOF(Gang Of Four)23种设计模式
- 拓展的两个设计模式
- 简单工厂模式(Simple Factory Pattern)
- 空对象模式(Null Object Pattern)
- 以及工厂模式与策略模式(Factory And Strategy Pattern)之间的区别,两个设计模式较为相似,细节上需要分清
每个设计模式都分为了八步介绍
- 先对设计模式进行简单介绍,尽量一句话概述,方便记忆
- 再以现实生活中的场景举例,抛出问题
- 使用对应的设计模式解决问题
- 提供了方便理解的类关系图
- 然后指出适用于当前设计模式的场景
- 当前设计模式编码时的易错点
- 当前设计模式的优缺点
- 最后再次对当前设计模式进行总结
使用代码案例时,可复制整个代码块,先粘贴至一个类中,再拆分
如有疑问添加文章封面联系方式
目录
[TOC]
设计模式分类 GOF(Gang Of Four)
GOF(Gang Of Four)是四位计算机科学家(Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)在1994年出版的书籍《设计模式:可复用面向对象软件的基础》中提出的一种设计模式分类和解决方案。该书介绍了23种常用的设计模式,分为创建型、结构型和行为型三大类。这本书的社会认可度非常高,被公认为设计模式领域的经典之作。
- 影响深远:GOF的设计模式成为了软件工程领域的经典之一,对于软件设计和架构具有广泛的应用。许多软件开发人员和团队都在日常工作中使用这些模式来解决常见的设计问题。
- 经典的设计原则:GOF的设计模式不仅提供了具体的解决方案,还强调了面向对象设计的一些重要原则,如封装、继承、多态和接口分离等。
- 共同的设计术语:GOF提出的模式在软件开发社区中创建了共同的设计术语和设计思维方式,使得开发人员之间更容易进行沟通和理解。
- 可复用性和可维护性:通过应用这些设计模式,开发人员可以更好地设计出具有可复用性和可维护性的软件系统,降低了代码的耦合性。
GOF的设计模式在软件开发领域得到了广泛的认可和应用,成为了许多软件开发人员必备的知识和工具之一。它为软件设计和架构提供了有力的指导,帮助开发人员构建高质量、可扩展的软件系统。
创建型模式(Creational Patterns)
序号 | 中文名称 | 英文名称 | 所用设计原则 |
---|---|---|---|
1 | 工厂方法模式 | Factory Method Pattern | SRP、OCP、LSP、DIP |
2 | 抽象工厂模式 | Abstract Factory Pattern | SRP、OCP、LSP、DIP |
3 | 单例模式 | Singleton Pattern | 无直接关联设计原则 |
4 | 原型模式 | Prototype Pattern | SRP、OCP、LSP、DIP |
5 | 建造者模式 | Builder Pattern | SRP、OCP、LSP、DIP |
结构型模式(Structural Patterns)
序号 | 中文名称 | 英文名称 | 所用设计原则 |
---|---|---|---|
6 | 适配器模式 | Adapter Pattern | SRP、OCP、LSP、ISP、DIP |
7 | 桥接模式 | Bridge Pattern | SRP、OCP、LSP、ISP、DIP |
8 | 组合模式 | Composite Pattern | SRP、OCP、LSP、ISP、DIP |
9 | 装饰模式 | Decorator Pattern | SRP、OCP、LSP、ISP、DIP |
10 | 外观模式 | Facade Pattern | SRP、OCP、LSP、ISP、DIP |
11 | 享元模式 | Flyweight Pattern | SRP、OCP、LSP、ISP、DIP |
12 | 代理模式 | Proxy Pattern | SRP、OCP、LSP、ISP、DIP |
行为型模式(Behavioral Patterns)
序号 | 中文名称 | 英文名称 | 所用设计原则 |
---|---|---|---|
13 | 责任链模式 | Chain of Responsibility Pattern | SRP、OCP、LSP、ISP、DIP |
14 | 命令模式 | Command Pattern | SRP、OCP、LSP、ISP、DIP |
15 | 解释器模式 | Interpreter Pattern | SRP、OCP、LSP、ISP、DIP |
16 | 迭代器模式 | Iterator Pattern | SRP、OCP、LSP、ISP、DIP |
17 | 中介者模式 | Mediator Pattern | SRP、OCP、LSP、ISP、DIP |
18 | 备忘录模式 | Memento Pattern | SRP、OCP、LSP、ISP、DIP |
19 | 观察者模式 | Observer Pattern | SRP、OCP、LSP、ISP、DIP |
20 | 状态模式 | State Pattern | SRP、OCP、LSP、ISP、DIP |
21 | 策略模式 | Strategy Pattern | SRP、OCP、LSP、ISP、DIP |
22 | 模板方法模式 | Template Method Pattern | SRP、OCP、LSP、ISP、DIP |
23 | 访问者模式 | Visitor Pattern | SRP、OCP、LSP、ISP、DIP |
面向对象设计原则(OOD Principle)
面向对象设计原则(OODP)Object-Oriented Design Principle
- 面向对象设计原则是一组指导性准则,是设计模式的基础,用于帮助软件开发人员设计和构建高质量、灵活、可维护和可扩展的面向对象软件系统。
- 遵循这些原则有助于提高代码的质量,减少重构的需求,避免常见的设计问题并提高软件系统的可靠性和可重用性,帮助我们构建出更好的软件。
单一职责原则(SRP)
单一职责原则(SRP)Single Responsibility Principle
- 每个类或模块应该有且只有一个单一的责任。
- 换句话说,一个类应该只负责完成一个明确定义的功能。
- 这样做可以降低类的复杂性,提高类的可维护性和可重用性。
开闭原则(OCP)
开闭原则(OCP)Open/Closed Principle
- 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
- 这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改现有代码。
- 这样可以保持现有功能的稳定性,并降低引入新功能时引入错误的风险。
里氏替换原则(LSP)
里氏替换原则(LSP)Liskov Substitution Principle
- 子类应该能够替换其父类并出现在任何使用父类的地方,而不会影响程序的正确性。
- 这就要求子类必须保持父类的行为,即子类不能修改父类的行为。
接口隔离原则(ISP)
接口隔离原则(ISP) Interface Segregation Principle
- 不应该强迫客户端依赖于它们不使用的接口。
- 接口应该被细化,只包含客户端需要的方法,这样可以降低类之间的耦合度,提高系统的灵活性和可维护性。
依赖倒置原则(DIP)
依赖倒置原则(DIP)Dependency Inversion Principle
- 高层模块不应该依赖于低层模块,而是应该依赖于抽象。
- 抽象不应该依赖于具体实现,而是具体实现应该依赖于抽象。
- 这样可以降低模块之间的耦合度,提高代码的可测试性和可扩展性。
迪米特法则(LOD)
迪米特法则(LoD)Law Of Demeter
- 也称为最少知识原则。
- 一个对象应该对其他对象有尽可能少的了解,只与直接的朋友进行交互。
- 这样可以降低类之间的耦合度,使系统更加灵活和易于维护。
合成复用原则(CRP)
合成复用原则(CRP)Composite Reuse Principle
- 尽量使用组合/聚合的方式而不是继承关系达到软件复用的目的,是 has-a 关系,而不是通过继承。
- 该原则促使开发者在设计中更多地考虑对象之间的组合关系,以达到模块化、灵活性和可维护性的目标。
创建型模式 (Creational Patterns)
创建型模式是设计模式中的一类,主要关注如何创建对象以及对象的实例化过程。它们提供了一种灵活、可复用的方式来创建对象,同时隐藏了对象创建的细节,从而降低了系统的耦合性。
这些创建型模式各自适用于不同的场景,可以根据具体需求来选择合适的设计模式来实现对象的创建和初始化。它们的共同目标是降低对象的创建和使用之间的耦合,提供更加灵活和可扩展的代码结构。
工厂方法模式(Factory Method Pattern)
在学习工厂方法模式之前建议先学习文章最后一章《必要拓展》中的简单工厂模式(Simple Factory Pattern)
1. 简单介绍
工厂方法模式(Factory Method Pattern)定义一个创建对象的接口,但由子类决定具体实例化哪个类。客户端只需要知道工厂接口,而不关心具体的产品类。
2. 实际问题
假设你是一家汽车制造公司,你有多个型号的汽车需要生产,每种型号的汽车有不同的配置和特性。你需要一个灵活的方式来生产不同型号的汽车,并且在将来能够轻松添加新的汽车型号。
3. 解决方案
- 定义一个产品接口,规范产品的行为。
- 创建一个工厂接口,包含一个工厂方法用于创建产品对象。
- 对每个具体产品,创建一个具体产品类,实现产品接口。
- 对每个具体产品,创建一个具体工厂类,实现工厂接口,负责创建具体产品的实例。
// 产品接口:汽车
interface Car {
void assemble();
}
// 具体产品:SUV 汽车
class SUVCar implements Car {
@Override
public void assemble() {
System.out.println("组装SUV汽车");
}
}
// 具体产品:轿车
class SedanCar implements Car {
@Override
public void assemble() {
System.out.println("组装轿车");
}
}
// 工厂接口:汽车工厂
interface CarFactory {
Car createCar();
}
// 具体工厂:SUV 汽车工厂
class SUVCarFactory implements CarFactory {
@Override
public Car createCar() {
return new SUVCar();
}
}
// 具体工厂:轿车工厂
class SedanCarFactory implements CarFactory {
@Override
public Car createCar() {
return new SedanCar();
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
System.out.println("===========SUV===========");
Car suvCar = new SUVCarFactory().createCar();
suvCar.assemble();
System.out.println("===========Sedan===========");
Car sedanCar = new SedanCarFactory().createCar();
sedanCar.assemble();
}
}
4. 类关系图
5. 应用场景
- 当一个类无法预先知道它需要创建的对象的确切类时,使用工厂方法可以延迟对象的实例化,使得具体的实例化由子类来决定。
- 当需要在运行时动态地选择创建某个类的对象时,工厂方法提供了一种灵活的解决方案。
6. 易错点
- 忘记实现产品接口:每个具体产品都必须实现产品接口,否则无法通过工厂方法创建产品对象。
- 忘记实现工厂接口:每个具体工厂都必须实现工厂接口,以提供工厂方法来创建产品对象。
7. 优缺点
优点
- 通过工厂方法,将产品的创建与使用解耦,客户端不需要知道具体产品的类名,只需要通过工厂接口来创建对象,降低了耦合度。
- 具体产品的新增和变更对客户端代码没有影响,只需要新增或修改相应的具体工厂类即可。
缺点
- 每新增一个具体产品,都需要新增一个相应的具体工厂类,导致类的个数增加,增加了系统的复杂性。
8. 总结
工厂方法模式是一种创建型设计模式,通过将产品的创建与使用解耦,让子类决定实例化哪个类。这样可以提高代码的灵活性和可扩展性,使得系统的演化和维护更加容易。虽然增加了类的个数,但对于复杂系统的开发来说,这是值得的权衡。在需要动态地选择创建对象,或者有多个类似产品等级结构的情况下,工厂方法模式是一个很好的选择。
抽象工厂模式(Abstract Factory Pattern)
1. 简单介绍
抽象工厂模式(Abstract Factory Pattern)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂可以创建多个不同类型的产品。
2. 实际问题
假设你是一家家具制造公司,你有多种类型的家具,如沙发、床、桌子等,每种类型的家具有不同的风格和材料。你需要一种灵活的方式来生产不同类型和风格的家具,并且在将来能够轻松添加新的家具类型和风格。
3. 解决方案
// 产品接口:沙发
interface Sofa {
void sitOn();
}
// 产品接口:床
interface Bed {
void sleepOn();
}
// 具体产品:现代风格沙发
class ModernSofa implements Sofa {
@Override
public void sitOn() {
System.out.println("坐在现代风格的沙发上");
}
}
// 具体产品:现代风格床
class ModernBed implements Bed {
@Override
public void sleepOn() {
System.out.println("睡在现代风格的床上");
}
}
// 具体产品:古典风格沙发
class ClassicalSofa implements Sofa {
@Override
public void sitOn() {
System.out.println("坐在古典风格的沙发上");
}
}
// 具体产品:古典风格床
class ClassicalBed implements Bed {
@Override
public void sleepOn() {
System.out.println("睡在古典风格的床上");
}
}
// 抽象工厂接口:家具工厂
interface FurnitureFactory {
Sofa createSofa();
Bed createBed();
}
// 具体工厂:现代风格家具工厂
class ModernFurnitureFactory implements FurnitureFactory {
@Override
public Sofa createSofa() {
return new ModernSofa();
}
@Override
public Bed createBed() {
return new ModernBed();
}
}
// 具体工厂:古典风格家具工厂
class ClassicalFurnitureFactory implements FurnitureFactory {
@Override
public Sofa createSofa() {
return new ClassicalSofa();
}
@Override
public Bed createBed() {
return new ClassicalBed();
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
System.out.println("===========现代家具===========");
ModernFurnitureFactory modernFurnitureFactory = new ModernFurnitureFactory();
Bed modernBed = modernFurnitureFactory.createBed();
modernBed.sleepOn();
Sofa modernSofa = modernFurnitureFactory.createSofa();
modernSofa.sitOn();
System.out.println("======古典家具========");
ClassicalFurnitureFactory classicalFurnitureFactory = new ClassicalFurnitureFactory();
Bed classicaBed = classicalFurnitureFactory.createBed();
classicaBed.sleepOn();
Sofa classicaSofa = classicalFurnitureFactory.createSofa();
classicaSofa.sitOn();
}
}
4. 类关系图
5. 应用场景
- 当需要创建一组相关或相互依赖的对象时,可以使用抽象工厂模式。例如,创建不同风格的家具,或者创建不同主题的界面元素等。
- 当希望系统在未来能够支持新的产品种类或产品族,而不需要修改现有代码时,抽象工厂模式提供了一种扩展的解决方案。
6. 易错点
- 在添加新的产品族时,需要修改抽象工厂接口及其所有子类,可能导致较大的改动。
- 在添加新的产品等级结构时,需要修改抽象工厂接口及其所有子类,可能导致较大的改动。
7. 优缺点
优点
- 将一组相关的产品封装在一起,客户端使用抽象接口来创建产品,不需要关心具体的实现细节,降低了客户端和具体产品类之间的耦合。
- 可以轻松替换不同的工厂类来创建不同的产品族,增加新的产品族也方便,符合开闭原则。
缺点
- 增加新的产品族或产品等级结构时,需要修改抽象工厂接口及其所有子类,可能导致较大的改动。
- 当产品族和产品等级结构过于复杂时,抽象工厂模式的类的数量可能会增加,导致系统复杂性增加。
8. 总结
抽象工厂模式是一种创建型设计模式,通过提供一个接口来创建一组相关或相互依赖的对象,将一系列产品封装在一起,使得客户端不需要知道具体产品的类名,只需使用抽象接口即可。抽象工厂模式适用于创建一组相关的产品,且希望系统能够轻松支持新的产品族或产品等级结构的场景。在使用抽象工厂模式时,需要注意在添加新的产品族或产品等级结构时,可能需要修改较多的代码。
单例模式(Singleton Pattern)
1. 简单介绍
单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个全局访问点。这样可以控制对象的创建和访问,通常用于管理共享资源或全局配置。
2. 实际问题
在现实世界中,我们可能会遇到这样的问题:希望某个类在整个应用程序中只能拥有一个实例,不论在何处访问该类,始终获取到相同的唯一实例。
3. 解决方案
- 静态内部类
静态内部类单例模式是一种创建单例对象的方法,它通过使用静态内部类来实现懒加载和线程安全。
// 静态内部类单例
public class StaticInnerClassSingleton {
private static class StaticInnerClass {
private static final StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return StaticInnerClass.staticInnerClassSingleton;
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 获取懒汉式单例实例
StaticInnerClassSingleton instance1 = StaticInnerClassSingleton.getInstance();
StaticInnerClassSingleton instance2 = StaticInnerClassSingleton.getInstance();
// 验证是否为同一实例
System.out.println(instance1 == instance2);
// Output: true
}
}
- 懒汉式单例
懒汉式是指在首次使用时才创建实例。解决方案为在类内部定义一个私有静态变量作为该类的唯一实例,并提供一个公共静态方法来获取该实例。
// 懒汉式单例
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
// 私有构造方法,防止其他类通过 new 创建实例
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 获取懒汉式单例实例
LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
// 验证是否为同一实例
System.out.println(instance1 == instance2);
// Output: true
}
}
- 饿汉式单例
饿汉式是指在类加载时就创建实例。解决方案为在类定义时直接创建一个私有静态实例,并提供一个公共静态方法来获取该实例。
// 饿汉式单例
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
// 私有构造方法,防止其他类通过 new 创建实例
public static EagerSingleton getInstance() {
return instance;
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 获取饿汉式单例实例
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = EagerSingleton.getInstance();
// 验证是否为同一实例
System.out.println(instance1 == instance2);
// Output: true
}
}
- 双重校验锁单例
双重校验锁单例是一种在懒汉式基础上进行改进的解决方案,旨在减少不必要的同步开销。
// 双重校验锁单例
public class DoubleCheckedSingleton {
private volatile static DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
// 私有构造方法,防止其他类通过 new 创建实例
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 获取双重校验锁单例实例
DoubleCheckedSingleton instance1 = DoubleCheckedSingleton.getInstance();
DoubleCheckedSingleton instance2 = DoubleCheckedSingleton.getInstance();
// 验证是否为同一实例
System.out.println(instance1 == instance2);
// Output: true
}
}
- 枚举单例
枚举单例是一种简洁且线程安全的单例模式解决方案。在Java中,枚举类型是天然的单例,保证在任何情况下都只有一个实例。
// 枚举单例
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 获取枚举单例实例
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
// 验证是否为同一实例
System.out.println(instance1 == instance2);
// Output: true
}
}
在每个示例中,我们都通过相应的静态方法获取单例实例,并通过比较引用地址验证是否为同一实例。由于单例模式保证在整个应用程序中只有一个实例,所以输出结果都应该是
true
。
4. 类关系图
5. 应用场景
- 需要在整个应用程序中共享某个资源,例如配置信息、数据库连接等。
- 控制资源的并发访问,避免资源冲突。
6. 易错点
- 线程安全问题:懒汉式和双重校验锁单例在多线程环境下可能会创建多个实例,需要采取同步措施保证线程安全,但同步会影响性能。
- 反序列化问题:如果单例类实现了Serializable接口,当对象被反序列化时,可能会创建新的实例,破坏单例特性,需要通过readResolve()方法解决。
7. 优缺点
静态内部类单例模式
优点
- 单例对象只有在第一次使用时才会被创建,提高了性能和资源利用率。
- 静态内部类的加载过程由类加载器保证了线程安全,无需额外的同步措施。
- 实现简单,代码清晰,不依赖复杂的同步机制。
缺点
- 静态内部类单例模式无法传递参数给单例类的构造函数,因此在一些特殊情况下可能不适用。
懒汉式单例模式
优点
- 实现懒加载,只有在实例需要时才会创建。
- 简单易懂。
缺点
- 线程安全需要额外考虑,可能会影响性能。
- 反序列化可能会破坏单例特性。
饿汉式单例模式
优点
- 实现简单,不需要考虑线程安全问题。
- 线程安全,可以直接用于多线程环境。
缺点
- 不支持懒加载,可能导致资源浪费。
双重校验锁单例模式
优点
- 实现懒加载,只有在实例需要时才会创建。
- 在多线程环境下保持了较好的性能。
缺点
- 实现相对复杂,容易出错。
- JDK 1.5之前的版本中可能存在双重检查锁失效的问题。
枚举单例模式
优点
- 简洁且线程安全,天然保证只有一个实例。
- 支持其他方法和属性的定义。
缺点
- 不能实现懒加载,实例在类加载时就被创建。
8. 总结
- 静态内部类单例模式:通过静态内部类实现了懒加载和线程安全,代码简洁清晰。适合大多数情况下的单例需求。
- 饿汉式单例模式:在类加载时就创建实例,天生线程安全,但可能浪费资源,因为不管是否使用都会创建实例。
- 懒汉式单例模式:在第一次使用时才创建实例,节省了资源,但需要考虑线程安全问题,通常需要加锁来保证线程安全。
- 双重校验锁单例模式:结合了饿汉式和懒汉式的优点,懒加载且线程安全。但实现相对复杂,需要考虑指令重排问题。
- 枚举单例模式:使用枚举类型来实现单例,天生线程安全且防止反射和序列化攻击。简单、安全,但无法懒加载。
选择哪种单例模式应该根据具体需求来决定。静态内部类单例模式通常是一个很好的选择,因为它兼具了懒加载和线程安全的优点,而且实现简单。如果需要更高级的特性,如防止反射攻击,可以考虑使用枚举单例模式。
原型模式(Prototype Pattern)
1. 简单介绍
原型模式(Prototype Pattern)通过复制现有对象来创建新的对象,避免了使用常规构造函数来创建对象,从而提高性能和灵活性。
2. 实际问题
在现实世界中,我们可能会遇到这样的问题:希望创建一个对象的副本,并且可以根据原对象进行快速的复制和修改。
3. 解决方案
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象。Java中可以通过实现Cloneable接口和重写clone()方法来实现原型模式。
- 浅克隆
// 原型对象类
public class Prototype implements Cloneable {
private String data;
public Prototype(String data) {
this.data = data;
}
// 重写clone方法,实现浅拷贝
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date();
// 创建原型对象
Prototype originalPrototype = new Prototype("玄子", date);
// 克隆原型对象
Prototype clonedPrototype = originalPrototype.clone();
System.out.println("源对象:" + originalPrototype.hashCode() + "," + originalPrototype.getName() + "," + originalPrototype.getBirthday());
System.out.println("克隆对象:" + clonedPrototype.hashCode() + "," + clonedPrototype.getName() + "," + clonedPrototype.getBirthday());
System.out.println("================================");
date.setTime(22222222);
System.out.println("源对象:" + originalPrototype.hashCode() + "," + originalPrototype.getName() + "," + originalPrototype.getBirthday());
System.out.println("克隆对象:" + clonedPrototype.hashCode() + "," + clonedPrototype.getName() + "," + clonedPrototype.getBirthday());
// 虽然克隆出来的是两个内存地址的对象
// 但修改数据后,源对象与克隆对象的数据都发生了改变
// 数据引用的是同一内存地址
}
/*
源对象:990368553,玄子,Sun Sep 24 02:50:52 CST 2023
克隆对象:396873410,玄子,Sun Sep 24 02:50:52 CST 2023
================================
源对象:990368553,玄子,Thu Jan 01 14:10:22 CST 1970
克隆对象:396873410,玄子,Thu Jan 01 14:10:22 CST 1970
*/
}
- 克隆属性实现深克隆
// 重写clone方法与属性,实现深拷贝
@Override
public Prototype clone() throws CloneNotSupportedException {
Prototype clone = (Prototype) super.clone();
clone.birthday = (Date) this.birthday.clone();
return clone;
}
// 实现深克隆仅需要,在源对象的 clone() 方法内对属性进行单独克隆
/*
源对象:990368553,玄子,Sun Sep 24 02:50:52 CST 2023
克隆对象:396873410,玄子,Sun Sep 24 02:50:52 CST 2023
================================
源对象:990368553,玄子,Thu Jan 01 14:10:22 CST 1970
克隆对象:396873410,玄子,Sun Sep 24 03:00:40 CST 2023
*/
- 使用反序列化实现深克隆
// 原型对象类,实现 Serializable 接口
public class Prototype implements Serializable {
private String name;
private Date birthday;
public Prototype(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
// 使用序列化和反序列化实现深拷贝
public Prototype deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 序列化当前对象
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
// 反序列化为新对象
return (Prototype) ois.readObject();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Date date = new Date();
// 创建原型对象
Prototype originalPrototype = new Prototype("玄子", date);
// 克隆原型对象
Prototype clonedPrototype = originalPrototype.deepClone();
System.out.println("源对象:" + originalPrototype.hashCode() + "," + originalPrototype.getName() + "," + originalPrototype.getBirthday());
System.out.println("克隆对象:" + clonedPrototype.hashCode() + "," + clonedPrototype.getName() + "," + clonedPrototype.getBirthday());
System.out.println("================================");
date.setTime(22222222);
System.out.println("源对象:" + originalPrototype.hashCode() + "," + originalPrototype.getName() + "," + originalPrototype.getBirthday());
System.out.println("克隆对象:" + clonedPrototype.hashCode() + "," + clonedPrototype.getName() + "," + clonedPrototype.getBirthday());
}
/*
源对象:990368553,玄子,Sun Sep 24 02:50:52 CST 2023
克隆对象:396873410,玄子,Sun Sep 24 02:50:52 CST 2023
================================
源对象:990368553,玄子,Thu Jan 01 14:10:22 CST 1970
克隆对象:396873410,玄子,Thu Jan 01 14:10:22 CST 1970
*/
}
4. 类关系图
5. 应用场景
- 需要创建复杂对象,但创建过程较为耗时。
- 需要创建大量相似对象,复制现有对象比重新创建更高效。
6. 易错点
浅拷贝问题:默认情况下,clone()方法执行的是浅拷贝,即只复制基本类型的字段和引用类型的地址,而不复制引用类型对象本身。如果对象中包含引用类型字段,需要注意处理深拷贝问题。
7. 优缺点
优点
- 提高对象的创建效率,避免重复执行初始化操作。
- 方便快速创建和修改对象副本。
缺点
- 如果对象的复制过程较为复杂,可能需要实现深拷贝。
- 在处理引用类型字段时需要特别注意,避免共享引用对象导致意外修改。
8. 总结
原型模式是一种创建型设计模式,通过复制现有对象来创建新对象。在Java中,可以通过实现Cloneable接口和重写clone()方法来实现原型模式。原型模式适用于需要创建复杂对象或大量相似对象的场景,可以提高对象的创建效率,并方便快速创建和修改对象副本。需要注意处理引用类型字段时的浅拷贝问题,并根据具体情况考虑是否需要实现深拷贝。
建造者模式(Builder Pattern)
1. 简单介绍
建造者模式(Builder Pattern)将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
2. 实际问题
在现实世界中,我们可能会遇到这样的问题:希望构建一个复杂的对象,该对象有多个可选属性,并且在创建过程中可以灵活组合这些属性。
3. 解决方案
建造者模式是一种创建型设计模式,它通过将对象的构建过程和表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常包含一个Builder
接口和一个ConcreteBuilder
实现类,以及一个Director
类来指导构建过程。
- 建造者模式
// 建造者接口
public interface Builder {
void buildPart1();
void buildPart2();
void buildPart3();
Product getResult();
}
// 具体建造者
public class ConcreteBuilder implements Builder {
private final Product product = new Product();
public void buildPart1() {
product.setPart1("Part1");
}
public void buildPart2() {
product.setPart2("Part2");
}
public void buildPart3() {
product.setPart3("Part3");
}
public Product getResult() {
return product;
}
}
// 产品类
public class Product {
private String part1;
private String part2;
private String part3;
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
public String getPart3() {
return part3;
}
public void setPart3(String part3) {
this.part3 = part3;
}
@Override
public String toString() {
return "Product{" +
"part1='" + part1 + '\'' +
", part2='" + part2 + '\'' +
", part3='" + part3 + '\'' +
'}';
}
}
// 指导者类
public class Director {
public void construct(Builder builder) {
builder.buildPart1();
builder.buildPart2();
builder.buildPart3();
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 创建指导者和建造者对象
Director director = new Director();
Builder builder = new ConcreteBuilder();
// 指导建造过程并获取产品对象
director.construct(builder);
Product product = builder.getResult();
// 输出产品信息,产品信息顺序固定
System.out.println(product);
// Output: Product{part1='Part1', part2='Part2', part3='Part3'}
}
}
- 建造者模式(无指挥
Director
)
// 建造者接口
public interface Builder {
Builder buildPart1(String part);
Builder buildPart2(String part);
Builder buildPart3(String part);
Product getResult();
}
// 具体建造者
public class ConcreteBuilder implements Builder {
private final Product product = new Product();
public Builder buildPart1(String part) {
product.setPart1(part);
return this;
}
public Builder buildPart2(String part) {
product.setPart2(part);
return this;
}
public Builder buildPart3(String part) {
product.setPart3(part);
return this;
}
public Product getResult() {
return product;
}
}
// 产品类
public class Product {
private String part1 = "part1";
private String part2 = "part1";
private String part3 = "part1";
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
public String getPart3() {
return part3;
}
public void setPart3(String part3) {
this.part3 = part3;
}
@Override
public String toString() {
return "Product{" +
"part1='" + part1 + '\'' +
", part2='" + part2 + '\'' +
", part3='" + part3 + '\'' +
'}';
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 创建指导者和建造者对象
ConcreteBuilder concreteBuilder = new ConcreteBuilder();
Product product = concreteBuilder.getResult();
// 输出产品信息
System.out.println(product);
// Output: Product{part1='Part1', part2='Part2', part3='Part3'}
System.out.println("============================");
// 创建指导者和建造者对象
ConcreteBuilder concreteBuilder2 = new ConcreteBuilder();
// 链式编程:在原来的基础上,可自由组合,若不组合,则默认
Product product2 = concreteBuilder2.buildPart1("part2").buildPart2("part1").buildPart3("part3").getResult();
// 输出产品信息,产品信息顺序可自由搭配
System.out.print(product2);
// Output: Product{part1='part2', part2='part1', part3='part3'}
}
}
4. 类关系图
5. 应用场景
- 需要创建复杂对象,且对象的构建过程相对稳定,但表示可以灵活组合的场景。
- 当一个对象有多个可选属性,且属性之间有依赖关系时,可以使用建造者模式进行构建。
6. 易错点
- 需要根据实际需求定义建造者接口和具体建造者类,确保建造过程的正确性和灵活性。
- 在指导者类中指导建造过程,确保正确的建造顺序和步骤。
7. 优缺点
优点
- 将对象的构建过程和表示分离,使得构建过程可以创建不同的表示,提高了构建过程的灵活性。
- 可以隐藏产品的内部结构,使得客户端只关注产品的高层接口。
缺点
- 需要定义多个类,增加了代码复杂性。
- 不适用于只有少量可选属性的对象,过度使用建造者模式可能会导致代码冗余。
8. 总结
建造者模式是一种创建型设计模式,它通过将对象的构建过程和表示分离,使得同样的构建过程可以创建不同的表示。在Java中,可以通过定义建造者接口和具体建造者类来实现建造者模式,并通过指导者类指导建造过程。建造者模式适用于需要创建复杂对象,且对象的构建过程相对稳定但表示可以灵活组合的场景。需要注意定义建造者接口和具体建造者类,确保建造过程的正确性和灵活性。同时,建造者模式也应避免过度使用,以免造成代码冗余。
结构型模式 (Structural Patterns)
结构型模式(Structural Patterns)是设计模式中的一类,它主要关注对象和类的组合,以实现更大的结构,以及改变或简化类之间的交互方式。这些模式使得不同类和对象之间的关系更加灵活,同时也降低了系统的耦合度,使系统更易于维护和扩展。
结构型模式在软件开发中起到了重要的作用,它们帮助我们在设计阶段选择合适的模式,提高系统的设计质量和性能,并促进代码的重用和维护。不同的结构型模式适用于不同的场景和需求,通过合理使用结构型模式,可以让系统更加灵活、可扩展和易于理解。
适配器模式(Adapter Pattern)
1. 简单介绍
适配器模式(Adapter Pattern)将一个类的接口转换成客户端所期望的另一个接口,使得不兼容的类可以协同工作。
2. 实际问题
假设我们现在有一台笔记本,需要插入网线来上网,但是笔记本并没有网线接口,只有一个 USB 接口,网线与 USB 接口并不兼容,我们无法直接在笔记本上插入网线,需要使用转接器把 USB 接口转换为网线接口
3. 解决方案
适配器模式是一种结构型设计模式,它通过创建一个适配器类来转换一个类的接口为客户端所期望的另一个接口。适配器模式主要包含三个角色:目标接口(Adapter
)、被适配者(Computer
)和适配器类(AdapterNetwork
)。
// 被适配者类
public class Computer {
public void surf(Adapter adapter) {
adapter.adapter();
System.out.println(this.getClass().getSimpleName() + ":电脑上网");
}
}
// 转换器接口
public interface Adapter {
public void adapter();
}
// 适配器类
public class AdapterNetwork implements Adapter {
private final Network network;
public AdapterNetwork(Network network) {
this.network = network;
}
@Override
public void adapter() {
network.getNet();
System.out.println(this.getClass().getSimpleName() + ":将网口转为USB");
}
}
// 网口
public class Network {
public void getNet() {
System.out.println(this.getClass().getSimpleName() + ":联网");
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
// 创建被适配者对象,电脑需要联网
Computer computer = new Computer();
// 创建网口
Network network = new Network();
// 使用适配器将,USB口转换为网口
Adapter adapter = new AdapterNetwork(network);
// 电脑插上转换后的网口
computer.surf(adapter);
}
/*
Network:联网
AdapterNetwork:将网口转为USB
Computer:电脑上网
*/
}
4. 类关系图
5. 应用场景
- 将一个已有的类或接口转换成客户端所期望的另一个接口。
- 在现有系统中需要使用一些已有的类,但这些类的接口与系统要求的接口不一致时。
- 适配器模式可以用于系统的扩展和升级,将新的功能整合到已有系统中。
6. 易错点
- 确保适配器类实现了目标接口,并正确调用了被适配者类的方法。
- 注意被适配者类的接口与目标接口的兼容性,确保适配器类能够正确地适配新的功能。
7. 优缺点
优点
- 允许将不兼容的类或接口转换为可用的接口,提高代码的复用性和灵活性。
- 可以避免修改现有代码,降低了代码的风险。
缺点
- 增加了代码的复杂性,引入了适配器类,可能会导致类的数量增加。
8. 总结
适配器模式是一种结构型设计模式,它通过创建一个适配器类来转换一个类的接口为客户端所期望的另一个接口。适配器模式适用于将不兼容的类或接口转换为可用的接口,提高代码的复用性和灵活性,避免修改现有代码。需要注意适配器类实现目标接口,并正确调用被适配者类的方法。同时,适配器模式也应避免过度使用,以免引入过多的适配器类增加代码复杂性。
桥接模式(Bridge Pattern)
1. 简单介绍
桥接模式(Bridge Pattern):将抽象部分和实现部分分离,使它们可以独立变化,从而增加系统的灵活性。
2. 实际问题
假设我们要开发一个绘图软件,支持绘制不同的图形(如圆形、矩形、椭圆等)和填充不同的颜色(如红色、绿色、蓝色等)。如果直接使用继承来实现每种图形和颜色的组合,将会导致类爆炸,即需要创建大量的类来覆盖所有的组合可能性,这会导致代码的复杂性增加。我们希望能够将图形和颜色的绘制过程解耦,使得每种图形和颜色可以独立地变化而不影响其他部分。
3. 解决方案
桥接模式是一种结构型设计模式,它将抽象部分和实现部分分离,使它们可以独立变化,从而增加系统的灵活性。桥接模式主要包含两个层次结构:抽象化(Abstraction
)和实现化(Implementation
)。
// 抽象化类
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
}
// 具体抽象化类 - 圆形
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
public void draw() {
System.out.print("绘制圆形:");
color.applyColor();
}
}
// 具体抽象化类 - 矩形
public class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
public void draw() {
System.out.print("绘制矩形:");
color.applyColor();
}
}
// 实现化接口
public interface Color {
void applyColor();
}
// 具体实现化类 - 红色
public class RedColor implements Color {
public void applyColor() {
System.out.println("使用红色");
}
}
// 具体实现化类 - 绿色
public class GreenColor implements Color {
public void applyColor() {
System.out.println("使用绿色");
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建具体实现化对象
Color redColor = new RedColor();
Color greenColor = new GreenColor();
// 创建具体抽象化对象
Shape redCircle = new Circle(redColor);
Shape greenRectangle = new Rectangle(greenColor);
// 绘制图形
redCircle.draw();
// Output: 绘制圆形:使用红色
greenRectangle.draw();
// Output: 绘制矩形:使用绿色
}
}
4. 类关系图
5. 应用场景
- 需要将抽象部分和实现部分分离,以便它们可以独立变化,从而增加系统的灵活性。
- 当一个类存在多个维度的变化时,可以使用桥接模式来简化类的继承关系,避免类的爆炸。
6. 易错点
- 确保抽象化类与实现化类之间的联系,避免过多或过少的耦合。
- 在设计类的继承结构时,需要合理地选择抽象化和实现化的关系,确保系统的灵活性和可维护性。
7. 优缺点
优点
- 将抽象部分和实现部分分离,使它们可以独立变化,增加了系统的灵活性和可扩展性。
- 桥接模式避免了类的爆炸现象,使得新增具体抽象化类或实现化类更加方便。
缺点
- 引入桥接模式会增加系统的复杂性,需要额外创建抽象化和实现化的类和接口。
8. 总结
桥接模式是一种结构型设计模式,它将抽象部分和实现部分分离,使它们可以独立变化,增加了系统的灵活性和可扩展性。通过桥接模式,可以将一个类的多个维度的变化进行解耦,避免了类的爆炸现象。需要注意在设计类的继承结构时,合理选择抽象化和实现化的关系,以确保系统的灵活性和可维护性。桥接模式适用于需要将抽象部分和实现部分分离的场景,特别是当一个类存在多个维度的变化时,可以使用桥接模式来简化类的继承关系。
组合模式(Composite Pattern)
1. 简单介绍
组合模式(Composite Pattern)将对象组合成树形结构以表示“整体-部分”层次关系,使得客户端对单个对象和组合对象的使用具有一致性。
2. 实际问题
假设我们正在开发一个文件系统,文件系统中包含文件和文件夹两种类型的元素。我们希望能够以树形结构来表示文件系统中的所有元素,并且能够对整个文件系统进行统一的操作,比如查找文件、删除文件、获取文件大小等。同时,我们还希望能够在文件夹中包含其他文件夹,形成一个递归的结构。
3. 解决方案
组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示“整体-部分”层次关系。组合模式主要包含两个角色:组合对象(Composite
)和叶子对象(Leaf
)。组合对象可以包含叶子对象或其他组合对象,形成递归的结构。
// 抽象组件类
public abstract class FileComponent {
protected String name;
public FileComponent(String name) {
this.name = name;
}
public abstract void display(int indentLevel);
}
// 叶子对象类 - 文件
public class File extends FileComponent {
public final int size;
public File(String name, int size) {
super(name);
this.size = size;
}
public void display(int indentLevel) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < indentLevel; i++) {
indent.append(" ");
// 每级缩进两个空格
}
System.out.println(indent + "File: " + name + ", Size: " + size + " KB");
}
}
// 组合对象类 - 文件夹
public class Folder extends FileComponent {
public final List<FileComponent> components;
public Folder(String name) {
super(name);
components = new ArrayList<>();
}
public void add(FileComponent component) {
components.add(component);
}
public void remove(FileComponent component) {
components.remove(component);
}
public void display(int indentLevel) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < indentLevel; i++) {
indent.append(" "); // 每级缩进两个空格
}
System.out.println(indent + "Folder: " + name);
for (FileComponent component : components) {
component.display(indentLevel + 1);
// 递归调用,增加缩进级别
}
}
}
public class XZ {
public static void main(String[] args) {
// 创建文件和文件夹
FileComponent file1 = new File("File1.txt", 100);
FileComponent file2 = new File("File2.txt", 50);
Folder folder1 = new Folder("Folder1");
folder1.add(file1);
folder1.add(file2);
FileComponent file3 = new File("File3.txt", 80);
FileComponent file4 = new File("File4.txt", 120);
Folder folder2 = new Folder("Folder2");
folder2.add(file3);
folder2.add(file4);
folder2.add(folder1);
// 显示文件系统的结构
folder2.display(0);
}
/*
Folder: Folder2
File: File3.txt, Size: 80 KB
File: File4.txt, Size: 120 KB
Folder: Folder1
File: File1.txt, Size: 100 KB
File: File2.txt, Size: 50 KB
*/
}
4. 类关系图
5. 应用场景
- 希望将对象组合成树形结构,以表示“整体-部分”层次关系。
- 需要对整个树形结构进行统一的操作,而无需关心具体是叶子对象还是组合对象。
- 当客户端代码需要处理复杂的对象结构时,可以使用组合模式来简化代码。
6. 易错点
- 确保组合对象和叶子对象具有一致的接口,以便客户端可以统一操作。
- 在设计组合对象类时,需要合理地处理叶子对象和组合对象的关系,避免过度递归。
7. 优缺点
优点
- 允许将对象组合成树形结构,表示“整体-部分”层次关系,简化了对象结构的表示。
- 可以统一对整个树形结构进行操作,使客户端代码更加简洁和灵活。
缺点
- 引入组合模式会增加系统的复杂性,需要额外创建组合对象和叶子对象的类。
8. 总结
组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示“整体-部分”层次关系。通过组合模式,可以将叶子对象和组合对象统一表示,使得客户端无需关心具体的对象类型。需要注意确保组合对象和叶子对象具有一致的接口,以便客户端可以统一操作。组合模式适用于需要表示复杂的对象结构的场景,同时需要对整个对象结构进行统一操作的情况。
装饰模式(Decorator Pattern)
1. 简单介绍
装饰模式(Decorator Pattern)动态地给对象添加额外的职责,是继承的替代方案,提供更加灵活的扩展能力。
2. 实际问题
假设我们正在开发一个咖啡店的点单系统。系统中有多种咖啡可以选择,并且用户可以根据个人口味选择添加不同的配料,比如牛奶、糖、巧克力等。每一种配料都有不同的价格,而且用户还可以选择多种配料的组合。我们希望能够以灵活的方式计算出点单的总价,并且可以在不修改原有咖啡类的情况下增加新的配料选项。
3. 解决方案
装饰模式是一种结构型设计模式,它允许将对象的功能动态地添加到对象中,同时不改变其接口。装饰模式主要包含三个角色:抽象组件(Component
)、具体组件(ConcreteComponent
)和装饰器(Decorator
)。
// 抽象组件类 - 咖啡
public interface Coffee {
double getCost();
String getDescription();
}
// 装饰器类 - 咖啡装饰器
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
public double getCost() {
return decoratedCoffee.getCost();
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// 具体组件类 - 简单咖啡
public class SimpleCoffee implements Coffee {
public double getCost() {
return 2.0;
}
public String getDescription() {
return "基础咖啡";
}
}
// 具体装饰器类 - 牛奶装饰器
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() {
return super.getCost() + 1.5;
}
public String getDescription() {
return super.getDescription() + ", 牛奶";
}
}
// 具体装饰器类 - 巧克力装饰器
public class ChocolateDecorator extends CoffeeDecorator {
public ChocolateDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
public double getCost() {
return super.getCost() + 2.0;
}
public String getDescription() {
return super.getDescription() + ", 巧克力";
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建简单咖啡对象
Coffee coffee = new SimpleCoffee();
// 添加牛奶和巧克力
coffee = new MilkDecorator(coffee);
coffee = new ChocolateDecorator(coffee);
coffee = new MilkDecorator(coffee);
// 显示点单地描述和总价
System.out.println("商品:" + coffee.getDescription());
System.out.println("花费:" + coffee.getCost());
}
/*
商品:基础咖啡, 牛奶, 巧克力, 牛奶
花费:7.0
*/
}
4. 类关系图
5. 应用场景
- 需要动态地给对象添加功能,而且希望在不修改原有代码的情况下增加新的功能。
- 当一个类有多个可选的功能,可以使用装饰模式来实现不同的组合。
6. 易错点
- 确保装饰器类继承自抽象组件类,并正确实现装饰器的功能。
- 需要合理地组织装饰器类之间的继承关系,确保功能的正确组合。
7. 优缺点
优点
- 可以动态地给对象添加功能,使得功能的增加更加灵活和动态。
- 装饰模式避免了使用继承来扩展功能,使得代码更加简洁和易于维护。
缺点
- 引入装饰器类会增加系统的复杂性,需要额外创建装饰器类和维护装饰器之间的关系。
8. 总结
装饰模式是一种结构型设计模式,它允许将对象的功能动态地添加到对象中,同时不改变其接口。通过装饰模式,可以在不修改原有代码的情况下增加新的功能,使得功能的增加更加灵活和动态。装饰模式适用于需要动态地给对象添加功能的场景,以及有多个可选功能的情况。需要注意在设计装饰器类时,合理地组织装饰器类之间的继承关系,确保功能的正确组合。
外观模式(Facade Pattern)
1. 简单介绍
外观模式(Facade Pattern)为复杂子系统提供一个简单的接口,使得子系统更易于使用。
2. 实际问题
假设我们正在开发一个家庭影院系统。家庭影院包含多个设备,比如投影仪、音响、DVD播放器等。每次观影时,需要打开投影仪、打开音响、打开DVD播放器等一系列操作,而且每个设备的控制接口可能都不相同,这会导致操作繁琐。我们希望能够提供一个简单的接口,让用户只需调用一个方法,就可以启动家庭影院系统并开始观影。
3. 解决方案
外观模式是一种结构型设计模式,它为子系统提供了一个统一的接口,使得子系统更加易于使用。外观模式主要包含一个外观类(Facade
)和多个子系统类(SubSystem
)。
// 子系统类 - 投影仪
public class Projector {
public void turnOn() {
System.out.println("投影仪开启");
}
public void turnOff() {
System.out.println("投影仪开启关闭");
}
}
// 子系统类 - 音响系统
public class AudioSystem {
public void turnOn() {
System.out.println("音响系统开启");
}
public void turnOff() {
System.out.println("音响系统关闭");
}
}
// 子系统类 - DVD播放器
public class DVDPlayer {
public void turnOn() {
System.out.println("DVD播放器开启");
}
public void turnOff() {
System.out.println("DVD播放器关闭");
}
}
// 外观类 - 家庭影院外观
public class HomeTheaterFacade {
private final Projector projector;
private final AudioSystem audioSystem;
private final DVDPlayer dvdPlayer;
public HomeTheaterFacade(Projector projector, AudioSystem audioSystem, DVDPlayer dvdPlayer) {
this.projector = projector;
this.audioSystem = audioSystem;
this.dvdPlayer = dvdPlayer;
}
public void watchMovie() {
projector.turnOn();
audioSystem.turnOn();
dvdPlayer.turnOn();
System.out.println("电影开始播放");
}
public void endMovie() {
projector.turnOff();
audioSystem.turnOff();
dvdPlayer.turnOff();
System.out.println("电影结束播放");
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建子系统对象
Projector projector = new Projector();
AudioSystem audioSystem = new AudioSystem();
DVDPlayer dvdPlayer = new DVDPlayer();
// 创建家庭影院外观对象
HomeTheaterFacade homeTheater = new HomeTheaterFacade(projector, audioSystem, dvdPlayer);
// 启动家庭影院并观影
homeTheater.watchMovie();
System.out.println("=============");
// 结束观影
homeTheater.endMovie();
}
/*
投影仪开启
音响系统开启
DVD播放器开启
电影开始播放
=============
投影仪开启关闭
音响系统关闭
DVD播放器关闭
电影结束播放
*/
}
4. 类关系图
5. 应用场景
- 当系统中存在复杂的子系统,需要对外提供简单的接口时,可以使用外观模式。
- 当客户端需要和多个子系统交互时,可以使用外观模式来简化交互过程。
6. 易错点
- 确保外观类中包含了所有子系统的功能,以提供统一的接口给客户端调用。
- 外观类不应该包含过多的业务逻辑,避免将复杂性转移到外观类中。
7. 优缺点
优点
外观模式提供了一个简单的接口,使得客户端更加易于使用子系统。
外观模式将子系统的复杂性隐藏起来,使得客户端不需要了解子系统的实现细节。
缺点
- 外观模式可能导致子系统之间的耦合增加,因为外观类需要调用多个子系统的功能。
8. 总结
外观模式是一种结构型设计模式,它为子系统提供了一个统一的接口,使得子系统更加易于使用。通过外观模式,可以将复杂的子系统封装起来,提供一个简单的接口给客户端调用。外观模式适用于需要与多个子系统交互或需要简化复杂子系统的场景。需要注意确保外观类中包含了所有子系统的功能,同时避免在外观类中添加过多的业务逻辑。
享元模式(Flyweight Pattern)
1. 简单介绍
享元模式(Flyweight Pattern)共享对象以减少内存占用,提高性能。
2. 实际问题
假设我们正在构建一个简单的绘图应用程序,用户可以在画布上绘制圆形和矩形。用户可以选择不同的颜色,位置和大小来绘制这些形状。由于用户可以创建许多相似的形状,我们需要一种方式来有效地共享相同颜色的形状对象,以减少内存使用。
3. 解决方案
享元模式是一种结构型设计模式,它通过共享相同类型的对象来减少内存的使用。享元模式主要包含两个角色:享元工厂(FlyweightFactory
)和享元对象(Flyweight
)。
// 享元对象接口 - 图形
public interface Shape {
void draw();
}
// 具体享元对象类 - 圆形
public class Circle implements Shape {
// 包含可以被共享的状态
private final String color;
public Circle(String color) {
this.color = color;
}
public void draw() {
// 绘制圆形的代码
System.out.println("绘制圆形-颜色:" + color);
}
}
// 具体享元对象类 - 矩形
public class Rectangle implements Shape {
// 包含可以被共享的状态
private final String color;
public Rectangle(String color) {
this.color = color;
}
public void draw() {
// 绘制矩形的代码
System.out.println("绘制矩形-颜色:" + color);
}
}
// 享元工厂类
public class ShapeFactory {
private static final Map<String, Shape> shapeMap = new HashMap<>();
public static Shape getShape(String color) {
Shape shape = shapeMap.get(color);
if (shape == null) {
// 如果不存在具有相同颜色的对象,则创建一个新对象
if ("red".equalsIgnoreCase(color)) {
shape = new Circle("red");
} else if ("green".equalsIgnoreCase(color)) {
shape = new Circle("green");
} else if ("blue".equalsIgnoreCase(color)) {
shape = new Circle("blue");
} else {
shape = new Rectangle("black");
}
// 将新对象存储在享元工厂中,以备后续共享
shapeMap.put(color, shape);
}
return shape;
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 获取并绘制图形
Shape redCircle = ShapeFactory.getShape("red");
redCircle.draw();
Shape greenCircle = ShapeFactory.getShape("green");
greenCircle.draw();
Shape blueCircle = ShapeFactory.getShape("blue");
blueCircle.draw();
Shape blackRectangle1 = ShapeFactory.getShape("black");
blackRectangle1.draw();
Shape blackRectangle2 = ShapeFactory.getShape("black");
blackRectangle2.draw();
}
/*
绘制圆形-颜色:red
绘制圆形-颜色:green
绘制圆形-颜色:blue
绘制矩形-颜色:black
绘制矩形-颜色:black
*/
}
4. 类关系图
5. 应用场景
当系统中存在大量相同或相似的对象,并且这些对象可以被共享时,可以使用享元模式来减少内存占用。
当需要频繁地创建和销毁对象时,可以使用享元模式来提高性能。
6. 易错点
- 确保享元工厂类能够正确地共享相同类型的对象,避免重复创建对象。
- 在使用享元对象时,需要注意控制对象的状态,确保其在不同场景下的使用正确。
7. 优缺点
优点
- 享元模式可以减少内存的占用,提高系统的性能。
- 通过共享相同类型的对象,可以节省对象的创建和销毁时间,加快系统的响应速度。
缺点
- 享元模式会增加系统的复杂性,需要额外维护对象的共享状态。
- 如果共享的对象有状态,需要在使用时正确管理对象的状态,否则可能导致不正确的结果。
8. 总结
享元模式是一种结构型设计模式,它通过共享相同类型的对象来减少内存的使用。通过享元模式,可以减少重复对象的创建,提高系统的性能。享元模式适用于存在大量相同或相似的对象,并且这些对象可以被共享的场景。在使用享元模式时,需要注意正确管理对象的状态,以避免出现不正确的结果。
代理模式(Proxy Pattern)
1. 简单介绍
代理模式(Proxy Pattern)为其他对象提供一种代理以控制对这个对象的访问。
2. 实际问题
这里我们直接使用实际业务发开中的场景,现有一个查询学生信息的 getStudentInfo() 方法。我们需要在方法之执行前后,分别执行相关的业务代码操作,以满足开发需求。
3. 解决方案
代理模式是一种结构型设计模式,它通过创建一个代理对象来控制对真实对象的访问。代理对象可以在真实对象的操作前后加入自己的逻辑,从而实现对真实对象的控制。代理模式主要包含三个角色:抽象主题(Subject
)、真实主题(RealSubject
)和代理(Proxy
)。
- 静态代理
// 业务接口
public interface StudentService {
public void getStudentInfo();
}
// 业务接口实现类
public class StudentServiceImpl implements StudentService {
@Override
public void getStudentInfo() {
System.out.println("获取学生信息");
}
}
// 静态代理对象
public class StudentServiceImplStaticProxy implements StudentService {
private final StudentService studentService;
public StudentServiceImplStaticProxy(StudentService studentService) {
this.studentService = studentService;
}
@Override
public void getStudentInfo() {
before();
studentService.getStudentInfo();
after();
}
public void before() {
System.out.println("方法执行前操作");
}
public void after() {
System.out.println("方法执行后操作");
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建代理对象
StudentService studentService = new StudentServiceImpl();
StudentServiceImplStaticProxy proxy = new StudentServiceImplStaticProxy(studentService);
proxy.getStudentInfo();
}
// 方法执行前操作
// 获取学生信息
// 方法执行后操作
}
- JDK动态代理
// 业务接口
public interface StudentService {
public void getStudentInfo();
}
// 业务接口实现类
public class StudentServiceImpl implements StudentService {
@Override
public void getStudentInfo() {
System.out.println("获取学生信息");
}
}
// 动态代理对象
public class StudentServiceImplDynamicProxy implements InvocationHandler {
private final Object studentService;
public StudentServiceImplDynamicProxy(StudentService studentService) {
this.studentService = studentService;
}
public StudentService getProxyInstance() {
return (StudentService) Proxy.newProxyInstance(studentService.getClass().getClassLoader(), studentService.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object invoke = method.invoke(studentService, args);
after();
return invoke;
}
public void before() {
System.out.println("方法执行前操作");
}
public void after() {
System.out.println("方法执行后操作");
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建代理对象
StudentService studentService = new StudentServiceImpl();
StudentService studentServiceProxy = new StudentServiceImplDynamicProxy(studentService).getProxyInstance();
studentServiceProxy.getStudentInfo();
}
// 方法执行前操作
// 获取学生信息
// 方法执行后操作
}
- CGLIB动态代理
// 业务接口
public interface StudentService {
public void getStudentInfo();
}
// 业务接口实现类
public class StudentServiceImpl implements StudentService {
@Override
public void getStudentInfo() {
System.out.println("获取学生信息");
}
}
// 动态代理对象
public class StudentServiceImplCglibDynamicProxy implements MethodInterceptor {
private final StudentService studentService;
public StudentServiceImplCglibDynamicProxy(StudentService studentService) {
this.studentService = studentService;
}
// 用来获取代理对象(创建一个代理对象)
public StudentService getProxyInstance() {
//可以通过Enhancer对象中的create()方法可以去生成一个类,用于生成代理对象
Enhancer enhancer = new Enhancer();
//设置父类(将目标类作为代理类的父类)
enhancer.setSuperclass(studentService.getClass());
//设置拦截器(回调对象为本身对象)
enhancer.setCallback(this);
//生成一个代理类对象并返回给调用着
return (StudentService) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
public void before() {
System.out.println("方法执行前操作");
}
public void after() {
System.out.println("方法执行后操作");
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建代理对象
StudentService studentService = new StudentServiceImpl();
StudentService proxy = new StudentServiceImplCglibDynamicProxy(studentService).getProxyInstance();
proxy.getStudentInfo();
}
// 方法执行前操作
// 获取学生信息
// 方法执行后操作
}
// 这里注意JDK版本需为1.8
4. 类关系图
5. 应用场景
当访问一个对象存在复杂的过程或需要控制访问对象时,可以使用代理模式来控制对对象的访问。
当需要在访问真实对象前后加入自己的逻辑时,可以使用代理模式来实现增强。
6. 易错点
确保代理对象实现了抽象主题接口,并正确实现代理的功能。
确保在真实对象初始化前不执行真实对象的操作,以提升性能。
7. 优缺点
优点
- 代理模式可以控制对真实对象的访问,从而实现对真实对象的控制和增强。
- 代理模式可以在真实对象的操作前后加入自己的逻辑,从而实现增强。
缺点
- 代理模式会增加系统的复杂性,需要创建额外的代理类来控制对真实对象的访问。
8. 总结
代理模式是一种结构型设计模式,它通过创建一个代理对象来控制对真实对象的访问。通过代理模式,可以控制对真实对象的访问,实现对真实对象的控制和增强。代理模式适用于需要在访问真实对象前后加入自己的逻辑,或者需要控制对真实对象的访问的场景。需要注意在代理对象中正确实现代理的功能,并确保真实对象的操作在真实对象初始化前不执行,以提升性能。
行为型模式 (Behavioral Patterns)
行为型模式(Behavioral Patterns)是设计模式的一种分类,它关注对象之间的交互和责任分配。行为型模式主要用于描述对象之间的通信方式和协作方式,以及对象如何相互影响和完成各自的任务。在行为型模式中,关注的是对象的行为和算法的分配,而不是对象的结构。
每种行为型模式都解决了特定类型的问题,可以根据实际的需求选择合适的模式来设计和实现软件系统。这些模式在软件开发中具有广泛的应用,能够提高代码的可维护性和扩展性,降低代码的耦合度,使得系统更加灵活和易于维护。
责任链模式(Chain of Responsibility Pattern)
1. 简单介绍
责任链模式(Chain of Responsibility Pattern)将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,将请求沿着处理链传递,直到有一个对象处理它为止。
2. 实际问题
假设你是一个客户服务中心的员工,你需要处理客户的问题和请求。有不同类型的问题,有些你可以处理,有些你需要转给你的主管,而有些问题则需要进一步转给高级管理层。你需要一种灵活的方式来处理这些问题,并且在将来能够轻松添加新的处理者。
3. 解决方案
在这个示例中,Request
类表示客户的请求,Handler
是抽象处理者类,具有设置下一个处理者的方法和处理请求的抽象方法。LowLevelEmployee
、MiddleManager
和 TopManagement
是具体处理者类,分别表示低级员工、中级主管和高级管理层。它们分别判断自己是否能够处理请求,如果能,则处理;如果不能,则将请求传递给链上的下一个处理者。
// 请求类
class Request {
private String requestType;
public Request(String requestType) {
this.requestType = requestType;
}
public String getRequestType() {
return requestType;
}
}
// 抽象处理者类
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(Request request);
}
// 具体处理者类:低级员工
class LowLevelEmployee extends Handler {
@Override
public void handleRequest(Request request) {
if (request.getRequestType().equals("简单问题")) {
System.out.println("低级员工处理简单问题");
} else {
if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
}
// 具体处理者类:中级主管
class MiddleManager extends Handler {
@Override
public void handleRequest(Request request) {
if (request.getRequestType().equals("一般问题")) {
System.out.println("中级主管处理一般问题");
} else {
if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
}
// 具体处理者类:高级管理层
class TopManagement extends Handler {
@Override
public void handleRequest(Request request) {
if (request.getRequestType().equals("复杂问题")) {
System.out.println("高级管理层处理复杂问题");
} else {
System.out.println("无人能处理该问题");
}
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建处理者对象
Handler lowLevelEmployee = new LowLevelEmployee();
Handler middleManager = new MiddleManager();
Handler topManagement = new TopManagement();
// 设置处理者之间的关系
lowLevelEmployee.setNextHandler(middleManager);
middleManager.setNextHandler(topManagement);
// 创建请求
Request request1 = new Request("简单问题");
Request request2 = new Request("一般问题");
Request request3 = new Request("复杂问题");
Request request4 = new Request("其他问题");
// 处理请求
lowLevelEmployee.handleRequest(request1);
lowLevelEmployee.handleRequest(request2);
lowLevelEmployee.handleRequest(request3);
lowLevelEmployee.handleRequest(request4);
}
/*
低级员工处理简单问题
中级主管处理一般问题
高级管理层处理复杂问题
无人能处理该问题
*/
}
4. 类关系图
5. 应用场景
- 当有多个对象可以处理同一个请求,并且这些对象可以组成一个处理链时,可以使用责任链模式。
- 每个对象都尝试处理请求,如果自己能处理,则处理;如果不能,则将请求传递给链上的下一个处理者,直到有一个处理者处理请求。
6. 易错点
- 在设置处理者的下一个处理者时,需要注意链的顺序,确保请求能够正确地沿着链传递。
- 如果链上的处理者没有正确地处理请求或者没有设置下一个处理者,可能会导致请求无法得到处理。
7. 优缺点
优点
- 将请求和处理者解耦,客户端不需要知道请求的处理者是谁,每个处理者只需关注自己能否处理请求即可。
- 可以动态地组织和拆除处理链,灵活性高,便于维护和扩展。
缺点
- 请求可能会在整个链上传递,直到有一个处理者处理为止,可能导致性能问题。
- 如果链太长,可能会导致请求的处理时间较长。
8. 总结
责任链模式是一种行为型设计模式,允许多个对象依次处理请求,形成一个链式结构。每个处理者负责判断自己是否能够处理请求,如果能,则处理;如果不能,则将请求传递给链上的下一个处理者。责任链模式适用于多个对象可以处理同一个请求的场景,且每个处理者只需关注自己能否处理请求。在使用责任链模式时,需要注意正确设置处理者的顺序和下一个处理者,以确保请求能够正确地沿着链传递。
命令模式(Command Pattern)
1. 简单介绍
命令模式(Command Pattern)将一个请求封装成一个对象,使得可以用不同的请求对客户端进行参数化。
1. 实际问题
假设你正在开发一个文本编辑器应用程序,你需要实现多个编辑操作,如撤销、重做、剪切、复制等。你需要一种灵活的方式来封装这些编辑操作,使得它们能够被单独执行、撤销和重做,并且能够轻松地添加新的编辑操作。
3. 解决方案
在这个示例中,Command
接口是命令接口,包含执行和撤销操作的抽象方法。CutCommand
和 CopyCommand
是具体命令类,实现了剪切和复制操作的具体逻辑。Editor
类是接收者,表示文本编辑器,包含实际的编辑操作。CommandExecutor
类是调用者,负责执行命令。
// 命令接口:编辑操作
interface Command {
void execute();
void undo();
}
// 具体命令:剪切操作
class CutCommand implements Command {
private Editor editor;
private String text;
public CutCommand(Editor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
editor.cut(text);
}
@Override
public void undo() {
editor.undoCut(text);
}
}
// 具体命令:复制操作
class CopyCommand implements Command {
private Editor editor;
private String text;
public CopyCommand(Editor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
editor.copy(text);
}
@Override
public void undo() {
editor.undoCopy(text);
}
}
// 接收者:文本编辑器
class Editor {
private String content = "我是玄子今年18";
public void cut(String text) {
content = content.replace(text, "");
System.out.println("剪切:" + text);
}
public void copy(String text) {
content += text;
System.out.println("复制:" + text);
}
public void undoCut(String text) {
content += text;
System.out.println("撤销剪切:" + text);
}
public void undoCopy(String text) {
content = content.substring(0, content.length() - text.length());
System.out.println("撤销复制");
}
public void display() {
System.out.println("当前内容:" + content);
}
}
// 调用者:命令执行者
class CommandExecutor {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
public void undoCommand() {
command.undo();
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建文本编辑器和命令执行者
Editor editor = new Editor();
editor.display();
CommandExecutor executor = new CommandExecutor();
// 创建剪切命令并执行
Command cutCommand = new CutCommand(editor, "今年18");
executor.setCommand(cutCommand);
executor.executeCommand();
editor.display();
// 撤销剪切操作
executor.undoCommand();
editor.display();
// 创建复制命令并执行
Command copyCommand = new CopyCommand(editor, "我是玄子");
executor.setCommand(copyCommand);
executor.executeCommand();
editor.display();
// 撤销复制操作
executor.undoCommand();
editor.display();
}
/*
当前内容:我是玄子今年18
剪切:今年18
当前内容:我是玄子
撤销剪切:今年18
当前内容:我是玄子今年18
复制:我是玄子
当前内容:我是玄子今年18我是玄子
撤销复制
当前内容:我是玄子今年18
*/
}
4. 类关系图
5. 应用场景
- 当需要将请求或操作封装成对象,实现请求的参数化、传递、撤销和重做时,可以使用命令模式。
- 它可以将请求发送者和接收者解耦,使得请求可以被单独处理,并且可以灵活地添加新的命令类。
6. 易错点
- 在设计命令接口时,需要考虑命令的参数和执行方式,以便实现请求的参数化和传递。
- 在实现具体命令类时,需要正确调用接收者的方法,确保命令可以正确地执行和撤销。
7. 优缺点
优点
- 将请求和操作封装成对象,解耦了请求的发送者和接收者,使得命令可以灵活组合和扩展。
- 实现请求的撤销和重做非常方便,只需要调用命令对象的撤销方法即可。
缺点
- 可能会导致命令类的数量增加,尤其是在有大量命令和接收者的情况下。
- 如果命令的执行逻辑复杂,可能会导致系统的复杂性增加。
8. 总结
命令模式是一种行为型设计模式,它将请求或操作封装成一个对象,实现请求的参数化、传递、撤销和重做,使得请求发送者和接收者解耦。命令模式适用于需要灵活组合和扩展命令的场景,或者需要实现请求的撤销和重做的场景。在使用命令模式时,需要注意正确设计命令接口和具体命令类,确保请求可以正确地传递和执行。
解释器模式(Interpreter Pattern)
1. 简单介绍
解释器模式(Interpreter Pattern)定义一种语言的文法,并解释语言中的句子。
2. 实际问题
假设你正在开发一个文本处理程序,你需要实现一个功能,将特定格式的字符串解释为相应的操作或行为。例如,用户输入一段字符串 "COPY file.txt TO folder/",你需要将它解释为将 "file.txt" 复制到 "folder/" 目录下。
3. 解决方案
在这个示例中,Expression
接口是抽象表达式,定义了一个解释方法。TerminalExpression
是终结符表达式,表示解释特定的终结符,如 "file.txt"。OrExpression
是非终结符表达式,表示解释复杂的非终结符,如 "COPY" 和 "TO"。InterpreterContext
是解释器环境,封装了一个解释器,负责解释给定的上下文。
// 抽象表达式:解释器接口
interface Expression {
boolean interpret(String context);
}
// 终结符表达式:解释特定的终结符
class TerminalExpression implements Expression {
private final String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
return context.contains(data);
}
}
// 非终结符表达式:解释复杂的非终结符
class OrExpression implements Expression {
private final Expression expr1;
private final Expression expr2;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
// 解释器环境:上下文
class InterpreterContext {
private final Expression expression;
public InterpreterContext(Expression expression) {
this.expression = expression;
}
public boolean interpret(String context) {
return expression.interpret(context);
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
Expression expression1 = new TerminalExpression("COPY");
Expression expression2 = new TerminalExpression("TO");
Expression expression = new OrExpression(expression1, expression2);
InterpreterContext interpreterContext = new InterpreterContext(expression);
System.out.println(interpreterContext.interpret("COPY file.txt TO folder/"));
}
// true
}
4. 类关系图
5. 应用场景
- 当需要解释一些特定语法规则或表达式时,可以使用解释器模式。
- 它适用于需要解释复杂表达式的场景,可以将一个语言表示为解释器类的层次结构。
6. 易错点
- 在设计解释器类时,需要根据语言的语法规则,定义相应的终结符和非终结符表达式,确保表达式可以正确地解释。
7. 优缺点
优点
- 将语言的解释与表达式解耦,使得解释过程更加灵活和可扩展。
- 可以灵活地组合表达式,实现复杂的解释规则。
缺点
- 如果语言的规则过于复杂,可能会导致解释器类的层次结构很庞大,增加系统的复杂性。
- 解释器模式可能不适用于所有的解释场景,如果解释逻辑很简单,使用解释器模式反而会增加代码的复杂性。
8. 总结
解释器模式是一种行为型设计模式,用于解释特定语法规则或表达式。它将语言的解释与表达式解耦,使得解释过程更加灵活和可扩展。解释器模式适用于需要解释复杂表达式的场景,可以将一个语言表示为解释器类的层次结构。在使用解释器模式时,需要根据语言的语法规则,定义相应的终结符和非终结符表达式,确保表达式可以正确地解释。
迭代器模式(Iterator Pattern)
1. 简单介绍
迭代器模式(Iterator Pattern)提供一种顺序访问集合对象元素的方法,而不用暴露其内部结构。
2. 实际问题
假设你正在开发一个音乐播放器应用程序,你需要管理用户的播放列表。播放列表是一个包含多首音乐的集合,你需要一种方法来遍历这个播放列表,播放其中的音乐,并且能够灵活地切换遍历方式。
3. 解决方案
在这个示例中,Iterator
接口是迭代器接口,定义了迭代器的两个方法 hasNext()
和 next()
。IterableCollection
接口是集合接口,定义了一个创建迭代器的方法 createIterator()
。MusicPlaylistIterator
是具体迭代器类,实现了遍历音乐播放列表的逻辑。MusicPlaylist
是具体集合类,包含了音乐播放列表的数据,并实现了创建迭代器的方法。
// 迭代器接口
interface Iterator<T> {
boolean hasNext();
T next();
}
// 集合接口
interface IterableCollection<T> {
Iterator<T> createIterator();
}
// 具体集合:音乐播放列表
class MusicPlaylist implements IterableCollection<String> {
private final String[] playlist;
public MusicPlaylist(String[] playlist) {
this.playlist = playlist;
}
@Override
public Iterator<String> createIterator() {
return new MusicPlaylistIterator(playlist);
}
}
// 具体迭代器
class MusicPlaylistIterator implements Iterator<String> {
private final String[] playlist;
private int currentIndex = 0;
public MusicPlaylistIterator(String[] playlist) {
this.playlist = playlist;
}
@Override
public boolean hasNext() {
return currentIndex < playlist.length;
}
@Override
public String next() {
return playlist[currentIndex++];
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
String[] playlist = {"《Slow Down》", "《Shadow Of The Sun》", "《Normal No More 》", "《There For You》", "《STAY》"};
MusicPlaylist musicPlaylist = new MusicPlaylist(playlist);
Iterator<String> iterator = musicPlaylist.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
/*
《Slow Down》
《Shadow Of The Sun》
《Normal No More 》
《There For You》
《STAY》
*/
}
4. 类关系图
5. 应用场景
- 当需要遍历一个集合,而又不想暴露其内部表示时,可以使用迭代器模式。
- 迭代器模式可以将遍历操作封装在迭代器中,使得客户端可以逐个访问集合的元素,而不需要了解集合的内部结构。
6. 易错点
- 在实现迭代器类时,需要确保迭代器能够正确遍历集合,并在遍历结束后返回正确的状态。
- 在实现集合类时,需要正确创建迭代器对象,并确保迭代器能够访问集合的元素。
7. 优缺点
优点
- 将集合的遍历操作封装在迭代器中,使得客户端可以逐个访问集合的元素,而不需要了解集合的内部结构。
- 迭代器模式增加了集合类的灵活性,可以通过不同的迭代器实现不同的遍历方式。
缺点
- 对于简单的集合类,使用迭代器模式可能会增加代码的复杂性。
8. 总结
迭代器模式是一种行为型设计模式,用于提供一种顺序访问集合中各个元素的方法,而不需要暴露其内部表示。迭代器模式适用于需要遍历一个集合,而又不想暴露其内部结构的场景。它将集合的遍历操作封装在迭代器中,使得客户端可以逐个访问集合的元素,并且能够灵活地切换遍历方式。在使用迭代器模式时,需要注意确保迭代器能够正确遍历集合,并且集合类能够正确创建迭代器对象。
中介者模式(Mediator Pattern)
1. 简单介绍
中介者模式(Mediator Pattern)用一个中介对象来封装一系列对象之间的交互,使得对象之间不再直接相互引用,降低耦合性。
2. 实际问题
假设你正在开发一个多用户聊天室应用程序,用户可以在聊天室中发送消息。每个用户可以看到其他用户发送的消息,并且可以回复消息。你需要一种方式来管理用户之间的通信,并确保消息能够正确地传递给目标用户。
3. 解决方案
在这个示例中,ChatRoomMediator
接口是中介者接口,定义了一个发送消息的方法 sendMessage()
。ChatRoom
是具体中介者类,表示聊天室,它实现了发送消息的逻辑。User
是用户类,每个用户都有一个名字和一个中介者,当用户发送消息时,调用中介者的 sendMessage()
方法来发送消息。
// 中介者接口
interface ChatRoomMediator {
void sendMessage(String message, User user);
}
// 具体中介者:聊天室
class ChatRoom implements ChatRoomMediator {
@Override
public void sendMessage(String message, User user) {
System.out.println(user.getName() + " 发送消息:" + message);
}
}
// 用户类
class User {
private String name;
private ChatRoomMediator mediator;
public User(String name, ChatRoomMediator mediator) {
this.name = name;
this.mediator = mediator;
}
public String getName() {
return name;
}
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建聊天室中介者
ChatRoomMediator chatRoom = new ChatRoom();
// 创建用户
User user1 = new User("User1", chatRoom);
User user2 = new User("User2", chatRoom);
User user3 = new User("User3", chatRoom);
// 用户发送消息
user1.sendMessage("Hello everyone!");
user2.sendMessage("Hi User1!");
user3.sendMessage("Nice to meet you all!");
}
/*
User1 发送消息:Hello everyone!
User2 发送消息:Hi User1!
User3 发送消息:Nice to meet you all!
*/
}
4. 类关系图
5. 应用场景
- 当多个对象之间存在复杂的交互关系,且对象之间不直接相互交互,而是通过一个中介者来进行通信时,可以使用中介者模式。
- 中介者模式可以减少对象之间的耦合性,并且使得系统更易于维护和扩展。
6. 易错点
- 在设计中介者接口时,需要考虑对象之间交互的方式和需要传递的参数,确保中介者能够正确地传递消息给目标对象。
7. 优缺点
优点
- 将对象之间的交互封装在中介者中,减少了对象之间的耦合性,使得系统更易于维护和扩展。
- 中介者模式可以将复杂的交互关系简化,使得系统的逻辑更清晰。
缺点
- 如果中介者对象过于庞大,可能会导致中介者本身的复杂性增加,影响系统的性能和可维护性。
8. 总结
中介者模式是一种行为型设计模式,用于封装一组对象之间的交互。它定义了一个中介者对象,用于管理对象之间的通信,减少对象之间的耦合性,并且使得系统更易于维护和扩展。中介者模式适用于多个对象之间存在复杂的交互关系,且对象之间不直接相互交互的场景。在使用中介者模式时,需要注意设计中介者接口,确保中介者能够正确地传递消息给目标对象。
备忘录模式(Memento Pattern)
1. 简单介绍
备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,以便在需要时恢复该状态。
2. 实际问题
假设你正在开发一个文本编辑器应用程序,用户可以在编辑器中输入和编辑文本。用户可能会频繁地对文本进行修改,但是有时候可能会误操作或者想要撤销之前的修改。你需要一种方法来保存编辑器的历史状态,以便用户可以随时撤销到之前的状态。
3. 解决方案
在这个示例中,EditorMemento
是备忘录类,用于保存文本编辑器的状态。TextEditor
是发起人类,表示文本编辑器,它包含了编辑器的内容和保存状态、恢复状态的方法。EditorHistory
是管理者类,用于保存和恢复编辑器的历史状态。
// 备忘录:保存编辑器的状态
class EditorMemento {
private String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
// 发起人:文本编辑器
class TextEditor {
private String content="";
public void write(String text) {
content += text;
}
public String getContent() {
return content;
}
public EditorMemento save() {
return new EditorMemento(content);
}
public void restore(EditorMemento memento) {
content = memento.getContent();
}
}
// 管理者:保存和恢复编辑器状态
class EditorHistory {
private Stack<EditorMemento> history = new Stack<>();
public void save(EditorMemento memento) {
history.push(memento);
}
public EditorMemento pop() {
return history.pop();
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建文本编辑器与备忘录
TextEditor editor = new TextEditor();
EditorHistory history = new EditorHistory();
// 编辑并保存文本
editor.write("第一行文字\n");
history.save(editor.save());
editor.write("第二行文字\n");
history.save(editor.save());
editor.write("第三行文字\n");
// 恢复最后一次保存的状态
editor.restore(history.pop());
// 输出结果:第一行文字\n第二行文字\n
System.out.println(editor.getContent());
}
}
4. 类关系图
5. 应用场景
- 当需要在不破坏对象封装性的前提下,保存和恢复对象的内部状态时,可以使用备忘录模式。
- 备忘录模式允许在后续的时间点将对象恢复到之前保存的状态,适用于需要撤销或回滚操作的场景。
6. 易错点
- 在实现备忘录类时,需要确保备忘录对象保存的状态可以被外部访问,但不能被直接修改,以保证备忘录对象的封装性。
7. 优缺点
优点
- 允许在后续的时间点将对象恢复到之前保存的状态,提供了撤销或回滚操作的能力。
- 对象的状态保存在备忘录中,可以避免在发起人中直接暴露对象的内部状态。
缺点
- 如果备忘录对象过多,可能会导致内存消耗较大。
- 如果备忘录对象需要频繁创建和恢复,可能会影响系统的性能。
8. 总结
备忘录模式是一种行为型设计模式,用于在不破坏封装性的前提下,保存和恢复对象的内部状态。备忘录模式允许在后续的时间点将对象恢复到之前保存的状态,提供了撤销或回滚操作的能力。它适用于需要保存历史状态,以便在后续操作中恢复状态的场景。在使用备忘录模式时,需要注意备忘录对象的封装性,确保状态可以被外部访问,但不能被直接修改。
观察者模式(Observer Pattern)
观察者模式(Observer Pattern)定义对象之间的一种一对多的依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会得到通知并自动更新。
1. 实际问题
假设你正在开发一个天气预报应用程序,你需要实现一个功能,让用户可以订阅并接收实时的天气信息。用户可以选择订阅多个城市的天气信息,并在天气发生变化时及时收到通知。
3. 解决方案
在这个示例中,Subject
是主题接口,定义了添加观察者、移除观察者和通知观察者的方法。Observer
是观察者接口,定义了更新状态的方法。WeatherForecast
是具体主题类,表示天气预报,它维护了一个观察者列表,并在状态发生变化时通知观察者。User
是具体观察者类,表示用户,当收到通知时,更新天气预报。
// 主题接口:被观察者
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(String message);
}
// 具体主题:天气预报
class WeatherForecast implements Subject {
private List<Observer> observers = new ArrayList<>();
private String weatherMessage;
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(weatherMessage);
}
}
public void setWeatherMessage(String weatherMessage) {
this.weatherMessage = weatherMessage;
notifyObservers();
}
}
// 具体观察者:用户
class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " 收到天气预报:" + message);
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建天气预报对象
WeatherForecast weatherForecast = new WeatherForecast();
// 创建观察者对象
User user1 = new User("张三");
User user2 = new User("李四");
// 添加观察者
weatherForecast.addObserver(user1);
weatherForecast.addObserver(user2);
// 设置天气预报信息,触发通知
weatherForecast.setWeatherMessage("明天有雨");
// 输出结果
// 张三 收到天气预报:明天有雨
// 李四 收到天气预报:明天有雨
// 移除观察者user2
weatherForecast.removeObserver(user2);
// 设置天气预报信息,触发通知
weatherForecast.setWeatherMessage("后天晴朗");
// 输出结果
// 张三 收到天气预报:后天晴朗
}
}
4. 类关系图
5. 应用场景
- 当一个对象的状态发生变化时,需要通知多个依赖对象,并且不希望主题和观察者之间紧密耦合时,可以使用观察者模式。
- 观察者模式允许主题独立地改变状态,而不影响观察者。
6. 易错点
- 在实现观察者模式时,需要注意主题对象应该维护观察者列表,并在状态发生变化时通知观察者。
- 观察者的更新逻辑应该与主题的逻辑分离,确保观察者和主题之间解耦。
7. 优缺点
优点
- 观察者模式实现了一种一对多的依赖关系,允许主题独立地改变状态,而不影响观察者。
- 可以动态地添加和移除观察者,使得系统更灵活和可扩展。
缺点
- 如果观察者较多或观察者的更新逻辑较复杂,可能会导致通知观察者的时间较长,影响系统的性能。
- 如果观察者和主题之间存在循环依赖,可能会导致系统的复杂性增加。
8. 总结
观察者模式是一种行为型设计模式,用于实现一种一对多的依赖关系,当一个对象的状态发生变化时,其依赖对象(观察者)会自动收到通知并更新。观察者模式允许主题(被观察者)和观察者之间解耦,使得主题可以独立地改变状态而不影响观察者。它适用于需要通知多个依赖对象的场景,如天气预报、事件处理等。在实现观察者模式时,需要注意主题对象维护观察者列表,并在状态发生变化时通知观察者,同时确保观察者和主题之间解耦。
状态模式(State Pattern)
1. 简单介绍
状态模式(State Pattern)允许对象在内部状态改变时改变其行为,使得对象看起来像是修改了其类。
2. 实际问题
假设你正在开发一个电梯控制系统,电梯有多个状态,包括开门状态、关门状态、运行状态和停止状态。在不同的状态下,电梯会有不同的行为,比如在运行状态下,电梯可以响应楼层按钮,而在停止状态下,电梯只能等待用户操作。
3. 解决方案
在这个示例中,ElevatorState
是状态接口,定义了电梯的四种状态:开门、关门、上行和下行。OpenState
、CloseState
、RunningState
和 StoppedState
是具体状态类,分别表示电梯的四种状态,它们实现了状态接口的方法。Elevator
是环境类,表示电梯,它维护了当前的状态,并提供了可以改变状态的方法。
// 状态接口
interface ElevatorState {
void openDoor();
void closeDoor();
void goUp();
void goDown();
}
// 具体状态:开门状态
class OpenState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("电梯已经是开门状态");
}
@Override
public void closeDoor() {
System.out.println("电梯关门");
}
@Override
public void goUp() {
System.out.println("电梯开始上行");
}
@Override
public void goDown() {
System.out.println("电梯开始下行");
}
}
// 具体状态:关门状态
class CloseState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("电梯开门");
}
@Override
public void closeDoor() {
System.out.println("电梯已经是关门状态");
}
@Override
public void goUp() {
System.out.println("电梯开始上行");
}
@Override
public void goDown() {
System.out.println("电梯开始下行");
}
}
// 具体状态:运行状态
class RunningState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("电梯不能在运行时开门");
}
@Override
public void closeDoor() {
System.out.println("电梯不能在运行时关门");
}
@Override
public void goUp() {
System.out.println("电梯继续上行");
}
@Override
public void goDown() {
System.out.println("电梯继续下行");
}
}
// 具体状态:停止状态
class StoppedState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("电梯开门");
}
@Override
public void closeDoor() {
System.out.println("电梯关门");
}
@Override
public void goUp() {
System.out.println("电梯开始上行");
}
@Override
public void goDown() {
System.out.println("电梯开始下行");
}
}
// 环境类:电梯
class Elevator {
private ElevatorState currentState;
public Elevator() {
currentState = new StoppedState();
}
public void setState(ElevatorState state) {
currentState = state;
}
public void openDoor() {
currentState.openDoor();
}
public void closeDoor() {
currentState.closeDoor();
}
public void goUp() {
currentState.goUp();
}
public void goDown() {
currentState.goDown();
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建电梯对象
Elevator elevator = new Elevator();
// 设置电梯状态为打开
elevator.setState(new OpenState());
elevator.openDoor(); // 电梯已经是开门状态
// 设置电梯状态为关闭
elevator.setState(new CloseState());
elevator.closeDoor(); // 电梯关门
// 设置电梯状态为运行
elevator.setState(new RunningState());
elevator.goUp(); // 电梯继续上行
// 设置电梯状态为停止
elevator.setState(new StoppedState());
elevator.openDoor(); // 电梯开门
}
}
4. 类关系图
5. 应用场景
- 当一个对象的行为随着内部状态的改变而改变时,可以使用状态模式。
- 状态模式可以将对象的行为封装在不同的状态类中,使得状态转换更加灵活和可扩展。
6. 易错点
- 在实现状态模式时,需要确保环境类正确维护当前的状态,并在状态改变时能够正确地调用相应状态的方法。
7. 优缺点
优点
- 将对象的行为封装在不同的状态类中,使得状态转换更加灵活和可扩展。
- 避免了使用大量的条件语句判断对象的状态,使得代码更加清晰和易于维护。
缺点
- 当状态较多或状态之间转换较为复杂时,可能会导致状态类的增加,增加了系统的复杂性。
8. 总结
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变其行为。状态模式将对象的行为封装在不同的状态类中,当对象的状态发生改变时,它的行为也会随之改变。状态模式适用于当对象的行为随着内部状态的改变而改变的场景,比如电梯控制系统、游戏角色状态等。在实现状态模式时,需要确保环境类正确维护当前的状态,并在状态改变时能够正确地调用相应状态的方法。
策略模式(Strategy Pattern)
1. 简单介绍
策略模式(Strategy Pattern)定义一系列算法,并使其可以相互替换,使得算法的变化独立于使用算法的客户端。
2. 实际问题
假设你正在开发一个电商网站,用户在购买商品时可以选择不同的支付方式,比如支付宝、微信支付、信用卡支付等。你需要设计一个灵活的支付系统,使得可以根据用户选择的支付方式来执行相应的支付逻辑。
3. 解决方案
在这个示例中,PaymentStrategy
是支付策略接口,定义了支付的方法 pay()
。AlipayStrategy
和 WechatPayStrategy
是具体支付策略类,表示支付宝支付和微信支付,它们实现了支付策略接口的方法。PaymentContext
是上下文类,表示支付系统,它包含了一个支付策略的引用,并提供了设置支付策略和执行支付的方法。
// 支付策略接口
interface PaymentStrategy {
void pay(double amount);
}
// 具体支付策略:支付宝支付
class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount + "元");
}
}
// 具体支付策略:微信支付
class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付:" + amount + "元");
}
}
// 上下文类:支付系统
class PaymentContext {
private final PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(double amount) {
strategy.pay(amount);
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
PaymentContext alipay = new PaymentContext(new AlipayStrategy());
alipay.executePayment(12);
PaymentContext wechatPay = new PaymentContext(new WechatPayStrategy());
wechatPay.executePayment(24);
}
/*
使用支付宝支付:12.0元
使用微信支付:24.0元
*/
}
4. 类关系图
5. 应用场景
- 当有多个算法可以选择,并且需要在运行时根据不同的条件选择合适的算法时,可以使用策略模式。
- 策略模式允许客户端在运行时选择合适的算法,而不需要改变客户端的代码。
6. 易错点
- 在实现策略模式时,需要确保策略类实现了相同的接口,以便在上下文类中可以统一使用。
7. 优缺点
优点
- 将算法封装在独立的策略类中,使得算法可以互相替换,增加了系统的灵活性和可扩展性。
- 客户端可以在运行时选择合适的算法,而不需要改变客户端的代码,提高了系统的可维护性和扩展性。
缺点
- 当策略较多或策略之间逻辑较复杂时,可能会导致策略类的增加,增加了系统的复杂性。
8. 总结
策略模式是一种行为型设计模式,它定义了一系列算法族,并将每个算法封装在独立的策略类中,使得算法可以互相替换。策略模式适用于当有多个算法可以选择,并且需要在运行时根据不同的条件选择合适的算法的场景。在实现策略模式时,需要确保策略类实现了相同的接口,以便在上下文类中可以统一使用。
模板方法模式(Template Method Pattern)
1. 简单介绍
模板方法模式(Template Method Pattern)定义一个算法的骨架,将一些步骤的实现延迟到子类中。
2. 实际问题
假设你正在开发一个咖啡店点单系统,该系统需要支持不同类型的咖啡,如美式咖啡、拿铁咖啡和卡布奇诺等。不同类型的咖啡在制作过程中可能有一些不同的步骤,但整体制作流程是相同的。你希望设计一个通用的咖啡制作模板,使得每种类型的咖啡可以共享通用的制作流程,并且允许子类来实现特定类型咖啡的特殊步骤。
3. 解决方案
在这个示例中,CoffeeTemplate
是咖啡制作模板,定义了咖啡的制作流程,其中 makeCoffee()
方法是模板方法,包含了制作咖啡的通用流程,而 boilWater()
、brewCoffee()
、pourIntoCup()
和 addCondiments()
是具体步骤,其中 brewCoffee()
和 addCondiments()
是抽象方法,由子类具体实现。AmericanoCoffee
和 LatteCoffee
是具体咖啡类,分别表示美式咖啡和拿铁咖啡,它们继承自咖啡制作模板,并实现了特定类型咖啡的特殊步骤。
// 咖啡制作模板
abstract class CoffeeTemplate {
public final void makeCoffee() {
boilWater();
brewCoffee();
pourIntoCup();
addCondiments();
}
protected void boilWater() {
System.out.println("烧水");
}
protected abstract void brewCoffee();
protected void pourIntoCup() {
System.out.println("倒入杯中");
}
protected abstract void addCondiments();
}
// 具体咖啡:拿铁咖啡
class LatteCoffee extends CoffeeTemplate {
@Override
protected void brewCoffee() {
System.out.println("冲泡拿铁咖啡");
}
@Override
protected void addCondiments() {
System.out.println("加入牛奶");
}
}
// 具体咖啡:美式咖啡
class AmericanoCoffee extends CoffeeTemplate {
@Override
protected void brewCoffee() {
System.out.println("冲泡美式咖啡");
}
@Override
protected void addCondiments() {
// 美式咖啡不加调料
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
CoffeeTemplate americanoCoffee = new AmericanoCoffee();
americanoCoffee.makeCoffee();
System.out.println("===========");
CoffeeTemplate latteCoffee = new LatteCoffee();
latteCoffee.makeCoffee();
}
/*
烧水
冲泡美式咖啡
倒入杯中
===========
烧水
冲泡拿铁咖啡
倒入杯中
加入牛奶
*/
}
4. 类关系图
5. 应用场景
- 当有一个算法的流程固定,但其中某些步骤可能会有所不同,并且希望通过子类来实现这些不同的步骤时,可以使用模板方法模式。
- 模板方法模式允许将算法的结构封装在一个模板中,而将具体的实现细节交给子类。
6. 易错点
- 在实现模板方法模式时,需要确保模板方法是
final
或者不能被子类覆盖,以防止子类改变算法的骨架。
7. 优缺点
优点
- 将算法的骨架封装在一个模板方法中,使得算法的结构更加清晰和易于理解。
- 允许子类在不改变算法结构的情况下重新定义算法的某些步骤,增加了系统的灵活性和可扩展性。
缺点
- 当模板方法过于复杂或有过多的具体步骤时,可能会导致子类的增加,增加了系统的复杂性。
8. 总结
模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并将一些步骤延迟到子类中实现。模板方法模式适用于当有一个算法的流程固定,但其中某些步骤可能会有所不同的场景。在实现模板方法模式时,需要确保模板方法是 final
或者不能被子类覆盖,以防止子类改变算法的骨架。
访问者模式(Visitor Pattern)
1. 简单介绍
访问者模式(Visitor Pattern)在不改变对象结构的前提下,定义作用于某个对象结构中的各个元素的操作。
2. 实际问题
假设你正在开发一个旅游景点导览系统,该系统需要展示不同类型的景点,如博物馆、动物园和公园等。每种类型的景点都有不同的展示方式,比如博物馆需要展示历史文物,动物园需要展示各种动物,公园需要展示美丽的花园。你希望设计一个通用的展示系统,使得可以根据不同类型的景点来展示不同的内容。
3. 解决方案
在这个示例中,Element
是元素接口,定义了接受访问者的方法 accept(Visitor visitor)
。Museum
、Zoo
和 Park
是具体元素类,表示博物馆、动物园和公园,它们实现了元素接口的方法,并在 accept
方法中将自身作为参数传递给访问者。Visitor
是访问者接口,定义了访问者的方法 visit(Museum museum)
、visit(Zoo zoo)
和 visit(Park park)
。DisplayVisitor
是具体访问者类,表示展示访问者,它实现了访问者接口的方法,并在每个方法中根据具体元素的类型展示相应的内容。
// 元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体元素:博物馆
class Museum implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 博物馆特有方法
public String getExhibition() {
return "历史文物展览";
}
}
// 具体元素:动物园
class Zoo implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 动物园特有方法
public String getAnimals() {
return "狮子、大象、长颈鹿等";
}
}
// 具体元素:公园
class Park implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 公园特有方法
public String getGarden() {
return "美丽的花园";
}
}
// 访问者接口
interface Visitor {
void visit(Museum museum);
void visit(Zoo zoo);
void visit(Park park);
}
// 具体访问者:展示访问者
class DisplayVisitor implements Visitor {
@Override
public void visit(Museum museum) {
System.out.println("展示博物馆,展览内容:" + museum.getExhibition());
}
@Override
public void visit(Zoo zoo) {
System.out.println("展示动物园,动物:" + zoo.getAnimals());
}
@Override
public void visit(Park park) {
System.out.println("展示公园,花园:" + park.getGarden());
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
// 创建元素对象
Element museum = new Museum();
Element zoo = new Zoo();
Element park = new Park();
// 创建访问者对象
Visitor displayVisitor = new DisplayVisitor();
// 对元素应用访问者
museum.accept(displayVisitor);
zoo.accept(displayVisitor);
park.accept(displayVisitor);
}
/*
展示博物馆,展览内容:历史文物展览
展示动物园,动物:狮子、大象、长颈鹿等
展示公园,花园:美丽的花园
*/
}
4. 类关系图
5. 应用场景
- 当有一个数据结构中包含不同类型的元素,并且希望根据不同的元素执行不同的操作时,可以使用访问者模式。
- 访问者模式允许将算法从数据结构中分离出来,使得可以在不改变数据结构的情况下定义新的算法。
6. 易错点
- 在实现访问者模式时,需要确保每个具体元素类都正确实现了接受访问者的方法,并在其中调用访问者的对应方法。
7. 优缺点
优点
- 将算法从数据结构中分离出来,增加了系统的灵活性和可扩展性。新的访问者可以在不改变数据结构的情况下定义新的操作。
缺点
- 当元素较多或元素的类型较复杂时,可能会导致访问者类的增加,增加了系统的复杂性。
8. 总结
访问者模式是一种行为型设计模式,它允许将算法从数据结构中分离出来,使得可以在不改变数据结构的情况下定义新的算法。访问者模式适用于当有一个数据结构中包含不同类型的元素,并且希望根据不同的元素执行不同的操作的场景。在实现访问者模式时,需要确保每个具体元素类都正确实现了接受访问者的方法,并在其中调用访问者的对应方法。
必要性拓展(Necessity Expansion)
简单工厂模式(Simple Factory Pattern)
1. 简单介绍
简单工厂模式(Simple Factory Pattern)虽然它不在 GoF 的列表中,但在实际开发中,简单工厂模式是一种经常被使用的设计模式。它简化了对象的创建过程,提高了代码的可维护性和扩展性。
2. 实际问题
假设你正在开发一个计算器应用程序,用户可以输入两个操作数和运算符,然后应用程序会根据输入的运算符进行相应的计算,并返回结果。不同的运算符对应不同的计算方法,例如加法、减法、乘法和除法等。你希望设计一个通用的计算器工厂,根据用户输入的运算符,生产出相应的计算器对象,从而实现不同运算的计算功能。
3. 实现方式
// 运算器接口
interface Operator {
double calculate(double operand1, double operand2);
}
// 具体运算器:加法运算器
class AdditionOperator implements Operator {
@Override
public double calculate(double operand1, double operand2) {
return operand1 + operand2;
}
}
// 具体运算器:减法运算器
class SubtractionOperator implements Operator {
@Override
public double calculate(double operand1, double operand2) {
return operand1 - operand2;
}
}
// 具体运算器:乘法运算器
class MultiplicationOperator implements Operator {
@Override
public double calculate(double operand1, double operand2) {
return operand1 * operand2;
}
}
// 具体运算器:除法运算器
class DivisionOperator implements Operator {
@Override
public double calculate(double operand1, double operand2) {
if (operand2 == 0) {
throw new IllegalArgumentException("除数不能为零");
}
return operand1 / operand2;
}
}
// 简单工厂类
class CalculatorFactory {
public static Operator createOperator(String operatorType) {
switch (operatorType) {
case "+":
return new AdditionOperator();
case "-":
return new SubtractionOperator();
case "*":
return new MultiplicationOperator();
case "/":
return new DivisionOperator();
default:
throw new IllegalArgumentException("不支持的运算符:" + operatorType);
}
}
}
// 调用代码
public class XZ {
public static void main(String[] args) {
Operator operator1 = CalculatorFactory.createOperator("+");
System.out.println(operator1.calculate(1, 2));
Operator operator2 = CalculatorFactory.createOperator("-");
System.out.println(operator2.calculate(1, 2));
Operator operator3 = CalculatorFactory.createOperator("*");
System.out.println(operator3.calculate(1, 2));
Operator operator4 = CalculatorFactory.createOperator("/");
System.out.println(operator4.calculate(1, 2));
// Operator operator5 = CalculatorFactory.createOperator("%");
// System.out.println(operator5.calculate(1, 2));
// 执行没有的 Operator 抛出异常
}
}
在这个示例中,Operator
是运算器接口,定义了一个 calculate()
方法来进行运算。AdditionOperator
、SubtractionOperator
、MultiplicationOperator
和 DivisionOperator
是具体运算器类,分别表示加法运算器、减法运算器、乘法运算器和除法运算器,它们都实现了运算器接口的方法。CalculatorFactory
是简单工厂类,它通过 createOperator()
方法来根据用户输入的运算符类型,创建相应的运算器对象。
4. 类关系图
classDiagram
direction BT
class AdditionOperator {
+ calculate(double, double) double
+ AdditionOperator()
}
class CalculatorFactory {
+ createOperator(String) Operator
+ CalculatorFactory()
}
class DivisionOperator {
+ calculate(double, double) double
+ DivisionOperator()
}
class MultiplicationOperator {
+ calculate(double, double) double
+ MultiplicationOperator()
}
class Operator {
<<Interface>>
+ calculate(double, double) double
}
class SubtractionOperator {
+ calculate(double, double) double
+ SubtractionOperator()
}
class XZ {
+ main(String[]) void
+ XZ()
}
AdditionOperator ..> Operator
CalculatorFactory ..> AdditionOperator : «create»
CalculatorFactory ..> DivisionOperator : «create»
CalculatorFactory ..> MultiplicationOperator : «create»
CalculatorFactory ..> SubtractionOperator : «create»
DivisionOperator ..> Operator
MultiplicationOperator ..> Operator
SubtractionOperator ..> Operator
5. 应用场景
- 当需要根据不同的条件创建不同类型的对象时,可以使用简单工厂模式。
- 简单工厂模式将对象的创建逻辑封装在工厂类中,使得客户端不需要知道具体的对象创建过程,只需要通过工厂类来获取对象。
6. 易错点
- 在实现简单工厂模式时,需要注意工厂类的职责,它应该负责根据条件创建相应的对象,而不应该承担过多的业务逻辑。
7. 优缺点
优点
- 将对象的创建逻辑封装在工厂类中,使得客户端不需要关心对象的具体创建过程,降低了客户端和具体对象的耦合。
- 简单工厂模式可以通过工厂类来统一管理对象的创建,便于代码的维护和管理。
缺点
- 如果需要添加新的运算器类型,需要修改工厂类的代码,这可能违反了开闭原则,导致工厂类的修改。
8. 总结
简单工厂模式是一种创建型设计模式,它通过一个工厂类来创建不同类型的对象,使得客户端不需要关心对象的具体创建过程。简单工厂模式适用于当需要根据不同的条件创建不同类型的对象时。在实现简单工厂模式时,需要注意工厂类的职责,它应该负责根据条件创建相应的对象,而不应该承担过多的业务逻辑。虽然简单工厂模式有一些优点,但也有一些缺点,例如如果需要添加新的对象类型,可能需要修改工厂类的代码,这可能违反了开闭原则。因此,在应用简单工厂模式时需要权衡考虑,选择合适的设计方案。
空对象模式(Null Object Pattern)
1. 简单介绍
空对象模式(Null Object Pattern)虽然它不在 GoF 的列表中,但在实际开发中,我们通常使用空对象模式(Null Object Pattern)来替代传统的null值或者抛出异常的做法。空对象模式是一种创建一个默认对象的设计模式,这个默认对象包含了类似于对null的处理,但是避免了空指针异常的出现。如果有NULL对象被调用,就会返回一个默认的对象而不是null值。
2. 实际问题
在一个电商网站中,我们需要对商品进行分类、下单、支付等操作。当有用户查询或者购买不存在的商品时,我们需要能够处理该异常情况,确保不会出现空指针异常等错误,同时也需要保证程序正常运行。
3. 解决方案
// 商品抽象类
public abstract class Product {
protected String name;
protected double price;
public abstract String getName();
public abstract double getPrice();
}
// 商品类
public class ConcreteProduct extends Product {
public ConcreteProduct(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
// 空商品类
public class NullProduct extends Product {
public NullProduct() {
this.name = "null";
this.price = 0.0;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
// 商品工厂类
public class ProductFactory {
private static final Map<String, Product> products = new HashMap<>();
static {
products.put("product1", new ConcreteProduct("Product 1", 100.0));
products.put("product2", new ConcreteProduct("Product 2", 200.0));
}
public static Product getProduct(String productName) {
Product product = products.get(productName);
if (product != null) {
return product;
}
return new NullProduct();
}
}
// 调用代码的类
public class XZ {
public static void main(String[] args) {
Product product1 = ProductFactory.getProduct("product1");
System.out.println("product 1 name: " + product1.getName() + ", price: " + product1.getPrice());
Product product3 = ProductFactory.getProduct("product3");
System.out.println("product 3 name: " + product3.getName() + ", price: " + product3.getPrice());
}
}
4. 类关系图
5. 应用场景
- 当对象应该具有某些行为,但是目前没有能够实现它们时。
- 当一个空值(null)带来了麻烦,并且需要将其替换为默认值。
6. 易错点
- 确认返回的空对象与需要替代的空值的类型相同。
- 空对象的实现必须符合原接口的规范,以确保替代效果正确。
7. 优缺点
优点
- 避免了空指针异常的出现。
- 可以让代码更具可读性和简洁性。
- 可以避免频繁检查null值引起的代码冗余。
缺点
- 可能会增加代码的复杂性。
- 创建不必要的对象可能会占用内存空间。
8. 总结
空对象模式是一种非常实用的设计模式,可以有效地解决空值的问题,并且避免了空指针异常的出现。在项目开发中,我们可以根据实际需要,使用空对象模式对代码进行优化和重构,使之更加健壮、易读和易于维护。
工厂模式与策略模式(Factory And Strategy Pattern)
1. 工厂模式(Factory Pattern)
工厂模式是一种创建对象的设计模式。它通过定义一个公共的接口或抽象类,并由具体的工厂类来创建对象实例。工厂模式将对象的创建与使用分离,客户端只需要与工厂进行交互,而无需直接创建对象。
主要特点
- 定义一个公共的接口或抽象类作为产品的基类。
- 使用具体的工厂类来创建具体的产品实例。
- 客户端通过工厂类获取所需产品的实例。
适用场景
- 当一个类不知道它所需要的具体对象类型时,可以使用工厂模式来创建对象。
- 当一个类希望由其子类来指定创建对象的具体类型时,可以使用工厂模式。
2. 策略模式(Strategy Pattern)
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装成单独的类,使它们可以相互替换。策略模式使得算法的变化独立于使用算法的客户端。
主要特点
- 将可变的行为封装成独立的策略类。
- 客户端通过上下文类来选择和使用具体的策略。
适用场景
- 当一个系统需要在多个算法中选择一种,并且希望在运行时可以动态切换算法时,可以使用策略模式。
- 当一个类中有多种变化的行为,每种行为都可以通过继承或接口来实现时,可以使用策略模式。
3. 区别总结(Difference Summary)
- 工厂模式是创建型模式,关注的是对象的创建过程;策略模式是行为型模式,关注的是算法的封装和切换。
- 工厂模式通过工厂类来创建对象,而策略模式通过上下文类来选择和使用算法。
- 工厂模式强调对象的创建过程和客户端与产品之间的解耦,而策略模式强调封装可变的行为并使其可以相互替换。
- 工厂模式一般用于创建对象的场景,而策略模式一般用于算法的封装和切换场景。
设计模式拓展(Design Mode Expansion)
设计模式是一种用于解决特定问题的通用解决方案,它们是经过验证的、被广泛接受的最佳实践。除了常见的设计模式,还有一些拓展和衍生的设计模式,以及一些设计原则和编程技巧可以进一步优化和扩展设计模式的应用。
- 并发设计模式:并发编程中有一些特定的设计模式,用于解决多线程和并发访问的问题,如锁、信号量、阻塞队列等。
- 函数式编程的设计模式:随着函数式编程的兴起,也出现了一些与函数式编程相关的设计模式,如柯里化、函数组合、惰性求值等。
- 架构模式:除了针对具体问题的设计模式,还有一些面向整个软件系统架构的设计模式,如分层架构、微服务架构等。
- 响应式编程模式:响应式编程是一种面向异步数据流和事件驱动的编程范式,它在处理事件和数据流时使用了一些特定的设计模式,如观察者模式和迭代器模式。
- 数据访问设计模式:用于解决数据访问层的设计问题,如数据访问对象模式(DAO)、仓储模式等。
这些拓展和衍生的设计模式都是在特定场景下为了解决特定问题而产生的,它们丰富了设计模式的应用范围,并提供了更多的解决方案供开发者选择。在实际应用中,我们需要根据具体的问题和需求选择合适的设计模式,或者结合多种设计模式来构建出更复杂、更灵活的系统。同时,也应该注意不要滥用设计模式,只有在合适的场景下使用合适的设计模式才能真正发挥它们的优势。
玄子Share 设计模式 GOF 全23种 + 七大设计原则 9.25