以下是一些方法和思路可以帮助你更清晰地识别使用了哪种设计模式。
1. 确定模式时的思考步骤
以下是分析代码时,你可以遵循的一些思路和步骤,帮助你识别可能使用的设计模式:
a. 识别类和对象的角色
首先,看看类和对象在系统中扮演的角色。这是识别设计模式的关键线索,因为每种设计模式都有其特定的角色。
- 职责:每个类或对象的职责是什么?是否有一些类在做某些任务时将功能分配给其他类,或在某些情况下改动自己的行为?
- 状态和行为的关系:某个类的行为是否会随着对象的状态变化而改变?
- 对象间的协作:多个类之间是如何交互的?是否存在某种统一的接口来简化多种操作?
b. 查看类之间的关系
很多设计模式的核心就在于类与类之间的关系,了解类的继承、组合和接口实现关系,有助于识别模式。
- 继承和接口:是否有类通过继承或接口实现一些功能,以便于扩展或改变行为?
- 组合关系:是否通过对象组合(而非继承)来实现灵活的功能模块?
- 工厂方法:是否有工厂类来创建对象,而不是直接在代码中new对象?
c. 是否有灵活的扩展和变化机制
许多设计模式都具有很强的扩展性,关注代码中是否有容易扩展、变化的机制:
- 开闭原则:是否有部分代码设计成可以很容易添加新功能(开闭原则),比如使用接口或抽象类来隔离变化?
- 灵活切换或变化的策略:代码是否允许在运行时或编译时改变某些行为(比如策略模式、状态模式等)?
2. 常见设计模式的识别方法
以下是一些常见设计模式的识别方法,帮助你快速分析代码中的模式:
1. 工厂方法模式(Factory Method)
- 识别方法:查看代码是否存在工厂方法或工厂类,通常会有一个方法负责创建不同类型的对象,而不是直接 new 对象。
- 例子:如果你看到一个抽象类或接口提供一个方法 createProduct(),而具体的实现类通过继承或实现该接口来决定具体创建哪种产品,那么很可能使用了工厂方法模式。
class Product {
public:
virtual void doSomething() = 0;
};
class ConcreteProductA : public Product {
public:
void doSomething() override {
std::cout << "Product A" << std::endl;
}
};
class Creator {
public:
virtual Product* factoryMethod() = 0;
};
class ConcreteCreatorA : public Creator {
public:
Product* factoryMethod() override {
return new ConcreteProductA();
}
};
2. 单例模式(Singleton)
- 识别方法:检查是否有类的构造函数被隐藏(私有),并且是否有一个静态方法来返回该类的唯一实例。如果一个类只允许存在一个实例并提供全局访问点,那么可能是单例模式。
- 例子:类中有一个私有静态成员变量 instance,并且提供一个静态方法 getInstance() 返回唯一实例。
class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (!instance) {
instance = new Singleton();
}
return instance;
}
};
3. 观察者模式(Observer)
- 识别方法:查看是否有一个主体类(被观察者),它的状态变化会通知多个观察者类。通常会看到主体类维护一个观察者列表,状态变化时调用每个观察者的更新方法。
- 例子:如果一个类通过 addObserver()、removeObserver() 来管理一组观察者,并且状态变化时通知这些观察者,那么很可能是观察者模式。
class Subject {
private:
std::vector<Observer*> observers;
public:
void addObserver(Observer* observer) {
observers.push_back(observer);
}
void notifyObservers() {
for (auto observer : observers) {
observer->update();
}
}
};
4. 策略模式(Strategy)
- 识别方法:如果你看到不同的类(策略)实现同一接口,并且上下文类(通常有一个 setStrategy() 方法)会在运行时根据不同情况切换策略,那么可能是策略模式。
- 例子:策略模式允许行为在运行时发生变化,你通常会看到一个上下文类和多个策略类(实现相同接口)。
class Strategy {
public:
virtual void execute() = 0;
};
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Strategy A" << std::endl;
}
};
class Context {
private:
Strategy* strategy;
public:
void setStrategy(Strategy* strategy) {
this->strategy = strategy;
}
void executeStrategy() {
strategy->execute();
}
};
5. 状态模式(State)
- 识别方法:观察对象的行为是否会根据其内部状态发生变化,通常会看到一个状态接口,并且有多个具体的状态类实现不同的行为。上下文类会持有一个状态对象,并根据需要改变它。
- 例子:状态模式允许对象的行为在不同的状态下变化,通常通过上下文类来委托状态的变化。
class State {
public:
virtual void handle() = 0;
};
class ConcreteStateA : public State {
public:
void handle() override {
std::cout << "State A" << std::endl;
}
};
class Context {
private:
State* state;
public:
void setState(State* state) {
this->state = state;
}
void request() {
state->handle();
}
};
6. 外观模式(Facade)
- 识别方法:如果代码中有一个外观类,它将多个子系统的功能封装在一个简单的接口中,通常这种模式下会看到一个简化的接口暴露给外部调用者。
- 例子:外观模式封装了复杂的子系统行为,通过一个外观类来提供简化的接口,避免客户端直接与多个子系统交互。
class SubsystemA {
public:
void operationA() {
std::cout << "Subsystem A operation" << std::endl;
}
};
class Facade {
private:
SubsystemA subsystemA;
public:
void simplifiedOperation() {
subsystemA.operationA();
}
};
3. 常见模式的相似点与区分
有些设计模式看起来相似,尤其是它们的目标都是封装和简化复杂系统。以下是一些常见模式的相似点与区分方法:
- 外观模式 vs. 代理模式:
- 外观模式将多个复杂子系统的接口统一化,简化对复杂系统的访问。
- 代理模式是对某个对象的控制,它通过代理对象来控制对真实对象的访问。
- 区别:外观模式主要是简化对系统的使用,而代理模式是通过控制访问来实现某种额外的功能(如懒加载、权限控制等)。
- 状态模式 vs. 策略模式:
- 状态模式和策略模式都依赖于封装变化的行为。
- 区别:状态模式侧重于根据对象的内部状态来改变行为,而策略模式侧重于通过上下文类选择不同的策略来改变行为。
- 模板方法模式 vs. 策略模式:
- 都是将某些行为提取成可以变化的部分。
- 区别:模板方法模式是通过定义算法框架并留出钩子方法供子类实现,而策略模式是通过上下文类在运行时切换不同的策略。
4. 总结
通过了解不同设计模式的意图、角色和结构,你可以在代码中识别出对应的模式。关键是关注代码的职责划分、类之间的关系、行为的变化和灵活扩展的机制。