前言: Java 中,常见的设计模式包含 23 种,这些设计模式分为三种类型:创建型模式、结构型模式和行为型模式。每种设计模式都解决了特定类型的问题,并具有各自的优缺点和适用场景。
创建型模式(5种)
单例模式(Singleton Pattern):确保类只有一个实例,并提供全局访问点。
工厂模式(Factory Pattern):定义一个用于创建对象的接口,让子类决定实例化哪个类。
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
建造者模式(Builder Pattern):将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
原型模式(Prototype Pattern):通过复制现有对象来创建新对象,而不是通过实例化类。
结构型模式(7种)
适配器模式(Adapter Pattern):将一个类的接口转换成客户端所期待的另一个接口,使得原本不兼容的接口能够一起工作。
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们可以独立变化。
组合模式(Composite Pattern):将对象组合成树形结构以表示”部分-整体”的层次结构,使得客户端统一对待单个对象和组合对象。
装饰者模式(Decorator Pattern):动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
外观模式(Facade Pattern):为子系统中的一组接口提供一个统一的接口,以简化子系统的使用。
享元模式(Flyweight Pattern):通过共享技术实现相同或相似对象的重用,以减少内存使用和提高性能。
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
行为型模式(11种)
责任链模式(Chain of Responsibility Pattern):为解除请求的发送者和接收者之间的耦合,而使多个对象都有机会处理请求。
命令模式(Command Pattern):将请求封装成对象,以便参数化客户端调用的行为。
解释器模式(Interpreter Pattern):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
迭代器模式(Iterator Pattern):提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部表示。
中介者模式(Mediator Pattern):用一个中介对象封装一系列对象的交互,从而使对象间的交互松耦合。
备忘录模式(Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,使得当一个对象状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
状态模式(State Pattern):允许对象在其内部状态改变时改变它的行为。
策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,并使它们可以互换。
模板方法模式(Template Method Pattern):定义一个算法的骨架,而将一些步骤延迟到子类中。
访问者模式(Visitor Pattern):在不改变已有类的情况下,定义对这些类新的操作。
创建型模式 单列模式 单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。单例模式通常用于需要全局唯一对象的场景,例如配置管理类、日志类、线程池等。
实现单例模式的几种方式 1. 饿汉式(Eager Initialization) 在类加载时就创建实例,这种方式简单但在类加载时就完成实例化,可能会造成资源浪费。
示例代码 1 2 3 4 5 6 7 8 9 10 11 public class EagerSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton (); private EagerSingleton () { } public static EagerSingleton getInstance () { return INSTANCE; } }
2. 懒汉式(Lazy Initialization) 在需要时才创建实例,线程不安全,需要额外的同步控制。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class LazySingleton { private static LazySingleton instance; private LazySingleton () { } public static LazySingleton getInstance () { if (instance == null ) { instance = new LazySingleton (); } return instance; } }
3. 线程安全的懒汉式(Synchronized Method) 通过同步来保证线程安全,但会影响性能。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SynchronizedSingleton { private static SynchronizedSingleton instance; private SynchronizedSingleton () { } public static synchronized SynchronizedSingleton getInstance () { if (instance == null ) { instance = new SynchronizedSingleton (); } return instance; } }
4. 双重检查锁(Double-Checked Locking) 结合懒汉式和同步的优点,减少同步带来的性能开销。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DoubleCheckedLockingSingleton { private static volatile DoubleCheckedLockingSingleton instance; private DoubleCheckedLockingSingleton () { } public static DoubleCheckedLockingSingleton getInstance () { if (instance == null ) { synchronized (DoubleCheckedLockingSingleton.class) { if (instance == null ) { instance = new DoubleCheckedLockingSingleton (); } } } return instance; } }
5. 静态内部类(Bill Pugh Singleton) 利用静态内部类特性,既实现了延迟加载,又保证了线程安全。
示例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 public class StaticInnerClassSingleton { private StaticInnerClassSingleton () { } private static class SingletonHelper { private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton (); } public static StaticInnerClassSingleton getInstance () { return SingletonHelper.INSTANCE; } }
6. 枚举(Enum Singleton) 枚举类型的单例模式实现最简单,并且天生是线程安全的,防止反序列化创建新的对象。
示例代码 1 2 3 4 5 6 7 public enum EnumSingleton { INSTANCE; public void doSomething () { } }
优点
唯一实例 :确保系统中只有一个实例,减少资源开销。
全局访问 :提供一个全局访问点,方便控制和管理实例。
延迟加载 :部分实现方式可以实现延迟加载(如懒汉式、静态内部类)。
缺点
单例类的职责过重 :可能导致“上帝对象”问题,使类变得臃肿。
隐藏依赖关系 :不利于依赖注入,可能会使代码难以测试。
多线程问题 :某些实现方式在多线程环境下需要注意线程安全问题。
使用场景
需要唯一实例 :如配置管理类、日志记录器、线程池、缓存等。
控制资源使用 :如需要频繁访问数据库或文件系统的场景,通过单例模式来控制资源访问。
单例模式是设计模式中最常见的一种,适用于需要全局唯一实例的场景。选择合适的实现方式,可以有效提高程序的性能和稳定性。
工厂模式 工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式,而无需向客户端暴露创建逻辑。工厂模式定义了一个用于创建对象的接口,但是让子类决定实例化哪个类。这样,工厂模式允许类将实例化延迟到子类。
主要组成部分
工厂接口(Factory) :声明了一个用于创建对象的工厂方法,可以是接口、抽象类或具体类。
具体工厂(Concrete Factory) :实现了工厂接口,负责实际创建对象的工厂类。
产品接口(Product) :定义了工厂创建的对象的接口,可以是接口或抽象类。
具体产品(Concrete Product) :实现了产品接口,是工厂创建的具体对象。
示例代码 产品接口 1 2 3 4 public interface Product { void doSomething () ; }
具体产品 - ProductA 1 2 3 4 5 6 7 public class ProductA implements Product { @Override public void doSomething () { System.out.println("ProductA is doing something." ); } }
具体产品 - ProductB 1 2 3 4 5 6 7 public class ProductB implements Product { @Override public void doSomething () { System.out.println("ProductB is doing something." ); } }
工厂接口 1 2 3 4 public interface Factory { Product createProduct () ; }
具体工厂 - FactoryA 1 2 3 4 5 6 7 public class FactoryA implements Factory { @Override public Product createProduct () { return new ProductA (); } }
具体工厂 - FactoryB 1 2 3 4 5 6 7 public class FactoryB implements Factory { @Override public Product createProduct () { return new ProductB (); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 public class FactoryPatternDemo { public static void main (String[] args) { Factory factoryA = new FactoryA (); Product productA = factoryA.createProduct(); productA.doSomething(); Factory factoryB = new FactoryB (); Product productB = factoryB.createProduct(); productB.doSomething(); } }
优点
封装性 :工厂模式将对象的创建过程封装在工厂类中,客户端无需了解具体的创建细节。
解耦性 :工厂模式将客户端代码与具体的产品类解耦,客户端只需要知道产品的接口即可。
可扩展性 :增加新的具体产品和具体工厂很容易,符合开闭原则。
缺点
增加了系统复杂度 :引入了多个工厂类和产品类,增加了系统的复杂度。
使用场景
对象的创建过程复杂,需要隐藏创建逻辑 :当对象的创建过程比较复杂,需要隐藏创建逻辑时,可以使用工厂模式。
需要根据不同的条件创建不同的对象 :当需要根据不同的条件创建不同的对象时,可以使用工厂模式。
工厂模式通过封装对象的创建过程,使得客户端无需了解具体的创建细节,从而实现了对象的创建和使用的分离。它适用于对象的创建过程比较复杂,或者需要根据不同的条件创建不同的对象的场景。
抽象工厂模式 抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的最佳方式,而无需指定它们的具体类。抽象工厂模式通过引入抽象工厂接口和抽象产品接口,实现了工厂方法模式的扩展,使得一族相关或相互依赖的对象可以被一起创建。
主要组成部分
抽象工厂接口(Abstract Factory) :声明了一组用于创建一族产品的抽象方法,每个方法对应一种产品。
具体工厂(Concrete Factory) :实现了抽象工厂接口,负责创建一族具体产品。
抽象产品接口(Abstract Product) :声明了一组产品的抽象方法,每个方法对应产品的一个属性或行为。
具体产品(Concrete Product) :实现了抽象产品接口,是抽象工厂创建的具体对象。
示例代码 抽象产品接口 1 2 3 4 public interface AbstractProductA { void doSomething () ; }
1 2 3 4 public interface AbstractProductB { void doSomething () ; }
具体产品 - ProductA1 1 2 3 4 5 6 7 public class ProductA1 implements AbstractProductA { @Override public void doSomething () { System.out.println("ProductA1 is doing something." ); } }
具体产品 - ProductA2 1 2 3 4 5 6 7 public class ProductA2 implements AbstractProductA { @Override public void doSomething () { System.out.println("ProductA2 is doing something." ); } }
具体产品 - ProductB1 1 2 3 4 5 6 7 public class ProductB1 implements AbstractProductB { @Override public void doSomething () { System.out.println("ProductB1 is doing something." ); } }
具体产品 - ProductB2 1 2 3 4 5 6 7 public class ProductB2 implements AbstractProductB { @Override public void doSomething () { System.out.println("ProductB2 is doing something." ); } }
抽象工厂接口 1 2 3 4 5 public interface AbstractFactory { AbstractProductA createProductA () ; AbstractProductB createProductB () ; }
具体工厂 - Factory1 1 2 3 4 5 6 7 8 9 10 11 12 public class Factory1 implements AbstractFactory { @Override public AbstractProductA createProductA () { return new ProductA1 (); } @Override public AbstractProductB createProductB () { return new ProductB1 (); } }
具体工厂 - Factory2 1 2 3 4 5 6 7 8 9 10 11 12 public class Factory2 implements AbstractFactory { @Override public AbstractProductA createProductA () { return new ProductA2 (); } @Override public AbstractProductB createProductB () { return new ProductB2 (); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class AbstractFactoryPatternDemo { public static void main (String[] args) { AbstractFactory factory1 = new Factory1 (); AbstractProductA productA1 = factory1.createProductA(); AbstractProductB productB1 = factory1.createProductB(); productA1.doSomething(); productB1.doSomething(); AbstractFactory factory2 = new Factory2 (); AbstractProductA productA2 = factory2.createProductA(); AbstractProductB productB2 = factory2.createProductB(); productA2.doSomething(); productB2.doSomething(); } }
优点
封装性 :抽象工厂模式将具体类的创建逻辑封装在具体工厂中,客户端无需了解具体创建细节。
解耦性 :抽象工厂模式将客户端与具体类解耦,客户端只需要知道产品的抽象接口即可。
产品族一致性 :抽象工厂模式可以确保一族产品之间的兼容性,因为同一个工厂创建的产品属于同一族。
缺点
扩展性差 :抽象工厂模式中增加新的产品族比较困难,因为需要修改抽象工厂接口和所有的具体工厂类。
使用场景
需要创建一系列相关或相互依赖的对象 :当需要创建一系列相关或相互依赖的对象时,可以使用抽象工厂模式。
系统中有多个产品族,但是每次只使用其中一个产品族 :当系统中有多个产品族,但是每次只使用其中一个产品族时,可以使用抽象工厂模式。
抽象工厂模式通过引入抽象工厂接口和抽象产品接口,实现了工厂方法模式的扩展,使得一族相关或相互依赖的对象可以被一起创建。它适用于需要创建一系列相关或相互依赖的对象的场景。
建造者模式 建造者模式(Builder Pattern)是一种创建型设计模式,它用于构建复杂对象的步骤逐步执行,并允许通过不同的配置创建不同类型的对象。建造者模式将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
主要组成部分
Builder(建造者) :定义创建产品各个部件的接口。
ConcreteBuilder(具体建造者) :实现Builder接口,构建并装配各个部件。
Product(产品) :具体要创建的复杂对象。
Director(指挥者) :负责安排已有的Builder对象的建造步骤。它知道如何使用Builder来构建产品。
示例代码 假设我们要建造一个复杂的电脑对象,包含CPU、内存和存储等部件。
产品类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Computer { private String CPU; private String RAM; private String storage; public void setCPU (String CPU) { this .CPU = CPU; } public void setRAM (String RAM) { this .RAM = RAM; } public void setStorage (String storage) { this .storage = storage; } @Override public String toString () { return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", storage=" + storage + "]" ; } }
建造者接口 1 2 3 4 5 6 7 public interface ComputerBuilder { void buildCPU () ; void buildRAM () ; void buildStorage () ; Computer getComputer () ; }
具体建造者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class GamingComputerBuilder implements ComputerBuilder { private Computer computer; public GamingComputerBuilder () { this .computer = new Computer (); } @Override public void buildCPU () { computer.setCPU("High-end CPU" ); } @Override public void buildRAM () { computer.setRAM("16GB RAM" ); } @Override public void buildStorage () { computer.setStorage("1TB SSD" ); } @Override public Computer getComputer () { return this .computer; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class OfficeComputerBuilder implements ComputerBuilder { private Computer computer; public OfficeComputerBuilder () { this .computer = new Computer (); } @Override public void buildCPU () { computer.setCPU("Mid-range CPU" ); } @Override public void buildRAM () { computer.setRAM("8GB RAM" ); } @Override public void buildStorage () { computer.setStorage("512GB SSD" ); } @Override public Computer getComputer () { return this .computer; } }
指挥者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Director { private ComputerBuilder builder; public Director (ComputerBuilder builder) { this .builder = builder; } public void constructComputer () { builder.buildCPU(); builder.buildRAM(); builder.buildStorage(); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Client { public static void main (String[] args) { ComputerBuilder gamingBuilder = new GamingComputerBuilder (); Director director = new Director (gamingBuilder); director.constructComputer(); Computer gamingComputer = gamingBuilder.getComputer(); System.out.println(gamingComputer); ComputerBuilder officeBuilder = new OfficeComputerBuilder (); director = new Director (officeBuilder); director.constructComputer(); Computer officeComputer = officeBuilder.getComputer(); System.out.println(officeComputer); } }
优点
分离复杂对象的创建过程和表示 :可以通过不同的建造者构造出不同的表示。
更好的控制细节 :建造者模式允许对构建过程进行更精细的控制。
易于扩展 :添加新的具体建造者无需修改现有代码,只需实现新的建造者类。
缺点
生成过程较为复杂 :如果构造步骤或者部件非常多,代码可能会变得复杂。
不适用于简单对象 :对于只需要简单构造的对象,引入建造者模式反而会增加代码的复杂性。
建造者模式非常适合那些构建步骤复杂或者部件繁多的对象,能很好地将构建过程与对象表示分离,提供灵活的对象创建机制。
原型模式 原型模式(Prototype Pattern)是一种创建型设计模式,通过复制现有对象来创建新对象,而不是通过实例化一个类来创建对象。这样可以简化对象的创建过程,特别是对于创建成本较高的对象。原型模式使用的是对象的克隆方法,通常需要实现一个克隆接口。
主要组成部分
Prototype(原型接口) :定义一个克隆自身的接口。
ConcretePrototype(具体原型类) :实现克隆方法,以创建自身的一个副本。
Client(客户端) :通过调用原型对象的克隆方法来创建新的对象。
示例代码 假设我们有一个需要大量复制的复杂对象,可以使用原型模式来创建这些对象。
原型接口 1 2 3 4 public interface Prototype extends Cloneable { Prototype clone () ; }
具体原型类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class ConcretePrototype implements Prototype { private String name; private int value; public ConcretePrototype (String name, int value) { this .name = name; this .value = value; } public void setName (String name) { this .name = name; } public void setValue (int value) { this .value = value; } @Override public Prototype clone () { try { return (Prototype) super .clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null ; } } @Override public String toString () { return "ConcretePrototype [name=" + name + ", value=" + value + "]" ; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Client { public static void main (String[] args) { ConcretePrototype prototype1 = new ConcretePrototype ("Prototype1" , 100 ); ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone(); prototype2.setName("Prototype2" ); prototype2.setValue(200 ); System.out.println(prototype1); System.out.println(prototype2); } }
优点
性能优化 :通过克隆对象创建新对象,可以避免复杂对象的重复创建,节省时间和资源。
简化对象创建 :可以简化创建复杂对象的过程,特别是那些包含多个配置或初始化步骤的对象。
动态调整 :可以在运行时动态调整克隆的对象,而不需要修改代码。
缺点
深拷贝与浅拷贝 :需要注意浅拷贝和深拷贝的问题,浅拷贝可能导致克隆对象与原对象共享相同的引用数据。
复杂性 :对于包含复杂引用关系的对象,深拷贝的实现可能会比较复杂。
克隆方法的实现 :有些对象可能不支持或者不适合克隆,克隆方法的实现需要谨慎。
适用场景
创建成本高的对象 :需要频繁创建的对象,其创建成本较高,可以通过克隆来减少开销。
对象初始化复杂 :对象的初始化过程复杂,可以通过克隆一个已经初始化好的对象来简化过程。
需要大量相似对象 :需要创建大量相似对象,且这些对象之间只有部分属性不同,可以通过原型模式快速创建。
原型模式通过克隆现有对象来创建新对象,适用于创建成本高、初始化复杂或需要大量相似对象的场景。使用原型模式可以显著提高对象创建的效率和灵活性。
结构型模式 适配器模式 适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类能够一起工作。适配器模式通过将一个类的接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。适配器模式有两种主要形式:类适配器模式和对象适配器模式。
主要组成部分
目标接口(Target) :客户端期望的接口。
需要适配的类(Adaptee) :现有的接口,需要进行适配的类。
适配器(Adapter) :将Adaptee的接口转换为Target接口的适配器类。
客户端(Client) :使用Target接口进行编程。
类适配器模式 类适配器模式通过多重继承(在Java中通过实现接口和继承类的方式)来适配接口。
示例代码 假设我们有一个旧的音频播放器,它只能播放MP3格式的音频文件,现在我们需要让它也能够播放MP4格式的音频文件。
目标接口 1 2 3 4 public interface MediaPlayer { void play (String audioType, String fileName) ; }
需要适配的类 1 2 3 4 5 6 7 8 9 10 11 public class Mp3Player implements MediaPlayer { @Override public void play (String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp3" )) { System.out.println("Playing mp3 file. Name: " + fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported" ); } } }
适配者类 1 2 3 4 5 6 public class Mp4Player { public void playMp4 (String fileName) { System.out.println("Playing mp4 file. Name: " + fileName); } }
适配器类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MediaAdapter implements MediaPlayer { Mp4Player mp4Player; public MediaAdapter (String audioType) { if (audioType.equalsIgnoreCase("mp4" )) { mp4Player = new Mp4Player (); } } @Override public void play (String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp4" )) { mp4Player.playMp4(fileName); } } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; @Override public void play (String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp3" )) { System.out.println("Playing mp3 file. Name: " + fileName); } else if (audioType.equalsIgnoreCase("mp4" )) { mediaAdapter = new MediaAdapter (audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported" ); } } public static void main (String[] args) { AudioPlayer audioPlayer = new AudioPlayer (); audioPlayer.play("mp3" , "song.mp3" ); audioPlayer.play("mp4" , "video.mp4" ); audioPlayer.play("vlc" , "movie.vlc" ); } }
对象适配器模式 对象适配器模式通过组合的方式,将现有的类的对象作为适配器的一个字段。
示例代码 对象适配器的实现与类适配器类似,但通过组合来实现。
适配器类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MediaAdapter implements MediaPlayer { Mp4Player mp4Player; public MediaAdapter (String audioType) { if (audioType.equalsIgnoreCase("mp4" )) { mp4Player = new Mp4Player (); } } @Override public void play (String audioType, String fileName) { if (audioType.equalsIgnoreCase("mp4" )) { mp4Player.playMp4(fileName); } } }
优点
复用现有类 :适配器模式可以复用现有的类,无需修改已有代码。
灵活性高 :适配器模式可以让两个不兼容的接口协同工作,提高系统的灵活性和可扩展性。
解耦合 :客户端无需了解适配器背后的实现细节,只需与目标接口交互。
缺点
额外的开销 :增加了一层间接性,可能会导致代码的复杂性增加。
过度使用 :如果系统中大量使用适配器模式,可能会导致代码结构混乱。
使用场景
使用第三方库 :当使用第三方库的接口与系统接口不兼容时,可以使用适配器模式。
旧系统改造 :在旧系统改造中,适配器模式可以让新旧系统一起工作。
接口不兼容 :当接口不兼容但需要协同工作时,可以使用适配器模式。
适配器模式通过引入一个适配器类,将原本不兼容的接口连接起来,使得系统更加灵活和可扩展。在实际开发中,适配器模式广泛应用于需要兼容多个接口的场景。
桥接模式 桥接模式(Bridge Pattern)是一种结构型设计模式,它通过将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式可以使系统在多个维度上扩展时不产生指数级的复杂性增长。
主要组成部分
抽象(Abstraction) :定义抽象的接口,并保存对实现部分的引用。
扩展抽象(Refined Abstraction) :扩展抽象接口,添加额外的操作。
实现(Implementor) :定义实现类的接口,接口不一定要与抽象部分完全一致。
具体实现(Concrete Implementor) :具体实现Implementor接口的方法。
示例代码 假设我们要设计一个图形绘制系统,可以绘制不同的形状(如圆形和方形),并且可以用不同的颜色(如红色和绿色)来绘制这些形状。
实现接口 1 2 3 4 public interface DrawAPI { void drawCircle (int radius, int x, int y) ; }
具体实现类 1 2 3 4 5 6 7 public class RedCircle implements DrawAPI { @Override public void drawCircle (int radius, int x, int y) { System.out.println("Drawing Circle[ color: red, radius: " + radius + ", x: " + x + ", y: " + y + "]" ); } }
1 2 3 4 5 6 7 public class GreenCircle implements DrawAPI { @Override public void drawCircle (int radius, int x, int y) { System.out.println("Drawing Circle[ color: green, radius: " + radius + ", x: " + x + ", y: " + y + "]" ); } }
抽象类 1 2 3 4 5 6 7 8 9 10 public abstract class Shape { protected DrawAPI drawAPI; protected Shape (DrawAPI drawAPI) { this .drawAPI = drawAPI; } public abstract void draw () ; }
扩展抽象类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Circle extends Shape { private int x, y, radius; public Circle (int x, int y, int radius, DrawAPI drawAPI) { super (drawAPI); this .x = x; this .y = y; this .radius = radius; } @Override public void draw () { drawAPI.drawCircle(radius, x, y); } }
客户端代码 1 2 3 4 5 6 7 8 9 public class BridgePatternDemo { public static void main (String[] args) { Shape redCircle = new Circle (100 , 100 , 10 , new RedCircle ()); Shape greenCircle = new Circle (100 , 100 , 10 , new GreenCircle ()); redCircle.draw(); greenCircle.draw(); } }
优点
分离抽象和实现 :桥接模式将抽象部分和实现部分分离,使它们可以独立变化。
提高系统可扩展性 :通过组合不同的抽象部分和实现部分,可以灵活地扩展系统功能。
减少类的数量 :避免了使用继承造成的类爆炸问题。
缺点
增加系统复杂性 :引入了额外的抽象层,可能会使系统变得更加复杂。
理解难度 :对开发人员来说,理解和实现桥接模式需要一定的时间和经验。
使用场景
需要多个维度扩展 :系统需要在多个维度上扩展,并且各个维度可以独立变化。
避免类爆炸 :通过桥接模式,可以避免由于多层继承带来的类爆炸问题。
希望实现细节对客户透明 :希望隐藏具体实现,只暴露抽象接口给客户。
桥接模式通过分离抽象部分和实现部分,使系统能够独立地扩展这两个部分。它特别适用于需要在多个维度上扩展系统的场景,能够有效避免类爆炸问题,提高系统的灵活性和可维护性。
组合模式 组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。也就是说,客户端可以一致地使用组合结构和单个对象,而无需关心它们是组合对象还是单个对象。
主要组成部分
组件(Component) :定义组合对象和叶子对象的共同接口。
叶子(Leaf) :表示组合的最基本对象,叶子节点没有子节点。
组合(Composite) :定义有子部件的那些对象。存储子部件,实现在组件接口中的子部件相关操作。
示例代码 假设我们要构建一个公司的层次结构,包括部门和员工。
组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import java.util.ArrayList;import java.util.List;public abstract class Employee { protected String name; protected String dept; protected int salary; public Employee (String name, String dept, int salary) { this .name = name; this .dept = dept; this .salary = salary; } public void add (Employee e) { throw new UnsupportedOperationException (); } public void remove (Employee e) { throw new UnsupportedOperationException (); } public List<Employee> getSubordinates () { return new ArrayList <>(); } public String toString () { return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary + " ]" ); } }
叶子 1 2 3 4 5 6 public class Developer extends Employee { public Developer (String name, String dept, int salary) { super (name, dept, salary); } }
1 2 3 4 5 6 public class Manager extends Employee { public Manager (String name, String dept, int salary) { super (name, dept, salary); } }
组合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.util.ArrayList;import java.util.List;public class CompositeEmployee extends Employee { private List<Employee> subordinates; public CompositeEmployee (String name, String dept, int salary) { super (name, dept, salary); subordinates = new ArrayList <>(); } @Override public void add (Employee e) { subordinates.add(e); } @Override public void remove (Employee e) { subordinates.remove(e); } @Override public List<Employee> getSubordinates () { return subordinates; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class CompositePatternDemo { public static void main (String[] args) { Employee CEO = new CompositeEmployee ("John" , "CEO" , 30000 ); Employee headSales = new CompositeEmployee ("Robert" , "Head Sales" , 20000 ); Employee headMarketing = new CompositeEmployee ("Michel" , "Head Marketing" , 20000 ); Employee clerk1 = new Developer ("Laura" , "Marketing" , 10000 ); Employee clerk2 = new Developer ("Bob" , "Marketing" , 10000 ); Employee salesExecutive1 = new Developer ("Richard" , "Sales" , 10000 ); Employee salesExecutive2 = new Developer ("Rob" , "Sales" , 10000 ); CEO.add(headSales); CEO.add(headMarketing); headSales.add(salesExecutive1); headSales.add(salesExecutive2); headMarketing.add(clerk1); headMarketing.add(clerk2); System.out.println(CEO); for (Employee headEmployee : CEO.getSubordinates()) { System.out.println(headEmployee); for (Employee employee : headEmployee.getSubordinates()) { System.out.println(employee); } } } }
优点
树形结构 :组合模式允许你创建一个树形结构来表示部分和整体层次结构。
一致性 :客户端可以一致地使用组合和单个对象,无需关心它们的具体类型。
易于扩展 :可以很容易地增加新的叶子和组合类,不影响现有代码。
缺点
复杂性增加 :对于简单的对象结构,使用组合模式会增加不必要的复杂性。
类型安全性 :由于组合模式使用了通用的组件接口,可能会导致类型安全问题,特别是在需要特定类型操作的时候。
使用场景
部分-整体层次结构 :需要表示对象的部分-整体层次结构,例如文件系统、组织结构、UI组件树等。
统一操作 :希望客户端可以一致地使用组合对象和单个对象,而无需关心它们的区别。
组合模式通过将对象组合成树形结构来表示部分和整体的层次结构,使得客户端可以一致地使用单个对象和组合对象。在实际开发中,组合模式广泛应用于各种层次结构的场景,如文件系统、GUI组件等。
装饰者模式 装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地给对象添加职责,而不会影响其他对象。装饰者模式提供了比继承更有弹性的替代方案,通过组合多个装饰者对象来增强功能。
主要组成部分
组件(Component) :定义一个对象接口,可以给这些对象动态地添加职责。
具体组件(Concrete Component) :实现组件接口的具体对象,职责可以动态地添加到这个对象。
装饰者(Decorator) :实现组件接口,内部持有一个组件对象,具体装饰者类可以通过调用组件对象的相应方法,并对其进行扩展。
具体装饰者(Concrete Decorator) :继承装饰者类,具体实现要添加的职责。
示例代码 假设我们有一个基本的饮料类,我们可以动态地给这个饮料添加不同的配料(如牛奶、糖等)。
组件接口 1 2 3 4 5 public interface Beverage { String getDescription () ; double cost () ; }
具体组件 1 2 3 4 5 6 7 8 9 10 11 12 public class Espresso implements Beverage { @Override public String getDescription () { return "Espresso" ; } @Override public double cost () { return 1.99 ; } }
抽象装饰者 1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; public CondimentDecorator (Beverage beverage) { this .beverage = beverage; } @Override public String getDescription () { return beverage.getDescription(); } }
具体装饰者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Milk extends CondimentDecorator { public Milk (Beverage beverage) { super (beverage); } @Override public String getDescription () { return beverage.getDescription() + ", Milk" ; } @Override public double cost () { return beverage.cost() + 0.30 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Sugar extends CondimentDecorator { public Sugar (Beverage beverage) { super (beverage); } @Override public String getDescription () { return beverage.getDescription() + ", Sugar" ; } @Override public double cost () { return beverage.cost() + 0.20 ; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 public class DecoratorPatternDemo { public static void main (String[] args) { Beverage beverage = new Espresso (); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Milk (beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Sugar (beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); } }
优点
灵活性 :可以动态地扩展对象的功能,比继承更灵活。
职责分配 :可以将不同的职责分配到不同的装饰者类中,符合单一职责原则。
避免类爆炸 :通过组合而不是继承,可以避免由于多层次继承导致的类爆炸问题。
缺点
复杂性增加 :由于增加了很多装饰者类,可能会使系统变得更加复杂。
调试困难 :因为功能是动态添加的,调试和排查问题可能会变得更加困难。
使用场景
需要动态扩展对象功能 :在不影响其他对象的情况下,动态地添加功能。
使用继承会导致类爆炸 :希望通过组合来替代多层次的继承,以避免类爆炸问题。
需要不同组合的功能 :希望通过不同的组合来实现不同的功能。
装饰者模式通过组合多个装饰者对象,能够在运行时动态地扩展对象的功能,而不影响其他对象。这使得系统具有更高的灵活性和可扩展性,适用于需要频繁扩展和变化的场景。
外观模式 外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口,用于访问子系统中的一组接口。外观模式定义了一个高层接口,使得客户端可以更加方便地使用子系统。
主要组成部分
外观(Facade) :提供了一个高层接口,封装了子系统的一组接口,简化了客户端与子系统之间的交互。
子系统(Subsystem) :实现了子系统的功能,但对客户端来说是不可见的。
示例代码 假设我们有一个电脑的启动过程,包括了启动CPU、内存、硬盘等多个子系统。
外观类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ComputerFacade { private CPU cpu; private Memory memory; private HardDrive hardDrive; public ComputerFacade () { this .cpu = new CPU (); this .memory = new Memory (); this .hardDrive = new HardDrive (); } public void start () { cpu.start(); memory.start(); hardDrive.start(); } }
子系统类 1 2 3 4 5 6 7 public class CPU { public void start () { System.out.println("CPU is starting..." ); } }
1 2 3 4 5 6 7 public class HardDrive { public void start () { System.out.println("Hard Drive is starting..." ); } }
1 2 3 4 5 6 7 public class Memory { public void start () { System.out.println("Memory is starting..." ); } }
客户端代码 1 2 3 4 5 6 7 8 public class FacadePatternDemo { public static void main (String[] args) { ComputerFacade computer = new ComputerFacade (); computer.start(); } }
优点
简化客户端 :外观模式隐藏了子系统的复杂性,为客户端提供了一个简化的接口。
解耦合 :客户端与子系统之间通过外观类进行通信,避免了直接依赖子系统的细节。
更好的封装性 :外观类对子系统进行了封装,使得子系统的变化对客户端是透明的。
缺点
不符合开闭原则 :外观类如果发生变化,可能会影响多个客户端。
过度封装 :如果外观类设计不合理,可能会导致过度封装,使得系统难以维护。
使用场景
简化复杂系统 :当系统中存在复杂的子系统,而客户端只需要使用部分功能时,可以考虑使用外观模式。
解耦合 :当希望将子系统和客户端之间的依赖关系解耦合时,可以使用外观模式。
提供简单接口 :当希望为客户端提供一个简单的接口,隐藏系统的复杂性时,可以使用外观模式。
外观模式通过提供一个统一的接口,封装了子系统的一组接口,简化了客户端与子系统之间的交互。这使得系统更易于维护和扩展,适用于需要隐藏复杂性、提供简单接口的场景。
享元模式 享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来有效地支持大量细粒度的对象。它适用于系统中存在大量相似对象,而这些对象可以有效地共享相同的状态或属性。
主要组成部分
享元接口(Flyweight) :定义了享元对象的接口,通过这个接口可以接受并作用于外部状态。
具体享元(Concrete Flyweight) :实现了享元接口,包含了内部状态和共享的外部状态。
享元工厂(Flyweight Factory) :负责创建和管理享元对象,通常实现了对象的池化,以便在需要时重复利用已经创建的对象。
示例代码 假设我们有一个简单的文本编辑器,需要大量的文本字符来组成文档。
享元接口 1 2 3 4 5 public interface TextCharacter { void display () ; }
具体享元类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ConcreteTextCharacter implements TextCharacter { private char character; public ConcreteTextCharacter (char character) { this .character = character; } @Override public void display () { System.out.print(character); } }
享元工厂类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.HashMap;import java.util.Map;public class TextCharacterFactory { private static Map<Character, TextCharacter> characterMap = new HashMap <>(); public static TextCharacter getTextCharacter (char character) { if (!characterMap.containsKey(character)) { characterMap.put(character, new ConcreteTextCharacter (character)); } return characterMap.get(character); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class FlyweightPatternDemo { public static void main (String[] args) { String document = "Hello World!" ; for (char c : document.toCharArray()) { TextCharacter textCharacter = TextCharacterFactory.getTextCharacter(c); textCharacter.display(); } } }
优点
减少内存消耗 :通过共享相同的状态或属性,减少了系统中对象的数量,降低了内存消耗。
提高性能 :共享对象可以在多个地方重复使用,提高了系统的性能。
减少对象创建 :通过享元工厂对对象进行管理,可以避免频繁创建和销毁对象,提高了系统的效率。
缺点
复杂性增加 :享元模式可能会引入额外的复杂性,包括共享状态的管理和同步问题。
可能会影响系统设计 :需要仔细权衡对象的共享和独立性,不当的使用可能会导致系统设计上的局限性。
使用场景
系统中存在大量相似对象 :如果系统中存在大量的相似对象,而这些对象可以共享相同的状态或属性,可以考虑使用享元模式。
需要缓存对象 :如果需要频繁地创建和销毁对象,可以考虑使用享元模式来缓存对象,提高系统的性能和效率。
享元模式适用于需要大量细粒度对象,并且这些对象可以共享相同状态或属性的场景。通过共享对象来减少内存消耗和提高性能,可以有效地优化系统的设计和性能。
代理模式 代理模式(Proxy Pattern)是一种结构型设计模式,它允许你提供一个替代品或者占位符,以控制对其它对象的访问。代理对象通常在客户端和目标对象之间起到中介作用,客户端通过代理间接访问目标对象,从而可以在访问前后执行额外的操作。
主要组成部分
抽象主题(Subject) :定义了目标对象和代理对象的共同接口,这样代理对象可以通过实现这个接口来替代目标对象。
真实主题(Real Subject) :定义了代理对象所代表的目标对象,是业务逻辑的具体执行者。
代理(Proxy) :保存了一个引用,使得代理可以访问目标对象,并提供与目标对象相同的接口,以便在需要时可以替代目标对象。
示例代码 假设我们有一个简单的图像查看器,需要加载大图像文件。为了避免每次都加载整个图像文件,我们可以使用代理模式来创建一个代理对象来加载图像。
抽象主题 1 2 3 4 5 public interface Image { void display () ; }
真实主题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class RealImage implements Image { private String filename; public RealImage (String filename) { this .filename = filename; loadFromDisk(); } @Override public void display () { System.out.println("Displaying " + filename); } private void loadFromDisk () { System.out.println("Loading " + filename); } }
代理类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ProxyImage implements Image { private String filename; private RealImage realImage; public ProxyImage (String filename) { this .filename = filename; } @Override public void display () { if (realImage == null ) { realImage = new RealImage (filename); } realImage.display(); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 public class ProxyPatternDemo { public static void main (String[] args) { Image image = new ProxyImage ("large_image.jpg" ); image.display(); System.out.println(); image.display(); } }
优点
代理对象与目标对象分离 :代理对象可以作为目标对象的代表,从而可以控制客户端对目标对象的访问。
代理对象可以扩展目标对象的功能 :代理对象可以在访问目标对象之前或之后执行额外的操作,如权限验证、缓存等。
远程代理 :可以通过代理对象在客户端和远程服务器之间进行通信,实现远程代理。
缺点
复杂性增加 :引入了额外的间接性,可能会增加系统的复杂性。
性能开销 :在访问目标对象时,需要经过代理对象,可能会带来一定的性能开销。
使用场景
远程代理 :需要在客户端和远程服务器之间进行通信时,可以使用远程代理。
虚拟代理 :需要延迟加载目标对象,或者在访问目标对象时执行额外的操作时,可以使用虚拟代理。
安全代理 :需要控制对目标对象的访问权限时,可以使用安全代理。
缓存代理 :需要缓存目标对象的结果,以提高访问速度时,可以使用缓存代理。
代理模式通过引入代理对象,控制客户端对目标对象的访问,并在访问前后执行额外的操作,从而可以更加灵活地管理和控制系统的行为。在实际开发中,代理模式广泛应用于需要对目标对象进行控制和管理的场景。
行为型模式 责任链模式 责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者处理该请求为止。责任链模式将请求发送者和接收者解耦,使得多个对象都有机会处理该请求,而不需要显式指定接收者。
主要组成部分
处理者(Handler) :定义了处理请求的接口,并维护了一个后继处理者的引用。
具体处理者(Concrete Handler) :实现了处理请求的具体逻辑,并可以决定是否将请求传递给下一个处理者。
客户端(Client) :创建请求,并将其发送给责任链的第一个处理者。
示例代码 假设我们有一个简单的报销审批系统,需要根据员工的职位级别来决定是否可以批准报销请求。高级别的员工可以审批更高额度的报销请求。
处理者接口 1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class Approver { protected Approver successor; public void setSuccessor (Approver successor) { this .successor = successor; } public abstract void processRequest (Request request) ; }
具体处理者类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Manager extends Approver { @Override public void processRequest (Request request) { if (request.getAmount() <= 1000 ) { System.out.println("Manager approves the request with amount: " + request.getAmount()); } else if (successor != null ) { successor.processRequest(request); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Director extends Approver { @Override public void processRequest (Request request) { if (request.getAmount() <= 5000 ) { System.out.println("Director approves the request with amount: " + request.getAmount()); } else if (successor != null ) { successor.processRequest(request); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class CEO extends Approver { @Override public void processRequest (Request request) { if (request.getAmount() <= 10000 ) { System.out.println("CEO approves the request with amount: " + request.getAmount()); } else { System.out.println("Request exceeds CEO's authority." ); } } }
请求类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Request { private double amount; public Request (double amount) { this .amount = amount; } public double getAmount () { return amount; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ChainOfResponsibilityPatternDemo { public static void main (String[] args) { Approver manager = new Manager (); Approver director = new Director (); Approver ceo = new CEO (); manager.setSuccessor(director); director.setSuccessor(ceo); Request request1 = new Request (500 ); manager.processRequest(request1); Request request2 = new Request (5000 ); manager.processRequest(request2); Request request3 = new Request (15000 ); manager.processRequest(request3); } }
优点
降低耦合度 :请求发送者和接收者解耦,可以灵活地组织处理链。
可扩展性 :可以很容易地添加新的处理者,并将它们插入到处理链中。
灵活性 :请求可以沿着处理链传递,直到有一个处理者处理为止。
缺点
性能问题 :请求可能需要在整个处理链中传递,如果处理链过长或者过于复杂,可能会影响系统的性能。
请求未被处理的风险 :如果处理链中没有处理请求的处理者,可能会导致请求未被处理。
使用场景
多级审批系统 :适用于需要多级审批的场景,例如报销审批系统、请假审批系统等。
事件处理 :适用于需要根据不同条件触发不同处理逻辑的场景,例如事件处理、异常处理等。
责任链模式通过将请求发送者和接收者解耦,使得多个对象都有机会处理请求,从而提高了系统的灵活性和可扩展性。它适用于多级审批、事件处理等场景,可以使得系统的设计更加灵活和可维护。
命令模式 命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而允许你将请求参数化、将请求排队、记录请求日志,以及支持可撤销的操作。命令模式将请求的发送者和接收者解耦,使得请求发送者无需知道请求的具体接收者,只需知道如何发送请求。
主要组成部分
命令(Command) :声明了执行操作的接口,通常包含执行(execute)方法。
具体命令(Concrete Command) :实现了命令接口,并封装了命令的接收者,负责调用接收者的相应方法。
接收者(Receiver) :知道如何实施与执行一个请求相关的操作。
调用者/请求者(Invoker) :负责调用命令对象执行请求的对象。
客户端(Client) :创建具体命令对象并设置其接收者。
示例代码 假设我们有一个简单的遥控器,可以控制电灯的开关。
命令接口 1 2 3 4 5 public interface Command { void execute () ; }
具体命令类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TurnOnLightCommand implements Command { private Light light; public TurnOnLightCommand (Light light) { this .light = light; } @Override public void execute () { light.turnOn(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TurnOffLightCommand implements Command { private Light light; public TurnOffLightCommand (Light light) { this .light = light; } @Override public void execute () { light.turnOff(); } }
接收者类 1 2 3 4 5 6 7 8 9 10 11 12 public class Light { public void turnOn () { System.out.println("Light is on" ); } public void turnOff () { System.out.println("Light is off" ); } }
请求者类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class RemoteControl { private Command command; public void setCommand (Command command) { this .command = command; } public void pressButton () { command.execute(); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CommandPatternDemo { public static void main (String[] args) { Light light = new Light (); Command turnOnCommand = new TurnOnLightCommand (light); Command turnOffCommand = new TurnOffLightCommand (light); RemoteControl remoteControl = new RemoteControl (); remoteControl.setCommand(turnOnCommand); remoteControl.pressButton(); remoteControl.setCommand(turnOffCommand); remoteControl.pressButton(); } }
优点
松耦合 :命令模式将请求发送者和接收者解耦,使得系统更加灵活。
可扩展性 :可以很容易地添加新的命令和接收者,而不需要修改现有的代码。
支持撤销和恢复 :命令对象可以记录操作历史,支持撤销和恢复操作。
缺点
类过多 :可能会创建大量的命令类和接收者类,增加了系统的复杂性。
命令调用可能无法保证完全执行 :如果命令的接收者是一个远程对象或者线程池中的对象,可能会导致命令的执行结果与期望不符。
使用场景
需要将请求发送者和接收者解耦 :命令模式可以将请求发送者和接收者解耦,使得系统更加灵活。
需要支持撤销和恢复操作 :命令模式可以记录操作历史,支持撤销和恢复操作,适用于需要此功能的场景。
命令模式通过将请求封装成一个对象,实现了请求的发送者和接收者之间的解耦,使得系统更加灵活和可扩展。它适用于需要将请求发送者和接收者解耦、支持撤销和恢复操作的场景。
解释器模式 解释器模式(Interpreter Pattern)是一种行为型设计模式,它定义了一种语言的文法,并且建立一个解释器来解释该语言中的句子。通过使用解释器模式,可以将复杂的问题表达为简单的语言,然后利用解释器来解释这个语言,从而可以轻松地改变或扩展这个语言。
主要组成部分
抽象表达式(Abstract Expression) :声明一个抽象的解释操作,通常是一个抽象类或接口。
终结符表达式(Terminal Expression) :实现了抽象表达式的解释操作,通常是文法中的基本单元。
非终结符表达式(Non-terminal Expression) :实现了抽象表达式的解释操作,并且通过递归调用来解释语言中的复杂语句。
环境类(Context) :包含解释器解释的全局信息。
示例代码 假设我们有一个简单的算术表达式解释器,可以解释加法和乘法运算。
抽象表达式 1 2 3 4 5 public interface Expression { int interpret (Context context) ; }
终结符表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class VariableExpression implements Expression { private String variable; public VariableExpression (String variable) { this .variable = variable; } @Override public int interpret (Context context) { return context.getValue(variable); } }
非终结符表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class AddExpression implements Expression { private Expression leftExpression; private Expression rightExpression; public AddExpression (Expression leftExpression, Expression rightExpression) { this .leftExpression = leftExpression; this .rightExpression = rightExpression; } @Override public int interpret (Context context) { return leftExpression.interpret(context) + rightExpression.interpret(context); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MultiplyExpression implements Expression { private Expression leftExpression; private Expression rightExpression; public MultiplyExpression (Expression leftExpression, Expression rightExpression) { this .leftExpression = leftExpression; this .rightExpression = rightExpression; } @Override public int interpret (Context context) { return leftExpression.interpret(context) * rightExpression.interpret(context); } }
环境类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.util.HashMap;import java.util.Map;public class Context { private Map<String, Integer> variables = new HashMap <>(); public void setVariable (String variable, int value) { variables.put(variable, value); } public int getValue (String variable) { return variables.get(variable); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class InterpreterPatternDemo { public static void main (String[] args) { Context context = new Context (); context.setVariable("x" , 5 ); context.setVariable("y" , 10 ); Expression expression = new AddExpression ( new VariableExpression ("x" ), new MultiplyExpression ( new VariableExpression ("y" ), new VariableExpression ("x" ) ) ); int result = expression.interpret(context); System.out.println("Result: " + result); } }
优点
扩展性 :可以很容易地扩展语法,增加新的解释器实现新的语法规则。
易于实现 :每个文法规则都可以实现为一个解释器,简单易懂。
缺点
复杂度 :对于复杂的文法规则,可能需要大量的解释器类来实现,增加了系统的复杂性。
效率问题 :由于解释器模式是递归的,可能会导致一些效率问题,尤其是处理复杂语法时。
使用场景
DSL(Domain Specific Language)解析 :解释器模式适用于需要解析和执行DSL的场景。
编译器 :解释器模式可以用于构建简单的编译器。
规则引擎 :解释器模式可以用于构建规则引擎,用于解析和执行规则。
解释器模式通过将语言的文法定义为一组解释器,然后使用解释器来解释这个语言,使得系统更加灵活和可扩展。它适用于需要解析和执行自定义语言或规则的场景。
迭代器模式 迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。迭代器模式将遍历操作从聚合对象中分离出来,使得聚合对象和遍历操作可以独立变化。
主要组成部分
迭代器接口(Iterator) :定义了访问和遍历聚合对象中元素的接口。
具体迭代器(Concrete Iterator) :实现了迭代器接口,并跟踪当前位置在聚合对象中的位置。
聚合接口(Aggregate) :定义了创建相应迭代器对象的接口。
具体聚合(Concrete Aggregate) :实现了聚合接口,返回相应的迭代器对象。
示例代码 假设我们有一个简单的集合类 MyList
,需要实现迭代器来遍历集合中的元素。
迭代器接口 1 2 3 4 5 6 7 public interface Iterator <T> { boolean hasNext () ; T next () ; }
具体迭代器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import java.util.NoSuchElementException;public class MyListIterator <T> implements Iterator <T> { private MyList<T> list; private int index = 0 ; public MyListIterator (MyList<T> list) { this .list = list; } @Override public boolean hasNext () { return index < list.size(); } @Override public T next () { if (!hasNext()) { throw new NoSuchElementException (); } return list.get(index++); } }
聚合接口 1 2 3 4 5 6 7 8 9 10 11 public interface MyList <T> { void add (T element) ; T get (int index) ; int size () ; Iterator<T> iterator () ; }
具体聚合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.util.ArrayList;import java.util.List;public class MyArrayList <T> implements MyList <T> { private List<T> elements = new ArrayList <>(); @Override public void add (T element) { elements.add(element); } @Override public T get (int index) { return elements.get(index); } @Override public int size () { return elements.size(); } @Override public Iterator<T> iterator () { return new MyListIterator <>(this ); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class IteratorPatternDemo { public static void main (String[] args) { MyList<Integer> list = new MyArrayList <>(); list.add(1 ); list.add(2 ); list.add(3 ); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
优点
分离聚合对象和迭代器 :迭代器模式将遍历操作从聚合对象中分离出来,使得聚合对象和遍历操作可以独立变化。
简化聚合对象的接口 :由于遍历操作被移到迭代器中,聚合对象的接口变得更简单,只需要实现迭代器的获取方法即可。
缺点
迭代器遍历方式固定 :具体迭代器只能实现一种遍历方式,无法灵活切换。
使用场景
需要遍历聚合对象的场景 :当需要遍历一个聚合对象中的元素,并且不暴露其内部表示时,可以使用迭代器模式。
需要提供多种遍历方式 :当需要提供多种不同的遍历方式时,可以为每种遍历方式创建一个具体迭代器。
迭代器模式通过将遍历操作从聚合对象中分离出来,使得聚合对象和遍历操作可以独立变化,提高了系统的灵活性和可扩展性。它适用于需要遍历聚合对象的场景,且希望提供多种遍历方式的场景。
中介者模式 中介者模式(Mediator Pattern)是一种行为型设计模式,它允许对象之间通过一个中介者对象进行通信,而不是直接相互引用。中介者模式将系统中的交互关系集中管理,减少了对象之间的直接耦合,使得系统更加灵活可扩展。
主要组成部分
中介者(Mediator) :定义了一个接口用于与各同事对象通信,并封装了各同事对象之间的交互关系。
具体中介者(Concrete Mediator) :实现了中介者接口,并负责协调各同事对象之间的交互关系。
同事(Colleague) :定义了一个接口用于与中介者进行通信,并将自己的状态通知中介者。
具体同事(Concrete Colleague) :实现了同事接口,并与其他同事对象通过中介者进行通信。
示例代码 假设我们有一个简单的聊天室应用,需要实现用户之间的消息交流,但是用户之间不直接通信,而是通过一个聊天室中介者进行通信。
中介者接口 1 2 3 4 5 6 7 public interface ChatMediator { void sendMessage (String message, User user) ; void addUser (User user) ; }
具体中介者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.ArrayList;import java.util.List;public class ChatRoom implements ChatMediator { private List<User> users = new ArrayList <>(); @Override public void sendMessage (String message, User user) { for (User u : users) { if (u != user) { u.receiveMessage(message); } } } @Override public void addUser (User user) { users.add(user); } }
同事接口 1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class User { protected ChatMediator mediator; protected String name; public User (ChatMediator mediator, String name) { this .mediator = mediator; this .name = name; } public abstract void sendMessage (String message) ; public abstract void receiveMessage (String message) ; }
具体同事 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class BasicUser extends User { public BasicUser (ChatMediator mediator, String name) { super (mediator, name); } @Override public void sendMessage (String message) { System.out.println(name + " sends message: " + message); mediator.sendMessage(message, this ); } @Override public void receiveMessage (String message) { System.out.println(name + " receives message: " + message); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MediatorPatternDemo { public static void main (String[] args) { ChatMediator chatRoom = new ChatRoom (); User user1 = new BasicUser (chatRoom, "User1" ); User user2 = new BasicUser (chatRoom, "User2" ); User user3 = new BasicUser (chatRoom, "User3" ); chatRoom.addUser(user1); chatRoom.addUser(user2); chatRoom.addUser(user3); user1.sendMessage("Hello, everyone!" ); user2.sendMessage("Hi, User1!" ); } }
优点
减少耦合 :中介者模式将对象之间的交互关系集中管理,减少了对象之间的直接耦合。
简化对象 :由于对象之间的交互逻辑被封装在中介者中,对象可以更专注于自己的业务逻辑,代码更加简洁清晰。
缺点
中介者过于庞大 :中介者模式可能会导致中介者对象变得过于庞大,处理过多的逻辑,使得代码难以维护。
使用场景
多个对象之间存在复杂的交互关系 :当系统中多个对象之间存在复杂的交互关系时,可以使用中介者模式来简化对象之间的交互逻辑。
减少子类生成 :当对象之间存在很多的交互关系,并且需要通过子类来实现不同的交互逻辑时,可以使用中介者模式来减少子类的生成。
中介者模式通过引入一个中介者对象来封装对象之间的交互关系,使得对象之间不直接相互引用,减少了对象之间的耦合,提高了系统的灵活性和可维护性。它适用于多个对象之间存在复杂的交互关系,并且需要减少对象之间的直接耦合的场景。
备忘录模式 备忘录模式(Memento Pattern)是一种行为型设计模式,它允许你捕获一个对象的内部状态,并在该对象之外保存这个状态,以便在以后的时候将该对象恢复到原先保存的状态。备忘录模式主要用于在不破坏封装性的前提下,捕获对象的内部状态并在外部保存、恢复这些状态。
主要组成部分
备忘录(Memento) :负责存储原始对象的内部状态。备忘录可以保护其状态免受直接访问,只允许发起者(Originator)访问备忘录的内部状态。
发起者(Originator) :负责创建备忘录,并记录其当前内部状态。可以使用备忘录保存和恢复自己的状态。
管理者(Caretaker) :负责保存备忘录。管理者不应该修改或检查备忘录的内容,只负责存储备忘录。
示例代码 假设我们有一个文本编辑器,用户可以对文本进行编辑,我们希望能够保存历史版本,并在需要时进行恢复。
备忘录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class EditorMemento { private final String content; public EditorMemento (String content) { this .content = content; } public String getContent () { return content; } }
发起者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class TextEditor { private String content; public String getContent () { return content; } public void setContent (String content) { this .content = content; } public EditorMemento save () { return new EditorMemento (content); } public void restore (EditorMemento memento) { content = memento.getContent(); } }
管理者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.util.ArrayList;import java.util.List;public class History { private final List<EditorMemento> mementos = new ArrayList <>(); public void push (EditorMemento memento) { mementos.add(memento); } public EditorMemento pop () { if (!mementos.isEmpty()) { return mementos.remove(mementos.size() - 1 ); } return null ; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MementoPatternDemo { public static void main (String[] args) { TextEditor editor = new TextEditor (); History history = new History (); editor.setContent("Version 1.0" ); history.push(editor.save()); editor.setContent("Version 2.0" ); history.push(editor.save()); editor.setContent("Version 3.0" ); history.push(editor.save()); editor.restore(history.pop()); System.out.println(editor.getContent()); editor.restore(history.pop()); System.out.println(editor.getContent()); } }
优点
封装性 :备忘录模式将备忘录的存储和恢复逻辑封装在发起者和管理者中,使得原始对象的状态对客户端透明。
可扩展性 :可以通过扩展备忘录类和发起者类来支持更多的备忘录功能。
缺点
资源消耗 :如果备忘录对象过多或过大,可能会消耗大量内存和处理时间。
使用场景
需要保存对象状态的场景 :当需要保存对象的内部状态,并在需要时能够恢复到原先的状态时,可以使用备忘录模式。
需要实现“撤销”功能的场景 :当需要实现撤销功能时,可以使用备忘录模式来保存对象的历史状态。
备忘录模式通过将对象的状态保存在备忘录中,并通过发起者和管理者来管理备忘录,实现了对象状态的保存和恢复,提高了系统的灵活性和可维护性。它适用于需要保存对象状态或实现“
观察者模式 观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。
主要组成部分
主题(Subject) :也称为被观察者或发布者,维护了一组观察者对象,并提供了添加、删除和通知观察者的方法。
观察者(Observer) :也称为订阅者或观察者,定义了一个更新接口,用于接收并响应主题状态的变化。
具体主题(Concrete Subject) :实现了主题接口,负责维护一组观察者,并在状态发生变化时通知观察者。
具体观察者(Concrete Observer) :实现了观察者接口,负责接收主题的通知,并更新自身状态。
示例代码 假设我们有一个简单的气象站应用,需要将气象数据实时展示给观察者。
主题接口 1 2 3 4 5 6 7 8 9 10 11 public interface Subject { void addObserver (Observer observer) ; void removeObserver (Observer observer) ; void notifyObservers () ; }
观察者接口 1 2 3 4 5 public interface Observer { void update (float temperature, float humidity, float pressure) ; }
具体主题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import java.util.ArrayList;import java.util.List;public class WeatherData implements Subject { private List<Observer> observers = new ArrayList <>(); private float temperature; private float humidity; private float pressure; @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(temperature, humidity, pressure); } } public void measurementsChanged () { notifyObservers(); } public void setMeasurements (float temperature, float humidity, float pressure) { this .temperature = temperature; this .humidity = humidity; this .pressure = pressure; measurementsChanged(); } }
具体观察者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Display implements Observer { private float temperature; private float humidity; private float pressure; @Override public void update (float temperature, float humidity, float pressure) { this .temperature = temperature; this .humidity = humidity; this .pressure = pressure; display(); } public void display () { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity" ); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ObserverPatternDemo { public static void main (String[] args) { WeatherData weatherData = new WeatherData (); Display display = new Display (); weatherData.addObserver(display); weatherData.setMeasurements(80 , 65 , 30.4f ); } }
优点
松耦合 :主题和观察者之间是松耦合的,使得它们可以独立地进行扩展和修改。
可重用性 :可以轻松地添加新的观察者和主题,以适应系统的变化和扩展。
通知机制 :主题对象会通知所有观察者,使得观察者可以自动更新自己的状态。
缺点
可能导致性能问题 :如果观察者数量过多或者观察者的更新操作过于复杂,可能会影响主题对象的性能。
使用场景
多个对象之间存在一对多的依赖关系 :当一个对象的状态发生变化需要通知多个对象时,可以使用观察者模式。
需要实现一种对象与多个对象之间的松耦合关系 :当一个对象与多个对象之间需要保持松耦合关系,以便实现对象之间的协作时,可以使用观察者模式。
观察者模式通过定义一种一对多的依赖关系,使得一个主题对象状态的改变可以自动通知多个观察者对象,从而实现对象之间的协作。它适用于多个对象之间存在一对多的依赖关系的场景,例如GUI界面中的事件处理、消息订阅发布系统等。
状态模式 状态模式(State Pattern)是一种行为型设计模式,它允许对象在其内部状态改变时改变它的行为。状态模式通过将对象的状态封装成独立的状态类,使得对象在不同的状态下具有不同的行为,并且可以在运行时动态地改变状态。
主要组成部分
环境(Context) :定义了客户端感兴趣的接口,并且维护一个当前状态对象的引用。
状态(State) :定义了一个接口或抽象类,用于封装环境对象中特定状态相关的行为。
具体状态(Concrete State) :实现了状态接口或抽象类,负责实现该状态下的具体行为。
示例代码 假设我们有一个简单的电梯系统,需要实现电梯在不同状态下的行为。
环境接口 1 2 3 4 5 6 7 public interface ElevatorState { void open () ; void close () ; void move () ; void stop () ; }
具体状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class OpenState implements ElevatorState { @Override public void open () { System.out.println("The door is already open." ); } @Override public void close () { System.out.println("Close the door." ); } @Override public void move () { System.out.println("Cannot move when the door is open." ); } @Override public void stop () { System.out.println("Stop the elevator." ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class CloseState implements ElevatorState { @Override public void open () { System.out.println("Open the door." ); } @Override public void close () { System.out.println("The door is already closed." ); } @Override public void move () { System.out.println("Move the elevator." ); } @Override public void stop () { System.out.println("Stop the elevator." ); } }
环境类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Elevator { private ElevatorState currentState; public void setState (ElevatorState state) { this .currentState = state; } public void open () { currentState.open(); } public void close () { currentState.close(); } public void move () { currentState.move(); } public void stop () { currentState.stop(); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 public class StatePatternDemo { public static void main (String[] args) { Elevator elevator = new Elevator (); elevator.setState(new CloseState ()); elevator.open(); elevator.close(); elevator.move(); elevator.open(); } }
优点
封装性 :状态模式将状态封装在独立的状态类中,使得状态具有较好的封装性,客户端无需了解状态的具体实现。
扩展性 :可以很容易地增加新的状态类,而无需修改原有代码,符合开闭原则。
减少条件分支 :状态模式将状态的判断转移到了状态类中,减少了大量的条件分支判断。
缺点
可能引入过多的状态类 :如果状态过多或者状态类过于复杂,可能会引入过多的状态类,导致系统变得复杂。
使用场景
对象的行为取决于其状态,并且状态可能在运行时发生改变 :当对象的行为取决于其状态,并且状态可能在运行时发生改变时,可以使用状态模式。
避免使用大量的条件分支 :当对象的行为存在大量的条件分支,并且这些条件分支随着状态的改变而改变时,可以使用状态模式。
状态模式通过将对象的状态封装成独立的状态类,并在环境对象中维护一个当前状态对象的引用,使得对象在不同的状态下具有不同的行为,并且可以在运行时动态地改变状态。它适用于对象的行为取决于其状态,并且状态可能在运行时发生改变的场景。
策略模式 策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装成单独的类,使得它们可以互相替换,而且客户端调用它们的方式保持一致。
主要组成部分
策略接口(Strategy) :定义了算法族的通用接口,可以是抽象类或接口。
具体策略(Concrete Strategy) :实现了策略接口,包含了具体的算法。
环境类(Context) :持有一个策略对象的引用,并在需要时调用策略对象的方法执行算法。
示例代码 假设我们有一个电商平台,针对不同的支付方式需要实现不同的支付策略。
策略接口 1 2 3 4 public interface PaymentStrategy { void pay (double amount) ; }
具体策略 1 2 3 4 5 6 7 public class AlipayStrategy implements PaymentStrategy { @Override public void pay (double amount) { System.out.println("Paid " + amount + " yuan via Alipay." ); } }
1 2 3 4 5 6 7 public class WechatPayStrategy implements PaymentStrategy { @Override public void pay (double amount) { System.out.println("Paid " + amount + " yuan via WeChat Pay." ); } }
环境类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class PaymentContext { private PaymentStrategy paymentStrategy; public PaymentContext (PaymentStrategy paymentStrategy) { this .paymentStrategy = paymentStrategy; } public void setPaymentStrategy (PaymentStrategy paymentStrategy) { this .paymentStrategy = paymentStrategy; } public void executePayment (double amount) { paymentStrategy.pay(amount); } }
客户端代码 1 2 3 4 5 6 7 8 9 public class StrategyPatternDemo { public static void main (String[] args) { PaymentContext context = new PaymentContext (new AlipayStrategy ()); context.executePayment(100 ); context.setPaymentStrategy(new WechatPayStrategy ()); context.executePayment(50 ); } }
优点
灵活性 :策略模式允许在运行时动态地切换算法,客户端可以根据需要选择不同的算法。
可扩展性 :可以很容易地添加新的策略类,而无需修改原有代码,符合开闭原则。
简化代码 :策略模式避免了大量的条件分支判断,使得代码更加清晰简洁。
缺点
客户端必须了解所有的策略类 :客户端必须了解所有的策略类,并且在选择具体策略时需要进行判断,增加了客户端的复杂度。
使用场景
需要在不同场景下使用不同算法的场景 :当一个系统需要动态地在几种算法中选择一种执行时,可以使用策略模式。
需要封装算法的场景 :当系统有多种类似的算法,可以使用策略模式封装这些算法,使得它们可以互相替换。
策略模式通过将算法封装成独立的策略类,并在环境类中持有一个策略对象的引用,使得客户端调用策略对象的方式保持一致,同时可以动态地切换算法。它适用于需要在不同场景下使用不同算法或封装算法的场景。
模板方法模式 模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
主要组成部分
模板类(Abstract Class) :定义了一个算法的框架,其中包含了算法的主要逻辑以及一系列抽象方法或具体方法,这些方法可以是固定的、可选的或空的。
具体子类(Concrete Class) :实现了模板类中的抽象方法,完成了算法中特定步骤的具体实现。
示例代码 假设我们有一个制作咖啡和茶的流程,流程中有一些共同的步骤,但是某些步骤在制作咖啡和茶时可能不同。
模板类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public abstract class Beverage { public final void prepareBeverage () { boilWater(); brew(); pourInCup(); if (addCondiments()) { addCondiments(); } } protected abstract void brew () ; private void boilWater () { System.out.println("Boiling water" ); } private void pourInCup () { System.out.println("Pouring into cup" ); } protected boolean addCondiments () { return true ; } }
具体子类 - 制作咖啡 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Coffee extends Beverage { @Override protected void brew () { System.out.println("Dripping coffee through filter" ); } @Override protected boolean addCondiments () { System.out.println("Adding sugar and milk" ); return false ; } }
具体子类 - 制作茶 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Tea extends Beverage { @Override protected void brew () { System.out.println("Steeping the tea" ); } @Override protected boolean addCondiments () { System.out.println("Adding lemon" ); return true ; } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 public class TemplateMethodPatternDemo { public static void main (String[] args) { Beverage coffee = new Coffee (); System.out.println("Making coffee..." ); coffee.prepareBeverage(); System.out.println(); Beverage tea = new Tea (); System.out.println("Making tea..." ); tea.prepareBeverage(); } }
优点
复用性 :模板方法模式将算法的骨架封装在模板类中,可以在不改变算法结构的情况下,复用算法中的公共部分。
扩展性 :模板方法模式允许子类重新定义算法的某些步骤,从而实现算法的动态扩展。
可维护性 :模板方法模式将算法的主要逻辑和细节部分分离,使得系统更容易维护和扩展。
缺点
逻辑复杂性 :模板方法模式可能会导致子类的数量增加,从而使得系统变得更加复杂。
使用场景
多个子类有共同的行为 :当多个子类有共同的行为,并且这些行为可以被封装成一个公共方法时,可以使用模板方法模式。
不同的子类需要实现相似的算法 :当不同的子类需要实现相似的算法,并且这些算法的框架已经确定时,可以使用模板方法模式。
模板方法模式通过定义一个算法的骨架,将一些步骤延迟到子类中实现,从而实现了算法的复用和扩展。它适用于多个子类有共同的行为或不同的子类需要实现相似的算法的场景。
在示例代码中,Beverage
类定义了制作饮料的算法骨架,其中包含了煮水、冲泡、倒入杯中等步骤,并且将添加调料的步骤设计为钩子方法,子类可以选择是否实现该步骤。具体的饮料类如 Coffee
和 Tea
分别实现了不同的冲泡和添加调料的步骤。
使用模板方法模式,可以轻松实现不同饮料的制作过程,并且在需要修改某一步骤时,只需要在相应的子类中进行修改,而不影响整体的算法结构。
访问者模式 访问者模式(Visitor Pattern)是一种行为型设计模式,它可以在不改变已有类的情况下,定义对这些类新的操作。该模式将数据结构与数据操作分离开来,使得数据结构的增加或修改不会影响到数据操作的变化。
主要组成部分
访问者接口(Visitor) :定义了对于每个元素类所提供的访问操作方法。在访问者模式中,这个接口是最重要的部分,每个具体的访问者都要实现这个接口。
具体访问者(Concrete Visitor) :实现了访问者接口中定义的操作方法,提供了对于元素对象的具体访问行为。
元素接口(Element) :定义了一个 accept 方法,该方法需要传入一个访问者对象作为参数,用来接受访问者的访问。
具体元素(Concrete Element) :实现了元素接口,提供了 accept 方法的具体实现。
对象结构(Object Structure) :维护了一个元素的集合,并提供了遍历这些元素的方法,用于供访问者访问。
示例代码 假设我们有一个简单的员工管理系统,其中包含不同类型的员工,我们希望对这些员工进行不同类型的访问。
元素接口 1 2 3 4 public interface Element { void accept (Visitor visitor) ; }
具体元素 - 全职员工 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class FullTimeEmployee implements Element { private String name; private double salary; public FullTimeEmployee (String name, double salary) { this .name = name; this .salary = salary; } public String getName () { return name; } public double getSalary () { return salary; } @Override public void accept (Visitor visitor) { visitor.visit(this ); } }
具体元素 - 兼职员工 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class PartTimeEmployee implements Element { private String name; private double workingHours; public PartTimeEmployee (String name, double workingHours) { this .name = name; this .workingHours = workingHours; } public String getName () { return name; } public double getWorkingHours () { return workingHours; } @Override public void accept (Visitor visitor) { visitor.visit(this ); } }
访问者接口 1 2 3 4 5 public interface Visitor { void visit (FullTimeEmployee employee) ; void visit (PartTimeEmployee employee) ; }
具体访问者 - 薪资计算器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class SalaryCalculator implements Visitor { @Override public void visit (FullTimeEmployee employee) { double salary = employee.getSalary(); System.out.println("Full-time employee " + employee.getName() + "'s salary is " + salary); } @Override public void visit (PartTimeEmployee employee) { double salary = employee.getWorkingHours() * 100 ; System.out.println("Part-time employee " + employee.getName() + "'s salary is " + salary); } }
对象结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.ArrayList;import java.util.List;public class EmployeeManagementSystem { private List<Element> employees = new ArrayList <>(); public void attach (Element employee) { employees.add(employee); } public void detach (Element employee) { employees.remove(employee); } public void accept (Visitor visitor) { for (Element employee : employees) { employee.accept(visitor); } }
客户端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class VisitorPatternDemo { public static void main (String[] args) { EmployeeManagementSystem system = new EmployeeManagementSystem (); FullTimeEmployee fullTimeEmployee = new FullTimeEmployee ("Alice" , 5000 ); PartTimeEmployee partTimeEmployee = new PartTimeEmployee ("Bob" , 20 ); system.attach(fullTimeEmployee); system.attach(partTimeEmployee); SalaryCalculator calculator = new SalaryCalculator (); system.accept(calculator); } }
优点
灵活性 :访问者模式将数据结构与数据操作解耦,使得数据结构和操作可以独立变化,灵活性较高。
扩展性 :可以很方便地添加新的访问操作,而不需要修改现有的数据结构。
符合开闭原则 :符合开闭原则,对于已有的数据结构和访问操作都可以进行扩展,而不需要修改已有的代码。
缺点
增加新的元素类较困难 :如果需要增加新的元素类,需要修改所有的访问者类,不符合开闭原则。
使用场景
数据结构相对稳定,但经常需要在此基础上定义新的操作 :当数据结构相对稳定,但经常需要在此基础上定义新的操作时,可以考虑使用访问者模式。
数据结构中的操作需要依赖于数据结构的具体类 :当数据结构中的操作需要依赖于数据结构的具体类时,可以考虑使用访问者模式。
访问者模式通过将数据结构和数据操作分离开来,使得数据结构的增加或修改不会影响到数据操作的变化,同时可以很方便地添加新的访问操作。它适用于数据结构相对稳定,但经常需要在此基础上定义新的操作的场景。