设计模式是软件工程中经过实践总结的、针对常见问题行之有效的解决方案。它们可以帮助开发者编写出更灵活、可维护和可扩展的代码。最常见的分类方式是将设计模式分为三大类:创建型模式、结构型模式和行为型模式,这三大类共包含23种经典设计模式,通常被称为GoF(Gang of Four)设计模式。
以下是这三大类设计模式及其包含的常见模式:
1. 创建型模式 (Creational Patterns)
创建型模式主要关注对象的创建过程,旨在将对象的创建与使用分离,从而降低系统的耦合度。
- 工厂方法模式 (Factory Method Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂模式 (Abstract Factory Pattern):提供一个接口,用于创建相关或依赖对象的家族,而无需明确指定具体类。
- 单例模式 (Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。
- 建造者模式 (Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式 (Prototype Pattern):通过复制现有对象来创建新对象,而不是通过实例化。
2. 结构型模式 (Structural Patterns)
结构型模式关注如何将类或对象组合成更大的结构,以实现新的功能。
- 适配器模式 (Adapter Pattern):将一个类的接口转换成客户端所期望的另一个接口,使原本不兼容的类可以一起工作。
- 装饰器模式 (Decorator Pattern):动态地给一个对象添加一些额外的职责,而不会影响其他对象。
- 代理模式 (Proxy Pattern):为另一个对象提供一个替身或占位符来控制对这个对象的访问。
- 外观模式 (Facade Pattern):为子系统中的一组接口提供一个统一的接口,简化了复杂子系统的使用。
- 桥接模式 (Bridge Pattern):将抽象与实现分离,使它们可以独立变化。
- 组合模式 (Composite Pattern):将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
- 享元模式 (Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。
3. 行为型模式 (Behavioral Patterns)
行为型模式关注对象之间的职责分配和交互方式。
- 策略模式 (Strategy Pattern):定义一系列算法,将它们封装起来,并使它们可以相互替换。
- 模板方法模式 (Template Method Pattern):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。
- 观察者模式 (Observer Pattern):定义对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。
- 迭代器模式 (Iterator Pattern):提供一种方法顺序访问一个聚合对象中的各个元素,而又无需暴露该对象的内部表示。
- 责任链模式 (Chain of Responsibility Pattern):将请求沿着一条责任链传递,直到有一个对象处理它为止。
- 命令模式 (Command Pattern):将一个请求封装为一个对象,从而使你可用不同的请求、队列或日志来参数化客户端。
- 备忘录模式 (Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 状态模式 (State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- 访问者模式 (Visitor Pattern):表示一个作用于某对象结构中的各元素的操作。
- 中介者模式 (Mediator Pattern):用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用。
- 解释器模式 (Interpreter Pattern):给定一个语言,定义它的文法表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
这些设计模式在实际开发中被广泛应用,例如Spring框架中就大量使用了工厂模式、单例模式和代理模式等。
创建型模式
我们来详细解释一下"创建型模式 (Creational Patterns)"这个概念。
核心思想:把"怎么造东西"和"怎么用东西"分开。
想象一下你正在建造一个乐高模型。
- "怎么造东西":这指的是乐高积木的生产过程。工厂里有模具,有原材料,有生产线,最终生产出各种形状、颜色的积木。你不需要知道这些积木是怎么被制造出来的,你只需要知道它们是积木就行。
- "怎么用东西":这指的是你拿到这些积木后,如何把它们拼装起来,搭建成你想要的模型。你只关心积木的功能和形状,不关心它们在工厂里的生产细节。
在软件开发中,"东西"就是"对象" (比如一个User对象,一个DatabaseConnection对象,一个Logger对象等)。
1. "怎么造东西" (对象的创建过程)
当我们说"对象的创建过程",通常是指:
- 使用
new关键字实例化一个类(例如new User())。 - 初始化这个对象(给对象的属性赋值)。
- 可能还需要进行一些复杂的配置或依赖注入。
2. "怎么用东西" (对象的使用)
当我们说"对象的使用",通常是指:
- 调用对象的方法(例如
user.save())。 - 访问对象的属性(例如
user.getName())。 - 将对象作为参数传递给其他方法。
为什么要把它们分开?
这就是"降低系统的耦合度"的关键。
- 耦合度 (Coupling):简单来说,就是两个或多个模块(或代码片段)之间的依赖程度。如果一个模块A直接依赖于模块B的内部实现细节,那么我们就说A和B的耦合度很高。当B的内部实现发生变化时,A也可能需要修改。
- 降低耦合度的好处 :
- 灵活性:如果创建对象的方式改变了(比如原来从数据库加载用户,现在从缓存加载),使用对象的代码不需要修改。
- 可维护性:创建逻辑集中在一个地方,更容易管理和修改。
- 可扩展性:如果需要创建新的对象类型,只需修改创建逻辑,使用对象的代码仍然保持不变。
- 可测试性:在测试使用对象的代码时,可以更容易地替换掉真实的创建过程,使用模拟对象(Mock Object)进行测试。
举个例子:
假设你正在开发一个游戏,需要创建不同类型的"敌人"对象(比如Goblin、Orc、Dragon)。
没有创建型模式(高耦合):
java
// 游戏主逻辑
public class Game {
public void startGame() {
// 直接在游戏逻辑中创建敌人
Goblin goblin1 = new Goblin(); // 游戏逻辑直接依赖于Goblin这个具体类
goblin1.attack();
Orc orc1 = new Orc(); // 游戏逻辑直接依赖于Orc这个具体类
orc1.move();
// 如果以后要加新的敌人类型,或者改变现有敌人的创建方式,
// 游戏主逻辑都需要修改。
}
}
在这个例子中,Game类直接知道并负责创建Goblin和Orc对象。Game类和Goblin、Orc这两个具体类之间是紧密耦合的。
使用创建型模式(例如工厂方法模式,降低耦合):
java
// 敌人接口
interface Enemy {
void attack();
void move();
}
// 具体敌人实现
class Goblin implements Enemy {
public void attack() { System.out.println("Goblin attacks!"); }
public void move() { System.out.println("Goblin moves!"); }
}
class Orc implements Enemy {
public void attack() { System.out.println("Orc attacks!"); }
public void move() { System.out.println("Orc moves!"); }
}
// 敌人工厂接口
interface EnemyFactory {
Enemy createEnemy();
}
// 具体哥布林工厂
class GoblinFactory implements EnemyFactory {
public Enemy createEnemy() {
return new Goblin(); // 工厂负责创建具体对象
}
}
// 具体兽人工厂
class OrcFactory implements EnemyFactory {
public Enemy createEnemy() {
return new Orc(); // 工厂负责创建具体对象
}
}
// 游戏主逻辑
public class Game {
// 游戏逻辑现在只需要一个工厂,而不需要知道具体敌人的类
public void startGame(EnemyFactory factory) {
Enemy enemy = factory.createEnemy(); // 游戏逻辑只知道要一个"敌人",不知道具体是哪个
enemy.attack();
enemy.move();
// 如果以后要创建新的敌人类型(比如Dragon),
// 只需要提供一个新的DragonFactory,游戏主逻辑startGame方法不需要修改。
// 游戏逻辑只依赖于Enemy接口和EnemyFactory接口,而不是具体的Goblin或Orc类。
}
public static void main(String[] args) {
Game game = new Game();
System.out.println("Playing with Goblin:");
game.startGame(new GoblinFactory()); // 传入哥布林工厂
System.out.println("\nPlaying with Orc:");
game.startGame(new OrcFactory()); // 传入兽人工厂
}
}
在这个改进的例子中:
- "怎么造东西" 的逻辑被封装到了
GoblinFactory和OrcFactory中。它们知道如何创建具体的Goblin或Orc对象。 - "怎么用东西" 的逻辑(
Game类中的startGame方法)现在只依赖于EnemyFactory接口和Enemy接口。它只知道它需要一个Enemy对象,并且可以通过factory.createEnemy()来获取,但它不知道也不关心 这个Enemy具体是Goblin还是Orc,也不知道它是如何被创建出来的。
这样,Game类和具体的敌人类型之间就没有直接的依赖了,耦合度大大降低。这就是创建型模式的核心价值。