创建型
1. 单例模式(Singleton Pattern)
单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。它的目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁访问且创建代价高的对象,或者需要控制资源访问点的情况下非常有用。
核心原理
单例模式的核心是在类内部实现对自身实例的唯一性和对这个实例的全局访问。通常,单例类会将其构造函数设为私有,防止外部直接实例化,并提供一个静态方法或属性作为全局访问点。
优点
- 资源控制:单例模式可以控制资源的消耗,避免多次创建同一对象造成的资源浪费。
- 全局访问:单例对象在整个应用中只有一个实例,可以通过全局访问点方便地访问。
- 线程安全:适当的实现可以确保在多线程环境下也能正确工作,避免资源冲突。
缺点
- 违反单一职责原则:单例类往往承担了过多的职责,这与单一职责原则相违背。
- 扩展困难:由于单例类没有抽象层,扩展单例类的功能变得困难。
- 滥用问题:过度使用单例模式可能导致系统变得难以理解和维护,因为它破坏了模块间的松耦合。
适用场景
- 频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。
- 控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。
- 工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。
Java示例
以下是几种常见的单例模式实现:
饿汉式(静态内部类)
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
懒汉式(线程安全)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
枚举单例(推荐)
public enum Singleton {
INSTANCE;
public void someMethod() {
// ...
}
}
枚举单例是Java中实现单例模式的一种简单且高效的方式,它天然支持序列化机制,防止反射攻击,同时保证线程安全。以上示例可通过 Singleton.INSTANCE 获取 Singleton的单例实例。
2. 工厂方法模式(Factory Method Pattern)
工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它提供了创建对象的最佳方式。在工厂方法模式中,一个抽象工厂类定义了一个创建对象的接口,但允许子类决定实例化哪一个类。工厂方法让类的实例化延迟到子类进行。
核心组件
- 抽象工厂(Creator):定义一个用于创建对象的接口,但让子类决定实例化哪一个类。
- 具体工厂(Concrete Creator):实现抽象工厂的接口,负责创建具体产品。
- 抽象产品(Product):定义了所有产品类的公共接口。
- 具体产品(Concrete Product):实现了抽象产品接口,是具体工厂创建的对象。
优点
- 封装性:封装了产品对象的创建细节,客户端不需要关心对象的创建过程。
- 解耦:客户端与具体产品类解耦,只与抽象工厂和抽象产品接口交互。
- 扩展性:当需要新增产品时,只需要增加具体产品类和对应的具体工厂类,不需要修改原有代码
缺点
- 增加系统复杂度:每增加一个产品,就需要增加一个具体产品类和一个对应的具体工厂类,这会增加系统的复杂度。
- 违反开闭原则:当需要增加新的产品时,需要修改原有的具体工厂类,这违反了“开闭原则”。
适用场景
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
Java示例
下面是一个简单的Java示例,展示如何使用工厂方法模式创建不同类型的按钮:
// 抽象产品
interface Button {
void display();
}
// 具体产品1
class WindowsButton implements Button {
@Override
public void display() {
System.out.println("Windows style button");
}
}
// 具体产品2
class MacButton implements Button {
@Override
public void display() {
System.out.println("Mac style button");
}
}
// 抽象工厂
abstract class GUIFactory {
abstract Button createButton();
}
// 具体工厂1
class WindowsFactory extends GUIFactory {
@Override
Button createButton() {
return new WindowsButton();
}
}
// 具体工厂2
class MacFactory extends GUIFactory {
@Override
Button createButton() {
return new MacButton();
}
}
// 客户端代码
public class Application {
private Button button;
public Application(GUIFactory factory) {
button = factory.createButton();
}
public void paint() {
button.display();
}
public static void main(String[] args) {
Application app = new Application(new MacFactory());
app.paint(); // 输出: Mac style button
}
}
在这个例子中,GUIFactory 是抽象工厂,WindowsFactory 和 MacFactory 是具体工厂,它们分别创建 WindowsButton 和 MacButton。客户端 Application 类通过传入不同的具体工厂来创建不同风格的按钮。这样,当需要支持新的操作系统时,只需要添加一个新的具体工厂类即可,而不需要修改 Application 类。
3. 抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种接口,用于创建一系列相关的或相互依赖的对象,而无需指定它们具体的类。此模式特别适用于创建产品族,即一组相关或相互依赖的对象。
核心组件
- 抽象工厂(Abstract Factory):定义创建产品族的接口。
- 具体工厂(Concrete Factory):实现抽象工厂接口,创建并返回具体产品。
- 抽象产品(Product):定义产品对象的接口。
- 具体产品(Concrete Product):实现抽象产品接口,是具体工厂创建的对象。
优点
- 封装性:将产品族的创建封装在一个工厂类中,客户端代码不需要知道具体的产品是如何创建的。
- 解耦:客户端与具体产品类解耦,只依赖于抽象工厂和抽象产品接口。
- 易于扩展:增加新的产品族时,只需添加新的具体工厂和具体产品类,不需要修改现有代码。
- 保证一致性:确保客户端始终使用同一个产品族中的对象,避免了不兼容对象的组合。
缺点
- 增加系统复杂度:每增加一个产品族,就需要增加一个具体工厂类和多个具体产品类,可能会导致系统复杂度上升。
- 违反开闭原则:当需要增加新的产品族时,需要修改现有的具体工厂类,这违反了“开闭原则”。
适用场景
- 当一个系统需要独立于它的产品创建、组合和表示时。
- 当一个系统要由多个产品系列中的一个来配置时。
- 当需要创建的产品具有多个变体,且这些变体是相互依赖的。
Java示例
假设我们有一个图形界面库,需要创建不同操作系统的窗口和按钮。我们可以使用抽象工厂模式来实现:
// 抽象产品接口
interface Window {}
interface Button {}
// 具体产品实现
class WindowsWindow implements Window {}
class MacOSWindow implements Window {}
class WindowsButton implements Button {}
class MacOSButton implements Button {}
// 抽象工厂接口
interface GUIFactory {
Window createWindow();
Button createButton();
}
// 具体工厂实现
class WindowsFactory implements GUIFactory {
@Override
public Window createWindow() {
return new WindowsWindow();
}
@Override
public Button createButton() {
return new WindowsButton();
}
}
class MacFactory implements GUIFactory {
@Override
public Window createWindow() {
return new MacOSWindow();
}
@Override
public Button createButton() {
return new MacOSButton();
}
}
// 客户端代码
public class Application {
private Window window;
private Button button;
public Application(GUIFactory factory) {
this.window = factory.createWindow();
this.button = factory.createButton();
}
public void paint() {
window.show();
button.click();
}
public static void main(String[] args) {
Application app = new Application(new MacFactory());
app.paint();
}
}
在这个例子中,GUIFactory 是抽象工厂,WindowsFactory 和 MacFactory 是具体工厂,它们分别创建 WindowsWindow 和 MacOSWindow 以及对应的按钮。客户端 Application 类通过传入不同的具体工厂来创建不同操作系统的窗口和按钮,从而支持跨平台的界面组件。
4. 建造者模式(Builder Pattern)
建造者模式(Builder Pattern)是一种创建型设计模式,它允许你逐步构建一个复杂的对象。这种模式将构建过程与表示分离,使得同样的构建过程可以创建不同的表示。建造者模式通常用于创建那些拥有多个可选组成部分的复杂对象,而且这些组成部分的构建顺序和方式可能会影响最终产品的表现形式。
核心组件
- 抽象建造者(Builder):定义一个用于创建产品对象的接口。
- 具体建造者(Concrete Builder):实现抽象建造者接口,负责构建并组装产品的各个部分。
- 指挥者(Director):调用建造者对象中的方法,构造一个完整的产品对象。
- 产品(Product):最终被创建的复杂对象。
优点
- 封装性:构建过程被封装在建造者类中,客户端不需要知道构建的细节。
- 灵活性:可以使用相同的构建过程创建不同的表示。
- 易于扩展:增加新的具体建造者类可以扩展产品类型,而不影响其他部分的代码。
缺点
- 增加系统复杂度:引入了额外的类(建造者类、指挥者类),增加了系统的复杂度。
- 违反开闭原则:如果产品内部组成发生变化,可能需要修改建造者类。
适用场景
- 当创建复杂对象的算法应该独立于组成该对象的部件以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
- 当对象的创建需要经过很多步骤,而且这些步骤可以有不同的组合时。
Java示例
假设我们要创建一个汉堡,汉堡可以有多种配料,我们可以使用建造者模式来实现:
// 产品 - 汉堡
class Burger {
private String bread;
private List<String> ingredients;
public Burger(String bread) {
this.bread = bread;
this.ingredients = new ArrayList<>();
}
public void addIngredient(String ingredient) {
ingredients.add(ingredient);
}
@Override
public String toString() {
return "Burger{" +
"bread='" + bread + '\'' +
", ingredients=" + ingredients +
'}';
}
}
// 抽象建造者
interface BurgerBuilder {
void setBread();
void addIngredients(String... ingredients);
Burger getResult();
}
// 具体建造者 - 素食汉堡建造者
class VeggieBurgerBuilder implements BurgerBuilder {
private Burger burger;
public VeggieBurgerBuilder() {
burger = new Burger("Whole wheat bun");
}
@Override
public void setBread() {
// 不需要做任何事情,因为已经在构造函数中设置了面包
}
@Override
public void addIngredients(String... ingredients) {
for (String ingredient : ingredients) {
burger.addIngredient(ingredient);
}
}
@Override
public Burger getResult() {
return burger;
}
}
// 具体建造者 - 肉汉堡建造者
class MeatBurgerBuilder implements BurgerBuilder {
private Burger burger;
public MeatBurgerBuilder() {
burger = new Burger("Brioche bun");
}
@Override
public void setBread() {
// 不需要做任何事情,因为已经在构造函数中设置了面包
}
@Override
public void addIngredients(String... ingredients) {
for (String ingredient : ingredients) {
burger.addIngredient(ingredient);
}
}
@Override
public Burger getResult() {
return burger;
}
}
// 指挥者
class Director {
public Burger construct(BurgerBuilder builder, String... ingredients) {
builder.setBread();
builder.addIngredients(ingredients);
return builder.getResult();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Director director = new Director();
BurgerBuilder veggieBuilder = new VeggieBurgerBuilder();
BurgerBuilder meatBuilder = new MeatBurgerBuilder();
Burger veggieBurger = director.construct(veggieBuilder, "Lettuce", "Tomato", "Avocado");
Burger meatBurger = director.construct(meatBuilder, "Beef patty", "Cheese", "Onion");
System.out.println(veggieBurger);
System.out.println(meatBurger);
}
}
在这个例子中,Burger 是产品,BurgerBuilder 是抽象建造者,VeggieBurgerBuilder 和 MeatBurgerBuilder 是具体建造者,Director 是指挥者。客户端通过指挥者调用具体建造者来创建不同类型的汉堡。
5. 原型模式(Prototype)
原型模式(Prototype Pattern)是一种创建型设计模式,它使用已有的实例作为原型,通过复制该原型对象来创建新的对象,而不是通过实例化类来创建对象。这种模式允许对象的克隆,即创建一个对象的副本,而无需知道其具体类型。原型模式的主要优势在于它可以避免构造函数的约束,提高创建对象的效率,尤其是在创建对象成本较高的情况下。
核心组件
- 抽象原型(Prototype):定义一个用于克隆的接口。
- 具体原型(Concrete Prototype):实现抽象原型接口,提供克隆自身的具体实现。
优点
- 性能高:使用原型模式复用的方式创建实例对象,比使用构造函数重新创建对象性能要高。
- 逃避构造函数的约束:可以直接在内存中拷贝,构造函数不会被执行,这对于构造函数复杂的类尤其有用。
- 资源优化:在资源优化场景下,类初始化需要消耗大量资源,原型模式可以避免重复初始化。
缺点
- 逃避构造函数的约束:直接在内存中拷贝,构造函数不会执行,这可能在某些情况下导致对象状态的不一致。
- 深拷贝与浅拷贝的问题:需要明确处理对象内的引用类型成员,否则可能导致对象共享同一引用,影响对象的独立性。
适用场景
- 创建新对象成本较大,如初始化需要占用较长的时间,占用太多的CPU资源或网络资源。
- 需要动态地增减产品族中的产品时。
- 当一个类的实例只能有几个不同状态组合中的一种时。
Java示例
在Java中,Cloneable 接口和 clone() 方法可以用来实现原型模式。下面是一个简单的Java示例:
import java.util.Date;
// 抽象原型
interface Prototype<T> extends Cloneable {
T clone();
}
// 具体原型
class Person implements Prototype<Person> {
private String name;
private Date birthDate;
public Person(String name, Date birthDate) {
this.name = name;
this.birthDate = birthDate;
}
public String getName() {
return name;
}
public Date getBirthDate() {
return birthDate;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 重写 clone 方法以实现深拷贝
public Person clone() {
try {
Person clonedPerson = (Person) super.clone();
clonedPerson.birthDate = (Date) this.birthDate.clone();
return clonedPerson;
} catch (CloneNotSupportedException e) {
throw new InternalError(e.toString());
}
}
}
// 客户端代码
public class PrototypeDemo {
public static void main(String[] args) {
Date birthDate = new Date();
Person person = new Person("John Doe", birthDate);
Person clonedPerson = person.clone();
System.out.println(person.getName() + ": " + person.getBirthDate());
System.out.println(clonedPerson.getName() + ": " + clonedPerson.getBirthDate());
}
}
在这个例子中,Person 类实现了 Prototype 接口,并重写了 clone() 方法以确保深拷贝。这样,当创建 Person 的克隆时,birthDate 字段也会被独立复制,避免了对象之间的引用共享。
结构型
1. 适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间可以协同工作。适配器模式的主要目的是将一个类的接口转换成客户希望的另一个接口,从而使原本因接口不兼容而不能一起工作的那些类可以一起工作。
核心组件
- 目标(Target):这是客户所期待的接口,适配器将被调整以符合这个接口。
- 适配者(Adaptee):这是需要被适配的类,它拥有客户不期望的接口。
- 适配器(Adapter):适配器持有适配者的引用,将适配者的接口转换为目标接口。
优点
- 提高了类的复用性:适配器模式可以让原本不兼容的类一起工作,提高了类的复用性。
- 增强了系统的灵活性:通过引入适配器,可以在不修改原有代码的情况下,使系统更加灵活,易于扩展和维护。
缺点
- 过多使用适配器会使系统变得复杂:过多的适配器会导致系统设计复杂,不易理解。
- 适配器模式本身并不符合“开闭原则”:当需要适配更多的接口时,需要增加更多的适配器类。
适用场景
- 当你需要复用一些现存的类,但是接口又与你的系统不兼容时。
- 当你想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作时。
- 当你希望使用已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
Java示例
假设我们有一个旧系统中的类 LegacyAudioPlayer 只能播放 MP3 文件,现在我们需要让它能够播放 WAV 文件,但是我们不想修改 LegacyAudioPlayer 的源代码。我们可以使用适配器模式来实现:
// 目标接口
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 适配者类
class LegacyAudioPlayer {
public void playMP3(String fileName) {
System.out.println("Playing MP3 file. Name: " + fileName);
}
}
// 适配器类
class MediaAdapter implements MediaPlayer {
private LegacyAudioPlayer legacyAudioPlayer;
public MediaAdapter() {
this.legacyAudioPlayer = new LegacyAudioPlayer();
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("mp3")) {
legacyAudioPlayer.playMP3(fileName);
} else if (audioType.equalsIgnoreCase("wav")) {
// 假设这里有一个转换WAV到MP3的方法
String mp3FileName = convertWavToMp3(fileName);
legacyAudioPlayer.playMP3(mp3FileName);
}
}
private String convertWavToMp3(String fileName) {
// 这里可以是转换逻辑
return "converted-" + fileName + ".mp3";
}
}
// 客户端代码
public class AdapterPatternDemo {
public static void main(String[] args) {
MediaPlayer mediaAdapter = new MediaAdapter();
mediaAdapter.play("mp3", "song.mp3");
mediaAdapter.play("wav", "song.wav");
}
}
在这个例子中,MediaPlayer 是目标接口,LegacyAudioPlayer 是适配者,MediaAdapter 是适配器。适配器持有 LegacyAudioPlayer 的实例,并将 MediaPlayer 的 play 方法调用适当地转换为 LegacyAudioPlayer 的 playMP3 方法。如果需要播放 WAV 文件,适配器会先将文件转换为 MP3 格式,然后调用 LegacyAudioPlayer 的方法。
2. 桥接模式(Bridge Pattern)
桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与其实现部分分离,使它们都可以独立地变化。桥接模式的主要目的是解耦一个抽象及其实现,以便两者可以独立地演变。这种模式通过提供一个桥接,将抽象与实现解耦,从而使得抽象和实现可以独立地扩展。
核心组件
- 抽象(Abstraction):定义使用实现部分的接口。
- 细化抽象(Refined Abstraction):扩展抽象,提供一个接口以访问实现部分的高级功能。
- 实现者(Implementor):定义实现部分的接口。
- 具体实现者(Concrete Implementor):实现实现者接口,提供具体的实现。
优点
- 分离抽象和实现:桥接模式允许抽象和实现独立变化,提高了系统的可扩展性和灵活性。
- 减少子类数目:相比于使用继承,桥接模式可以大大减少子类的数量,简化类层次结构。
- 易于维护和扩展:由于抽象和实现分离,当需要增加新的抽象或实现时,只需添加相应的类,而不需要修改现有代码。
缺点
- 增加理解难度:桥接模式引入了额外的抽象层,可能会使系统变得更加复杂,对于初学者来说理解起来可能更困难。
- 潜在的性能开销:桥接模式可能引入额外的间接调用,对于性能敏感的应用,这可能是一个考虑因素。
适用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当你不希望在抽象和它的实现部分之间有一个固定的绑定关系时。
- 当抽象和实现应该可以独立地扩展时。
Java示例
假设我们有一个绘图系统,需要绘制不同形状(如圆形和矩形)并且这些形状可以用不同的颜色来渲染。我们可以使用桥接模式来实现:
// 实现者接口
interface Renderer {
void drawCircle(float radius);
}
// 具体实现者 - 使用向量图形渲染
class VectorRenderer implements Renderer {
@Override
public void drawCircle(float radius) {
System.out.println("Drawing a circle [VectorRenderer: " + radius + "]");
}
}
// 具体实现者 - 使用位图图形渲染
class RasterRenderer implements Renderer {
@Override
public void drawCircle(float radius) {
System.out.println("Drawing a circle [RasterRenderer: " + radius + "]");
}
}
// 抽象类
abstract class Shape {
protected Renderer renderer;
public Shape(Renderer renderer) {
this.renderer = renderer;
}
public abstract void draw();
}
// 细化抽象 - 圆形
class Circle extends Shape {
private float radius;
public Circle(Renderer renderer, float radius) {
super(renderer);
this.radius = radius;
}
@Override
public void draw() {
renderer.drawCircle(radius);
}
}
// 客户端代码
public class BridgePatternDemo {
public static void main(String[] args) {
Renderer rasterRenderer = new RasterRenderer();
Renderer vectorRenderer = new VectorRenderer();
Shape circle1 = new Circle(rasterRenderer, 10);
circle1.draw();
Shape circle2 = new Circle(vectorRenderer, 10);
circle2.draw();
}
}
在这个例子中,Renderer 是实现者接口,VectorRenderer 和 RasterRenderer 是具体实现者,Shape 是抽象类,Circle 是细化抽象。Shape 类持有一个 Renderer 对象的引用,这样就可以在运行时选择不同的渲染方式。当我们创建 Circle 对象时,可以选择使用 RasterRenderer 或 VectorRenderer 来渲染,从而实现了渲染方式和形状的解耦。
3. 组合模式(Composite Pattern)
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树状结构来表示“部分-整体”的层次结构。组合模式使得客户端可以一致地处理单个对象和组合对象,即以相同的方式操作叶子对象和树枝对象。
核心组件
- Component(组件):定义了包含所有对象的公共接口,无论是叶子对象还是复合对象。
- Leaf(叶子):实现Component接口,代表树结构中的叶节点。
- Composite(复合):也实现Component接口,但包含其他Component对象的集合,代表树结构中的分支节点。
优点
- 高层模块无须关心下层模块的具体实现:组合模式使得高层模块可以一致地处理单个对象和组合对象,降低了系统各部分的相互依赖。
- 易于增加新的组件:可以很容易地增加新的组件,因为高层模块只需要关注Component接口。
- 易于实现树形结构:组合模式非常适合表示树形结构,例如文件系统、组织结构等。
缺点
- 设计复杂度增加:组合模式增加了设计的复杂度,尤其是当系统中存在大量的Component对象时。
- 客户端可能需要处理空指针异常:如果客户端代码没有正确处理null的情况,可能会抛出空指针异常。
适用场景
- 当你想表示对象的部分-整体层次结构时。
- 当你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
Java示例
假设我们正在设计一个文件系统,其中文件夹可以包含其他文件夹和文件,我们可以使用组合模式来实现:
// Component接口
interface FileSystemItem {
void add(FileSystemItem item);
void remove(FileSystemItem item);
FileSystemItem getChild(int i);
void printList();
}
// Leaf - 文件
class File implements FileSystemItem {
private String name;
public File(String name) {
this.name = name;
}
@Override
public void add(FileSystemItem item) {
// 文件不能包含其他项
throw new UnsupportedOperationException("Cannot add to a file.");
}
@Override
public void remove(FileSystemItem item) {
// 文件不能包含其他项
throw new UnsupportedOperationException("Cannot remove from a file.");
}
@Override
public FileSystemItem getChild(int i) {
// 文件不能包含其他项
throw new UnsupportedOperationException("Cannot get child of a file.");
}
@Override
public void printList() {
System.out.println("File: " + name);
}
}
// Composite - 文件夹
class Directory implements FileSystemItem {
private String name;
private List<FileSystemItem> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public void add(FileSystemItem item) {
children.add(item);
}
@Override
public void remove(FileSystemItem item) {
children.remove(item);
}
@Override
public FileSystemItem getChild(int i) {
return children.get(i);
}
@Override
public void printList() {
System.out.println("Directory: " + name);
for (FileSystemItem item : children) {
item.printList();
}
}
}
// 客户端代码
public class CompositePatternDemo {
public static void main(String[] args) {
FileSystemItem root = new Directory("root");
FileSystemItem subDir1 = new Directory("subDir1");
FileSystemItem subDir2 = new Directory("subDir2");
FileSystemItem file1 = new File("file1.txt");
FileSystemItem file2 = new File("file2.txt");
root.add(subDir1);
root.add(subDir2);
subDir1.add(file1);
subDir2.add(file2);
root.printList();
}
}
在这个例子中,FileSystemItem 是Component接口,File 和 Directory 分别是Leaf和Composite。Directory 可以包含其他 FileSystemItem 对象,而 File 不能。客户端代码可以一致地处理 File 和 Directory,调用它们的 printList 方法来打印文件系统的内容。
4. 装饰器模式(Decorator Pattern)
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许在不修改原类代码的情况下动态地给对象添加新的功能。装饰器模式通过使用对象的组合而非继承来达到扩展功能的目的,使得系统更加灵活,可以动态地给一个对象添加职责。
核心组件
- Component(组件):定义了一个对象接口,可以给这些对象动态地添加职责。
- ConcreteComponent(具体组件):定义了具体的组件对象。
- Decorator(装饰器):持有Component类型的引用,并定义了一个与Component接口一致的接口。
- ConcreteDecorator(具体装饰器):负责给Component对象添加职责。
优点
- 扩展性好:可以在运行时动态地给对象添加功能,而不必修改原有的代码。
- 符合开闭原则:可以通过增加新的装饰器类来增强功能,而无需修改现有代码。
- 替代继承:在某些情况下,装饰器模式可以替代继承,避免了类的爆炸性增长。
缺点
- 产生很多小类:每增加一个功能,就需要增加一个新的装饰器类,可能会导致类的数量增加。
- 调试困难:由于装饰器模式的动态特性,调试时可能难以追踪装饰器链中具体是哪个装饰器产生了效果。
适用场景
- 当需要在运行时给一个对象添加额外的功能,或者动态地增加职责时。
- 当不能采用生成子类的方法进行扩充时,一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
- 当需要增加由一些基本功能的排列组合而产生的非常强大的功能时。
Java示例
假设我们有一个饮料基类,我们想动态地给饮料添加不同的配料,如糖、奶泡等。下面是一个使用装饰器模式的例子:
// Component
interface Beverage {
String getDescription();
double cost();
}
// ConcreteComponent
class Espresso implements Beverage {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
// Decorator
abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription();
}
}
// ConcreteDecorator
class Mocha implements CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.20;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
}
class Whip implements CondimentDecorator {
public Whip(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 0.10;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
}
// 客户端代码
public class DecoratorPatternDemo {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
beverage = new Mocha(new Whip(new Espresso()));
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}
在这个例子中,Beverage 是Component接口,Espresso 是具体组件,CondimentDecorator 是装饰器基类,Mocha 和 Whip 是具体装饰器。装饰器持有被装饰对象的引用,并在其方法中调用被装饰对象的方法,同时可以添加自己的行为。这样,我们可以在运行时动态地给饮料添加不同的配料,而无需修改饮料的原始代码。
5. 外观模式(Facade Pattern)
外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。外观模式的主要目的是简化一个复杂系统的使用,通过提供一个更高层次的接口,使得这个子系统更加容易被使用。
核心组件
- Facade(外观):提供了一个简单的接口,用于访问子系统中的一群接口。
- Subsystems(子系统):子系统可以由任何数量的对象组成,每个子系统负责一部分的业务逻辑。
优点
- 降低耦合:外观模式可以降低子系统与客户端之间的耦合度,使得子系统的变化不会影响到客户端。
- 简化使用:通过外观模式,客户端可以只通过一个简单的接口来使用整个子系统,无需了解子系统的内部细节。
- 更好的分层:外观模式可以帮助系统更好地分层,使得每一层都有清晰的职责。
缺点
- 不符合开闭原则:当添加新的子系统或者移除现有子系统时,可能需要修改外观类或客户端的代码。
- 可能增加系统复杂度:如果过度使用外观模式,可能会增加系统中类的数量,从而增加系统的复杂度。
适用场景
- 当一个系统有多个子系统,且这些子系统相对独立,但需要对外提供统一的接口时。
- 当需要为一个复杂子系统提供一个简单接口时。
- 当构建层次化结构的系统时,使用外观模式定义系统中每一层的入口点。
Java示例
假设我们有一个家庭自动化系统,包括灯光控制、温度控制和安全系统。我们可以使用外观模式来简化对这些子系统的使用:
// 子系统 - 灯光控制
class LightControl {
public void turnOn() {
System.out.println("Lights are on.");
}
public void turnOff() {
System.out.println("Lights are off.");
}
}
// 子系统 - 温度控制
class TemperatureControl {
public void setTemperature(int temperature) {
System.out.println("Setting temperature to " + temperature + " degrees.");
}
}
// 子系统 - 安全系统
class SecuritySystem {
public void arm() {
System.out.println("Security system is armed.");
}
public void disarm() {
System.out.println("Security system is disarmed.");
}
}
// Facade
class HomeAutomationFacade {
private LightControl lightControl = new LightControl();
private TemperatureControl temperatureControl = new TemperatureControl();
private SecuritySystem securitySystem = new SecuritySystem();
public void goodNight() {
lightControl.turnOff();
temperatureControl.setTemperature(18);
securitySystem.arm();
}
public void goodMorning() {
lightControl.turnOn();
temperatureControl.setTemperature(22);
securitySystem.disarm();
}
}
// 客户端代码
public class FacadePatternDemo {
public static void main(String[] args) {
HomeAutomationFacade facade = new HomeAutomationFacade();
// 使用外观模式简化操作
facade.goodNight();
System.out.println("---");
facade.goodMorning();
}
}
在这个例子中,HomeAutomationFacade 是外观类,它持有LightControl、TemperatureControl和SecuritySystem的引用。客户端可以通过调用HomeAutomationFacade的goodNight和goodMorning方法来简化对家庭自动化系统的使用,而无需直接与子系统交互。
6. 享元模式(Flyweight Pattern)
享元模式(Flyweight Pattern)是一种结构型设计模式,它用于减少创建大量相似对象所需的内存消耗。享元模式通过共享尽可能多的数据来支持大量细粒度的对象,通常用于处理大量相似对象的场景,尤其是当这些对象的大部分状态可以外部化时。
核心组件
- Flyweight(享元):存储内部状态,这部分状态是不变的,可以共享。
- UnsharedConcreteFlyweight(未共享的具体享元):实现享元接口,可能包含外部状态。
- FlyweightFactory(享元工厂):管理享元对象的创建和获取,确保相同的享元只被创建一次。
优点
- 节省内存:通过共享相同的对象,可以显著减少内存消耗。
- 提高性能:减少了对象的创建,可以提高系统性能。
缺点
- 增加复杂性:为了共享对象,需要将状态分为内部状态和外部状态,这可能增加代码的复杂性。
- 读取外部状态:在使用享元对象时,需要从外部传递状态,这可能导致运行时间稍微变长。
适用场景
- 当系统中有大量相似对象时。
- 当对象的大多数状态可以外部化时。
- 当使用少量的共享对象可以替代大量对象时。
- 当对象的创建和销毁成本较高时。
Java示例
假设我们正在开发一个文本编辑器,需要显示大量的字符,每个字符可以有不同的字体、大小和颜色。为了节省内存,我们可以使用享元模式来共享字符的内部状态(如字体、大小),而将颜色作为外部状态。
import java.util.HashMap;
import java.util.Map;
// Flyweight interface
interface CharacterFlyweight {
void display(int x, int y, char character, int color);
}
// UnsharedConcreteFlyweight
class ConcreteCharacterFlyweight implements CharacterFlyweight {
private final String font;
private final int size;
public ConcreteCharacterFlyweight(String font, int size) {
this.font = font;
this.size = size;
}
@Override
public void display(int x, int y, char character, int color) {
System.out.printf("Displaying character '%c' at (%d, %d) with font '%s', size %d, and color %d%n",
character, x, y, font, size, color);
}
}
// FlyweightFactory
public class CharacterFlyweightFactory {
private Map<String, CharacterFlyweight> flyweights = new HashMap<>();
public CharacterFlyweight getCharacterFlyweight(String font, int size) {
String key = font + "-" + size;
CharacterFlyweight flyweight = flyweights.get(key);
if (flyweight == null) {
flyweight = new ConcreteCharacterFlyweight(font, size);
flyweights.put(key, flyweight);
}
return flyweight;
}
}
// Client code
public class FlyweightPatternDemo {
public static void main(String[] args) {
CharacterFlyweightFactory factory = new CharacterFlyweightFactory();
CharacterFlyweight a = factory.getCharacterFlyweight("Arial", 12);
CharacterFlyweight b = factory.getCharacterFlyweight("Arial", 12);
CharacterFlyweight c = factory.getCharacterFlyweight("Times New Roman", 14);
a.display(10, 10, 'A', 0xFF0000); // Red
b.display(20, 20, 'B', 0x00FF00); // Green
c.display(30, 30, 'C', 0x0000FF); // Blue
}
}
在这个例子中,CharacterFlyweight 是享元接口,ConcreteCharacterFlyweight 是具体享元,CharacterFlyweightFactory 是享元工厂。字体和大小作为内部状态在享元对象中存储,而颜色作为外部状态在使用时传入。通过享元工厂,相同的字体和大小组合只会创建一次享元对象,从而节省了内存。
7. 代理模式(Proxy Pattern)
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一个代理以控制对这个对象的访问。代理模式允许程序员在访问一个类的实例之前,增加一些额外的操作,比如权限检查、日志记录、缓存等。
核心组件
- Subject(主题):定义了代理和真实主题共同的接口。
- RealSubject(真实主题):定义了代理所代表的真实对象。
- Proxy(代理):包含对真实主题的引用,以便在代理自身处理请求时,它可以将请求转给真实主题。
优点
- 职责分离:代理模式可以将调用者和被调用者解耦,使得两者可以独立变化。
- 控制访问:可以用来控制对真实对象的访问,例如权限检查。
- 智能引用:代理可以智能地决定何时创建或销毁真实对象,例如懒加载。
- 远程代理:可以作为远程对象的本地代表,隐藏远程对象的位置和协议细节。
缺点
- 性能开销:每次调用都会经过代理,可能会增加额外的延迟。
- 实现复杂:特别是动态代理的实现可能比较复杂。
适用场景
- 当需要控制对一个对象的访问时。
- 当需要为远程对象创建一个本地代理时。
- 当需要创建一个对象的成本很高,希望通过一个轻量级的代理来代替时。
Java示例
下面是一个使用静态代理的Java示例,假设我们有一个图像加载和显示的场景,其中代理可以缓存图像,避免重复加载。
// Subject
interface Image {
void display();
}
// RealSubject
class RealImage implements Image {
private final String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(filename);
}
private void loadFromDisk(String filename) {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// Proxy
class ProxyImage implements Image {
private RealImage realImage;
private final String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// Client code
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像第一次加载
image.display();
// 再次显示图像,这次不会重新加载
image.display();
}
}
在这个例子中,Image 是主题接口,RealImage 是真实主题,ProxyImage 是代理。ProxyImage 在第一次调用display方法时会创建并加载RealImage,之后的调用则直接使用已加载的RealImage对象,避免了重复加载,实现了懒加载的效果。
行为型
1. 责任链模式(Chain of Responsibility Pattern)
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,它使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
核心组件
- Handler(处理器):定义了一个处理请求的接口,包含一个后继处理器的引用。
- ConcreteHandler(具体处理器):实现Handler接口,包含具体的处理逻辑。如果可以处理该请求,则处理;否则将请求传给后继处理器。
优点
- 降低耦合度:发送者和接收者之间解耦,发送者无需知道请求的处理者是谁。
- 灵活性:可以动态地增加或改变处理一个请求的接收者。
- 简化对象:使一个对象无需知道它的后继者是谁,简化了对象的设计。
缺点
- 可能无法保证请求一定被接收:如果处理链的最后一个处理器也没有处理请求,则请求将丢失。
- 系统性能:可能会比直接调用接收者更慢,因为请求会在链上传递。
- 调试困难:请求的处理过程可能不易跟踪,特别是在链很长的情况下。
适用场景
- 当有多个对象可以处理同一个请求,但是具体哪个对象处理该请求在运行时刻自动确定。
- 当在处理一个请求的处理者集合应被动态指定。
Java示例
假设在一个公司中,员工提交请假申请,根据请假天数,申请可能需要不同级别的管理者批准。我们可以使用责任链模式来处理这个场景:
// Handler
abstract class LeaveRequestHandler {
protected LeaveRequestHandler nextHandler;
public LeaveRequestHandler setNext(LeaveRequestHandler handler) {
nextHandler = handler;
return handler;
}
public abstract void handleRequest(int days);
}
// ConcreteHandler
class TeamLead extends LeaveRequestHandler {
@Override
public void handleRequest(int days) {
if (days <= 2) {
System.out.println("Team Lead approved leave for " + days + " days.");
} else {
if (nextHandler != null) {
nextHandler.handleRequest(days);
} else {
System.out.println("No handler available for " + days + " days leave request.");
}
}
}
}
class Manager extends LeaveRequestHandler {
@Override
public void handleRequest(int days) {
if (days <= 5) {
System.out.println("Manager approved leave for " + days + " days.");
} else {
if (nextHandler != null) {
nextHandler.handleRequest(days);
} else {
System.out.println("No handler available for " + days + " days leave request.");
}
}
}
}
class Director extends LeaveRequestHandler {
@Override
public void handleRequest(int days) {
System.out.println("Director approved leave for " + days + " days.");
}
}
// Client code
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
LeaveRequestHandler teamLead = new TeamLead();
LeaveRequestHandler manager = new Manager();
LeaveRequestHandler director = new Director();
teamLead.setNext(manager).setNext(director);
teamLead.handleRequest(1); // Should be handled by Team Lead
teamLead.handleRequest(3); // Should be handled by Manager
teamLead.handleRequest(6); // Should be handled by Director
}
}
在这个例子中,LeaveRequestHandler 是处理器接口,TeamLead、Manager 和 Director 是具体处理器。请求(请假天数)沿着责任链传递,直到找到合适的处理器。如果请求没有被任何一个处理器处理,那么它将不会得到响应
2. 命令模式(Command Pattern)
命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
核心组件
- Command(命令):声明执行操作的接口。
- ConcreteCommand(具体命令):定义一个接收者对象,然后在execute()方法中调用接收者对象的操作。
- Invoker(调用者):请求一个命令对象执行一个操作。
- Receiver(接收者):知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。
优点
- 降低发送者和接收者的耦合度:发送者与接收者完全解耦,发送者只持有命令的引用。
- 新的命令可以很容易添加到系统中去:系统可以很容易地增加新的命令。
- 支持撤销操作:如果命令对象保存了足够的状态,就可以支持撤销操作。
缺点
- 可能导致某些系统有过多的具体命令类:每一个具体操作都需要设计一个具体命令类。
- 增加了系统的抽象层次:引入了许多新的类,理解系统的难度可能会增加。
适用场景
- 当一个操作需要在不同的时间点被执行,或者需要撤销和重做。
- 当需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 当需要抽象出等待执行的行为,比如在GUI中,用户单击按钮,按钮调用命令对象的execute()方法,而具体的执行动作由具体命令对象来完成。
Java示例
假设我们有一个简单的遥控器,可以控制不同的设备,如电灯和电视,执行打开和关闭的操作。我们可以使用命令模式来设计这个系统:
// Command interface
interface Command {
void execute();
}
// Receiver classes
class Light {
public void on() {
System.out.println("Light is on");
}
public void off() {
System.out.println("Light is off");
}
}
class TV {
public void on() {
System.out.println("TV is on");
}
public void off() {
System.out.println("TV is off");
}
}
// ConcreteCommand classes
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
class TVOnCommand implements Command {
private TV tv;
public TVOnCommand(TV tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.on();
}
}
class TVOffCommand implements Command {
private TV tv;
public TVOffCommand(TV tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.off();
}
}
// Invoker class
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// Client code
public class CommandPatternDemo {
public static void main(String[] args) {
Light light = new Light();
TV tv = new TV();
RemoteControl remoteControl = new RemoteControl();
remoteControl.setCommand(new LightOnCommand(light));
remoteControl.pressButton();
remoteControl.setCommand(new TVOnCommand(tv));
remoteControl.pressButton();
remoteControl.setCommand(new LightOffCommand(light));
remoteControl.pressButton();
remoteControl.setCommand(new TVOffCommand(tv));
remoteControl.pressButton();
}
}
在这个例子中,Command 是命令接口,LightOnCommand、LightOffCommand、TVOnCommand 和 TVOffCommand 是具体命令,Light 和 TV 是接收者,RemoteControl 是调用者。通过这种方式,我们可以动态地向遥控器添加不同的命令,而且可以轻松地扩展系统以支持更多的设备和操作。
3. 解释器模式(Interpreter Pattern)
解释器模式(Interpreter Pattern)是一种行为设计模式,它允许你定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。这种模式可以用于编译器、查询语言解析器等领域,其中需要解析特定的语法规则。
核心组件
- AbstractExpression(抽象表达式):声明一个抽象接口,规范所有节点的解释操作。
- TerminalExpression(终结表达式):实现抽象表达式的接口,通常返回一个词法符号。
- NonterminalExpression(非终结表达式):也实现抽象表达式的接口,但包含对其他表达式的引用。
- Context(上下文):包含解释器之外的一些全局信息,可能被解释器的多个部分使用。
优点
- 易于扩展语言:可以通过增加新的终结表达式和非终结表达式来扩展语言的文法。
- 实现简单文法:对于简单的文法,解释器模式可以提供一个简单的实现方式。
缺点
- 效率问题:对于复杂的文法,解释器模式的效率较低,因为其递归性质导致了大量的函数调用。
- 难以维护:当文法变得复杂时,解释器模式的代码可能变得难以理解和维护。
适用场景
- 当一个应用的一部分功能需要动态地解析和执行某种语言时。
- 当一个语言的文法比较简单,且应用需要频繁解析和执行这个语言时。
Java示例
假设我们需要解析一个简单的算术表达式语言,其中包含加法和减法运算。我们可以使用解释器模式来实现:
// AbstractExpression
abstract class Expression {
abstract int interpret(Context context);
}
// TerminalExpression
class NumberExpression extends Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
int interpret(Context context) {
return number;
}
}
// NonterminalExpression
class AdditionExpression extends Expression {
private Expression left;
private Expression right;
public AdditionExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
class SubtractionExpression extends Expression {
private Expression left;
private Expression right;
public SubtractionExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
// Context
class Context {
// In this simple example, the context is not used.
}
// Client code
public class InterpreterPatternDemo {
public static void main(String[] args) {
Expression expr1 = new AdditionExpression(
new NumberExpression(5),
new NumberExpression(10)
);
System.out.println("5 + 10 = " + expr1.interpret(new Context()));
Expression expr2 = new SubtractionExpression(
new NumberExpression(20),
new NumberExpression(5)
);
System.out.println("20 - 5 = " + expr2.interpret(new Context()));
}
}
在这个例子中,Expression 是抽象表达式,NumberExpression 是终结表达式,AdditionExpression 和 SubtractionExpression 是非终结表达式,它们分别代表数字、加法和减法。Context 类在这里没有实际用途,但在更复杂的应用中,它可能包含解释器需要的额外信息。通过这种方式,我们可以构建和解析算术表达式树,并计算其结果。
4. 迭代器模式(Iterator Pattern)
迭代器模式(Iterator Pattern)是一种行为设计模式,它提供了一种方法来访问聚合对象(如列表、集合等)的元素,而无需暴露其底层表示。迭代器模式允许客户端顺序访问聚合对象的元素,而无需知道聚合对象的内部结构。
核心组件
- Iterator(迭代器):定义访问和遍历元素的接口。
- ConcreteIterator(具体迭代器):维护遍历过程中下一个元素的位置,并负责遍历整个聚合对象。
- Aggregate(聚合):定义一个用于创建迭代器的方法,并提供一个用于存储元素的容器。
- ConcreteAggregate(具体聚合):实现聚合接口,并返回一个适当的具体迭代器对象。
优点
- 封装遍历逻辑:迭代器模式封装了遍历逻辑,使得客户端不必关心聚合对象的内部结构。
- 支持多种遍历方式:可以通过实现不同的迭代器来支持多种遍历方式。
- 符合单一职责原则:迭代器模式将遍历逻辑从聚合对象中分离出来,使得聚合对象和迭代器各自承担自己的职责。
缺点
- 增加系统复杂性:为了支持迭代器,需要增加额外的类,可能会增加系统的复杂性。
- 迭代器模式的适用性有限:在某些情况下,使用语言内置的迭代器可能更为高效和简洁。
适用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为聚合对象提供统一的访问接口,而不暴露其内部结构时。
- 当聚合对象太大,不能一次性加载到内存中,需要按需加载时。
Java示例
在Java中,Iterable接口和Iterator接口已经提供了迭代器模式的实现。这里我们创建一个简单的例子,展示如何手动实现迭代器模式:
// Aggregate
interface Aggregate {
Iterator createIterator();
}
// ConcreteAggregate
class MyList implements Aggregate {
private List<String> items = new ArrayList<>();
public void add(String item) {
items.add(item);
}
@Override
public Iterator createIterator() {
return new MyIterator(items);
}
}
// Iterator
interface Iterator {
boolean hasNext();
Object next();
}
// ConcreteIterator
class MyIterator implements Iterator {
private List<String> items;
private int position = 0;
public MyIterator(List<String> items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.size();
}
@Override
public Object next() {
if (this.hasNext()) {
return items.get(position++);
}
return null;
}
}
// Client code
public class IteratorPatternDemo {
public static void main(String[] args) {
MyList list = new MyList();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
Iterator iterator = list.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
在这个例子中,MyList 是具体聚合,它实现了Aggregate接口,并提供了一个方法来创建迭代器。MyIterator 是具体迭代器,它实现了Iterator接口,负责遍历MyList中的元素。客户端代码使用迭代器遍历MyList中的元素,而不需要知道MyList是如何存储这些元素的。
5. 中介者模式(Mediator Pattern)
中介者模式(Mediator Pattern)是一种行为设计模式,它定义了对象间的一种间接通信机制,使得对象之间通过一个共享的中介者对象进行通信,而不是直接相互引用。这样可以减少对象间的依赖关系,使它们之间松耦合。
核心组件
- Mediator(中介者):定义一个接口用于各个同事对象之间的通信。
- ConcreteMediator(具体中介者):实现中介者接口,协调各个同事对象,通常它依赖于各个同事类的方法。
- Colleague(同事):定义一个接口用于同事对象之间的通信,同事对象只知道中介者,不知道其他的同事对象。
- ConcreteColleague(具体同事):实现同事接口,当需要和其他同事对象通信时,就通知中介者。
优点
- 降低耦合:同事类之间不再直接通信,而是通过中介者进行,降低了对象间的耦合度。
- 集中控制:将对象间的交互集中在一个中介者对象上,便于管理和控制。
- 易于维护和扩展:当需要改变对象间的交互时,只需要修改中介者即可,无须修改同事类。
缺点
- 中介者类过于庞大:如果一个系统中有大量的同事类,那么中介者类会非常复杂。
- 中介者模式的引入增加了系统的复杂性:需要额外的类和接口,增加了系统的理解和维护成本。
适用场景
- 当一组对象之间存在复杂的依赖关系,以至于形成了网状结构,难以理解和维护时。
- 当需要通过一个中心点来协调多个对象的交互时。
Java示例
假设我们有一个聊天室系统,其中用户可以发送消息给其他用户。我们使用中介者模式来管理用户之间的消息传递:
// Mediator interface
interface ChatRoom {
void sendMessage(String msg, User user);
}
// ConcreteMediator
class SimpleChatRoom implements ChatRoom {
@Override
public void sendMessage(String msg, User user) {
System.out.println(user.getName() + ": " + msg);
}
}
// Colleague interface
interface User {
String getName();
void receiveMessage(String msg);
}
// ConcreteColleague
class SimpleUser implements User {
private String name;
private ChatRoom chatRoom;
public SimpleUser(String name, ChatRoom chatRoom) {
this.name = name;
this.chatRoom = chatRoom;
}
@Override
public String getName() {
return name;
}
public void sendMessage(String msg) {
chatRoom.sendMessage(msg, this);
}
@Override
public void receiveMessage(String msg) {
System.out.println(name + " received: " + msg);
}
}
// Client code
public class MediatorPatternDemo {
public static void main(String[] args) {
ChatRoom chatRoom = new SimpleChatRoom();
User john = new SimpleUser("John", chatRoom);
User jane = new SimpleUser("Jane", chatRoom);
john.sendMessage("Hello Jane!");
jane.sendMessage("Hi John!");
}
}
在这个例子中,ChatRoom 是中介者接口,SimpleChatRoom 是具体中介者,它负责转发消息。User 是同事接口,SimpleUser 是具体同事,它们通过中介者发送和接收消息。客户端代码创建了两个用户并让它们通过中介者发送消息。这种方式确保了用户之间没有直接的依赖关系,所有的通信都通过中介者进行。
6. 备忘录模式(Memento Pattern)
备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下捕获和恢复一个对象的内部状态。这种模式经常用于实现撤销(undo)功能,或者在对象需要保存其状态以供以后恢复时使用。
核心组件
- Originator(发起人):包含一些重要的“内部状态”,需要在某些时候保存这份内部状态,在其他时候再恢复回来。
- Memento(备忘录):负责存储发起人的内部状态,在发起人需要时返回其内部状态。
- Caretaker(管理者):负责保存与管理备忘录对象,但不能对备忘录的内容进行操作或检查。
优点
- 封装性好:备忘录模式封装了发起人的内部状态,外界只能通过备忘录对象来获取和设置状态,而不能直接访问发起人的内部状态。
- 提供了一种可以恢复状态的机制:当用户需要时能够比较方便地将数据恢复到某个历史的状态。
缺点
- 消耗资源:如果系统中需要保存大量状态,备忘录可能会占用较多的存储空间。
- 备忘录的存储和管理可能会变得复杂:尤其是当备忘录数量很多时,管理这些备忘录可能需要额外的开销。
适用场景
- 需要保存和恢复数据的内部状态,而又不想暴露其内部结构。
- 需要提供一个可回滚的操作,例如实现撤销(undo)功能。
Java示例
假设我们有一个文本编辑器,需要保存文档的不同版本,以便用户可以随时恢复到之前的版本:
// Memento
class DocumentMemento {
private final String content;
public DocumentMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
// Originator
class Document {
private String content;
public Document(String content) {
this.content = content;
}
public void setContent(String content) {
this.content = content;
}
public DocumentMemento save() {
return new DocumentMemento(this.content);
}
public void restore(DocumentMemento memento) {
this.content = memento.getContent();
}
@Override
public String toString() {
return "Document{" +
"content='" + content + '\'' +
'}';
}
}
// Caretaker
class DocumentHistory {
private List<DocumentMemento> mementos = new ArrayList<>();
private int current = -1;
public void addMemento(DocumentMemento memento) {
if (this.current >= 0) {
this.mementos.subList(this.current + 1, this.mementos.size()).clear();
}
this.mementos.add(memento);
this.current++;
}
public DocumentMemento getMemento() {
if (this.current < 0) {
return null;
}
return this.mementos.get(this.current);
}
public DocumentMemento undo() {
if (this.current <= 0) {
return null;
}
this.current--;
return this.mementos.get(this.current);
}
public DocumentMemento redo() {
if (this.current >= this.mementos.size() - 1) {
return null;
}
this.current++;
return this.mementos.get(this.current);
}
}
// Client code
public class MementoPatternDemo {
public static void main(String[] args) {
Document document = new Document("Initial content");
DocumentHistory history = new DocumentHistory();
System.out.println(document);
history.addMemento(document.save());
document.setContent("First change");
System.out.println(document);
history.addMemento(document.save());
document.setContent("Second change");
System.out.println(document);
history.addMemento(document.save());
document.restore(history.undo().getContent());
System.out.println(document);
document.restore(history.undo().getContent());
System.out.println(document);
}
}
在这个例子中,Document 是发起人,它保存着文档的内容,DocumentMemento 是备忘录,用来存储文档的特定状态,DocumentHistory 是管理者,它负责保存和管理备忘录对象。通过这种方式,我们可以在任何时候保存文档的状态,并在需要时恢复到之前的状态。
7. 观察者模式(Observer Pattern)
观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式有时也被称作发布/订阅模式或模型-视图模式。
核心组件
- Subject(主题):也称为被观察者,它维护一个观察者列表,并在状态发生变化时通知所有观察者。
- Observer(观察者):也称为订阅者,它定义了一个更新的接口,以便在收到主题的通知时更新自己。
- ConcreteSubject(具体主题):实现Subject接口,当其状态发生变化时,向注册的观察者发出通知。
- ConcreteObserver(具体观察者):实现Observer接口,当收到主题的通知时,更新自己的状态。
优点
- 降低耦合度:观察者和主题之间是松耦合的,主题并不知道观察者具体是谁,只知道它们实现了观察者的接口。
- 支持广播通信:一个主题可以有多个观察者,当主题状态改变时,所有观察者都会收到通知。
- 遵循依赖倒置原则:观察者和主题之间的依赖是基于抽象的,这符合依赖倒置原则。
缺点
- 潜在的性能问题:如果观察者过多,通知所有观察者可能会造成性能瓶颈。
- 复杂性增加:在实现观察者模式时,需要处理注册、注销以及通知机制,这可能会增加代码的复杂性。
适用场景
- 当一个对象状态的改变需要通知其他对象,而不知道具体有多少对象时。
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁时。
- 当一个对象需要维护一系列依赖对象,而不想使用子类化或直接引用的方式时。
Java示例
以下是一个使用Java实现的观察者模式的简单示例,我们将创建一个天气预报系统,其中天气数据源(Subject)会通知多个天气显示设备(Observers):
import java.util.ArrayList;
import java.util.List;
// Observer interface
interface Observer {
void update(float temp, float humidity, float pressure);
}
// Subject interface
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// ConcreteSubject
class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
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();
}
}
// ConcreteObserver
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature + " F degrees and " + humidity + "% humidity");
}
}
// Client code
public class ObserverPatternDemo {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
weatherData.registerObserver(currentDisplay);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
在这个例子中,WeatherData 是具体主题,它维护了一个观察者列表,并在状态改变时通知所有观察者。CurrentConditionsDisplay 是具体观察者,它实现了Observer接口,并在收到通知时更新自己的状态并显示出来。当WeatherData的状态改变时,所有注册的观察者都会被通知并更新自己的显示。
8. 状态模式(State Pattern)
状态模式(State Pattern)是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。也就是说,对象看起来像是改变了其类。这个模式将与特定状态相关的行为封装在独立的类中,使得状态转换逻辑清晰且易于管理。
核心组件
- Context(上下文):维护一个对AbstractState对象的引用,这个引用代表当前状态。
- AbstractState(抽象状态):定义一个接口,封装与Context的一个特定状态相关的行为。
- ConcreteState(具体状态):实现AbstractState接口,每个ConcreteState对应Context的一个具体状态,并实现与该状态相关的行为。
优点
- 封装了转换:状态模式把转换的规则封装在不同的状态对象中,可以随着应用的需要增加更多的状态和转换。
- 简化了Context:Context不需要知道所有可能的状态,只需要知道当前的状态,具体的转换逻辑由状态对象来完成。
- 遵循开放封闭原则:新的状态可以自由添加到系统中,而不会影响到现有状态的代码。
缺点
- 状态模式的使用必然会增加系统类和对象的数量:每一个状态都是一个类,对于状态比较多的应用场景,这可能会导致类的膨胀。
- 状态模式的结构与实现都较为复杂:如果使用不当,可能会使程序结构混乱,不易理解。
适用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 当一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态时。
Java示例
假设我们有一个简单的交通信号灯系统,它可以处于红灯、黄灯或绿灯三种状态之一,每种状态都有特定的行为:
// AbstractState
interface TrafficLightState {
void handle(TrafficLight light);
}
// ConcreteStates
class RedLightState implements TrafficLightState {
@Override
public void handle(TrafficLight light) {
System.out.println("Red Light: Stop");
light.setState(light.getYellowLightState());
}
}
class YellowLightState implements TrafficLightState {
@Override
public void handle(TrafficLight light) {
System.out.println("Yellow Light: Prepare to go");
light.setState(light.getGreenLightState());
}
}
class GreenLightState implements TrafficLightState {
@Override
public void handle(TrafficLight light) {
System.out.println("Green Light: Go");
light.setState(light.getRedLightState());
}
}
// Context
class TrafficLight {
private TrafficLightState state;
private TrafficLightState redLightState;
private TrafficLightState yellowLightState;
private TrafficLightState greenLightState;
public TrafficLight() {
redLightState = new RedLightState();
yellowLightState = new YellowLightState();
greenLightState = new GreenLightState();
state = redLightState; // Initial state
}
public void setState(TrafficLightState state) {
this.state = state;
}
public TrafficLightState getRedLightState() {
return redLightState;
}
public TrafficLightState getYellowLightState() {
return yellowLightState;
}
public TrafficLightState getGreenLightState() {
return greenLightState;
}
public void pressButton() {
state.handle(this);
}
}
// Client code
public class StatePatternDemo {
public static void main(String[] args) {
TrafficLight trafficLight = new TrafficLight();
for (int i = 0; i < 6; i++) {
trafficLight.pressButton();
}
}
}
在这个例子中,TrafficLight 是上下文,它维护了一个当前状态的引用。TrafficLightState 是抽象状态接口,定义了handle方法。RedLightState、YellowLightState 和 GreenLightState 是具体状态,它们实现了TrafficLightState接口,并在handle方法中定义了状态转换的逻辑。当TrafficLight的pressButton方法被调用时,它会调用当前状态的handle方法,从而改变自身的状态。
9. 策略模式(Strategy Pattern)
策略模式(Strategy Pattern)是一种行为设计模式,它使你能在运行时改变对象的行为。此模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
核心组件
- Strategy(策略):定义一个接口,封装算法家族中的公共行为。
- Concrete Strategies(具体策略):实现策略接口,提供具体的算法实现。
- Context(上下文):维护一个对策略对象的引用,通常在构造时传入策略对象,并提供一个方法供客户端调用,该方法会委托给策略对象。
优点
- 低耦合:策略模式将算法的使用与算法的实现解耦。
- 易于扩展:可以通过增加新的具体策略类来增加行为,而无需修改原有代码。
- 遵循开放封闭原则:可以在不修改现有代码的情况下增加新策略。
缺点
- 客户端需要了解所有策略:客户端需要知道有哪些策略可用,并且需要选择合适的策略。
- 策略类增多:对于每种策略都需要一个类,可能导致类的数量增加。
适用场景
- 当一个系统应该动态地在几种算法中选择一种时。
- 当一个系统需要独立于它的算法的客户时。
- 当除算法外的其他因素相同,但算法随时间变化时。
Java示例
假设我们正在开发一个游戏,游戏中有不同的敌人,每种敌人都有自己的攻击方式。我们可以使用策略模式来实现这一需求:
// Strategy Interface
interface AttackStrategy {
void attack();
}
// Concrete Strategies
class FireAttack implements AttackStrategy {
@Override
public void attack() {
System.out.println("Attacking with fire!");
}
}
class IceAttack implements AttackStrategy {
@Override
public void attack() {
System.out.println("Attacking with ice!");
}
}
// Context
class Enemy {
private AttackStrategy strategy;
public Enemy(AttackStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(AttackStrategy strategy) {
this.strategy = strategy;
}
public void performAttack() {
strategy.attack();
}
}
// Client Code
public class StrategyPatternDemo {
public static void main(String[] args) {
Enemy enemy = new Enemy(new FireAttack()); // Create an enemy with a fire attack strategy
enemy.performAttack(); // Perform the attack
enemy.setStrategy(new IceAttack()); // Change the strategy to ice attack
enemy.performAttack(); // Perform the new attack
}
}
在这个例子中:
AttackStrategy 是策略接口,定义了攻击行为。
FireAttack 和 IceAttack 是具体策略,实现了不同的攻击方式。
Enemy 是上下文,它持有一个策略对象,并通过performAttack方法委托给策略对象执行攻击行为。
通过策略模式,我们可以在运行时改变敌人的攻击方式,而无需修改Enemy类的代码。
10. 模板方法模式(Template Method Pattern)
模板方法模式(Template Method Pattern)是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
核心组件
- AbstractClass(抽象类):定义一个模板方法,它包含一个或多个基本操作(抽象方法或具体方法),并定义了算法的骨架。
- ConcreteClass(具体类):继承自抽象类,实现抽象类中定义的基本操作。
优点
- 封装不变部分:将不变的部分封装在父类中,子类只实现变化的部分,减少了重复代码。
- 扩展性好:可以很容易地通过继承来扩展算法的某些步骤,而不需要修改已有的代码。
- 控制子类:父类可以控制子类的执行流程,限制子类对模板方法的修改。
缺点
- 模板方法的强制性:一旦定义了模板方法,子类必须实现所有抽象方法。
- 模板方法的修改困难:如果需要修改模板方法的流程,可能需要修改父类,这违反了开闭原则。
适用场景
- 当你想要定义算法的骨架,而将算法的步骤留给子类实现时。
- 当你希望在不改变算法结构的情况下,可以动态地改变算法的某些部分时。
Java示例
假设我们正在设计一个游戏,游戏中有多种类型的敌人,每种敌人的攻击方式大致相同,但具体实现有所不同。我们可以使用模板方法模式来实现这一需求:
// AbstractClass
abstract class Enemy {
// Template method
public final void attack() {
System.out.print("Enemy is preparing to attack...");
prepareAttack();
System.out.print("Enemy is performing the attack...");
performAttack();
System.out.println("Attack is over.");
}
// Basic operations
protected abstract void prepareAttack();
protected abstract void performAttack();
}
// ConcreteClass
class FireElemental extends Enemy {
@Override
protected void prepareAttack() {
System.out.print("Gathering fire energy...");
}
@Override
protected void performAttack() {
System.out.println("Flames burst out!");
}
}
class IceElemental extends Enemy {
@Override
protected void prepareAttack() {
System.out.print("Condensing ice particles...");
}
@Override
protected void performAttack() {
System.out.println("Ice shards shoot forward!");
}
}
// Client Code
public class TemplateMethodPatternDemo {
public static void main(String[] args) {
Enemy fireElemental = new FireElemental();
Enemy iceElemental = new IceElemental();
System.out.println("Fire Elemental attack:");
fireElemental.attack();
System.out.println("\nIce Elemental attack:");
iceElemental.attack();
}
}
在这个例子中:
Enemy 是抽象类,定义了attack模板方法和两个抽象的基本操作prepareAttack和performAttack。
FireElemental 和 IceElemental 是具体类,它们分别实现了prepareAttack和performAttack方法,提供了不同的攻击准备和执行方式。
通过模板方法模式,我们确保了所有敌人的攻击流程一致,同时允许每个敌人类型有其独特的攻击方式。
11. 访问者模式(Visitor Pattern)
访问者模式(Visitor Pattern)是一种行为设计模式,它允许你在不改变类结构的情况下,向一组已经存在的类中添加新的行为。这种模式利用一个访问者类,该类的方法代表了作用于某种数据结构元素上的一些操作。
核心组件
- Visitor(访问者):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作。
- ConcreteVisitor(具体访问者):实现Visitor接口,每个访问操作实现对具体元素的相应操作。
- Element(元素):定义一个接受操作以获得一个访问者对象,每个元素都可以被访问者访问。
- ConcreteElement(具体元素):实现Element接口,通常包含一个accept方法,用于接收一个访问者。
- ObjectStructure(对象结构):可以是一个集合,如列表或树形结构,它包含多个可以被访问者访问的元素。
优点
- 分离操作:访问者模式将数据结构和作用于结构上的操作分离,使得操作的增加变得容易。
- 易于增加新操作:可以通过添加新的访问者类来轻松地增加新操作,而无需修改现有元素类。
- 符合单一职责原则:每个访问者类负责一组相关的操作,使得类的职责更加明确。
缺点
- 增加对象结构的复杂性:如果对象结构经常改变,那么访问者和元素类的接口都需要修改,这会破坏封装。
- 破坏封装:访问者模式通常需要访问元素的内部结构,这可能破坏元素的封装性。
- 大量类的增加:对于每种元素和每种操作,都需要一个访问者类,这可能导致类的数目显著增加。
适用场景
- 当一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
- 当需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类。
Java示例
假设我们有一个表达式解析器,它可以解析加法和乘法表达式,我们可以使用访问者模式来实现:
// Element
interface Expression {
int interpret(Visitor visitor);
}
// ConcreteElements
class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Visitor visitor) {
return visitor.visitAddExpression(this);
}
}
class MultiplyExpression implements Expression {
private Expression left;
private Expression right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Visitor visitor) {
return visitor.visitMultiplyExpression(this);
}
}
// Visitor
interface Visitor {
int visitAddExpression(AddExpression expression);
int visitMultiplyExpression(MultiplyExpression expression);
}
// ConcreteVisitor
class ExpressionEvaluator implements Visitor {
@Override
public int visitAddExpression(AddExpression expression) {
return expression.left.interpret(this) + expression.right.interpret(this);
}
@Override
public int visitMultiplyExpression(MultiplyExpression expression) {
return expression.left.interpret(this) * expression.right.interpret(this);
}
}
// Client Code
public class VisitorPatternDemo {
public static void main(String[] args) {
Expression expr1 = new AddExpression(new MultiplyExpression(new AddExpression(new AddExpression(new MultiplyExpression(null, null), null), null), null), null);
ExpressionEvaluator evaluator = new ExpressionEvaluator();
System.out.println("Result: " + expr1.interpret(evaluator));
}
}
在这个例子中:
Expression 接口是元素接口,它定义了一个interpret方法,用于接受访问者。
AddExpression 和 MultiplyExpression 是具体元素,它们实现了Expression接口,并在interpret方法中调用访问者的方法。
Visitor 接口定义了访问者,它包含了访问不同类型元素的方法。
ExpressionEvaluator 是具体访问者,它实现了Visitor接口,并提供了具体的访问逻辑。
通过访问者模式,我们可以轻松地添加新的操作,例如添加一个新的访问者类来计算表达式的导数,而无需修改现有的元素类。