admin管理员组文章数量:1446760
我常用的六种设计模式
本文来源于读者朋友提问,期望出一篇设计模式相关的文章。
关于设计模式,我看了很多书籍和视频,但由于部分设计模式并不常用,所以也难免有些遗忘。后来索性就用到哪个时再深入学习哪个。
那我是怎么学习设计模式的呢,我现在会花更多的时间和精力了解设计原则,毕竟李建忠老师曾讲过:了解设计原则远比知道23种设计模式更重要。
本文将在介绍设计原则的基础上,抛砖引玉,简述我常用的六种设计模式。
设计原则
设计原则是架构设计的指导准则,也是我们学习设计模式的基础。八大设计原则如下:
- 单一职责原则:一个类只做一件事。每个类需要明确的职责划分,如果一个类承担的职责过多,需要思考下,其是否可以继续拆分。
- 开闭原则:对扩展开放,对修改关闭。该原则是指不要修改现有代码,可以增加新代码来扩展功能。
- 里氏替换原则:子类可以替换父类,但不影响程序的正确性。
- 依赖倒置原则:应该依赖抽象而不是具体类。这个我在实际使用较多,通过提供虚基类,其他类依赖虚基类,而不是依赖某个具体的实现类。
- 接口隔离原则:接口要小而专,避免大而全。其要求接口应该小而完备,多用private,少用public。
- 合成复用原则:优先使用组合而不是继承。继承在某种程度上会破坏封装性,而组合则不会,组合只要求被组合对象具备完备的接口,这样就可以通过接口来约束,从而可以保证封装性。
- 封装变化点:使用封装来创建对象间的分解层,让设计者可以在一侧进行修改,而不会影响到另一侧。从而实现层次间的松耦合。
- 针对接口编程:面向接口编程,而不是面向实现编程。客户程序无需知道对象的具体类型,只需要知道对象所具有的接口。
常用的设计模式
接下来抛砖引玉,简单介绍下我常用的几种设计模式。
单例模式
单例模式保证一个类只有一个实例的特性,适用于日志、公共数据等全局性质的对象。
适用场景:
- 需要频繁实例化的类,如数据库连接池。
- 资源管理器,控制资源的访问(如数据库连接)。
优点:
- 提供了对唯一实例的全局访问点。
- 实现了唯一实例的控制。
缺点:
- 违反了单一职责原则(既要负责该类的具体动作,还要负责对象的创建)。
- 单例模式的泛滥可能导致系统难以测试和维护。尤其是在两个单例相互依赖的场景中。
代码示例:
代码语言:javascript代码运行次数:0运行复制class Singleton {
private:
static Singleton* instance;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
Singleton* Singleton::instance = nullptr;
工厂模式
工厂模式用于创建对象,尤其是同一类型对象的创建多使用工厂模式,如图形类(下含各种类型的图形)、数据库类(下含各种数据类型的数据库)等。
适用场景:
- 创建对象需要大量重复代码。
- 创建多个对象时,这些对象属于一个产品族或产品等级结构。
优点:
- 将创建代码集中到一个地方,便于维护。
- 使得系统在创建对象时更加灵活。
缺点:
- 违反了开闭原则,添加新产品需要修改现有代码。
代码示例:
代码语言:javascript代码运行次数:0运行复制class IProduct {
public:
virtual void use() = 0;
};
class ConcreteProductA :public IProduct {
public:
void use() override {
std::cout << "Using Product A" << std::endl;
}
};
class ConcreteProductB :public IProduct {
public:
void use() override {
std::cout << "Using Product B" << std::endl;
}
};
class Factory {
public:
static IProduct* createProduct(int type) {
if (type == 1) {
returnnew ConcreteProductA();
} elseif (type == 2) {
returnnew ConcreteProductB();
}
returnnullptr;
}
};
策略模式
策略模式用于定义支持不同行为的相同方法,使得方法可以在不改变类的情况下改变行为。最常见的就是普通会员、VIP会员、SVIP会员的计费方法,我们可以使用策略模式来实现。
适用场景:
- 一个类定义了多种行为,并且这些行为在运行时需要动态切换。
- 需要使用不同的算法变体。
优点:
- 策略对象提供了封装和交换不同算法的能力。
- 使算法可以独立于使用它的客户而变化。
缺点:
- 客户必须了解不同的策略。
- 初始代码比简单的方法调用更长。
代码示例:
代码语言:javascript代码运行次数:0运行复制class IStrategy {
public:
virtual double calculatePrice(double originalPrice) = 0;
};
class NormalStrategy :public IStrategy {
public:
double calculatePrice(double originalPrice) override {
return originalPrice;
}
};
class VIPStrategy :public IStrategy {
public:
double calculatePrice(double originalPrice) override {
return originalPrice * 0.8;
}
};
class SVIPStrategy :public IStrategy {
public:
double calculatePrice(double originalPrice) override {
return originalPrice * 0.5;
}
};
class Context {
private:
IStrategy* strategy;
public:
Context(IStrategy* s) : strategy(s) {}
double executeStrategy(double originalPrice) {
return strategy->calculatePrice(originalPrice);
}
};
模板模式
模板模式定义了一个算法的骨架,而将一些步骤延迟到子类中。比如一个扬声器的实现类来讲,出声音具备相同的流程:初始化设备、播放。但是不同的扬声器的初始化设备的方法不同,我们可以使用模板模式来实现。
适用场景:
- 一次性实现一个算法的不变部分,并将可变行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中在一个公共父类中以避免代码重复。
优点:
- 封装不变部分,扩展可变部分。
- 提取公共代码,便于维护。
缺点:
- 父类中的代码可能难以理解。
- 代码的重构需要花费一定的时间和精力。
代码示例:
代码语言:javascript代码运行次数:0运行复制class AbstractSpeaker {
public:
void play() {
initializeDevice();
openDevice();
doPlay();
closeDevice();
}
protected:
virtual void initializeDevice() = 0;
virtual void openDevice() {
std::cout << "Opening device" << std::endl;
}
virtual void doPlay() {
std::cout << "Playing" << std::endl;
}
virtual void closeDevice() {
std::cout << "Closing device" << std::endl;
}
};
class SpeakerA :public AbstractSpeaker {
protected:
void initializeDevice() override {
std::cout << "Initializing Speaker A" << std::endl;
}
};
class SpeakerB :public AbstractSpeaker {
protected:
void initializeDevice() override {
std::cout << "Initializing Speaker B" << std::endl;
}
};
观察者模式
观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
适用场景:
- 一个抽象模型有两个方面,其中一个是依赖于另一个的。
- 一个对象需要通知其他对象,而它又不知道这些对象是谁。
优点:
- 实现了主题和观察者之间的抽象耦合。
- 支持广播通信。
缺点:
- 如果观察者数量很多,可能会导致效率问题。
- 如果主题和观察者之间的依赖关系复杂,可能会导致系统难以维护。
代码示例:
代码语言:javascript代码运行次数:0运行复制class IObserver {
public:
virtual void update() = 0;
};
class ISubject {
public:
virtual void attach(IObserver* observer) = 0;
virtual void detach(IObserver* observer) = 0;
virtual void notify() = 0;
};
class ConcreteSubject :public ISubject {
private:
std::vector<IObserver*> observers;
int state;
public:
void attach(IObserver* observer) override {
observers.push_back(observer);
}
void detach(IObserver* observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notify() override {
for (IObserver* observer : observers) {
observer->update();
}
}
void setState(int newState) {
state = newState;
notify();
}
};
class ConcreteObserver :public IObserver {
private:
ConcreteSubject* subject;
public:
ConcreteObserver(ConcreteSubject* subj) : subject(subj) {}
void update() override {
std::cout << "Observer updated: " << subject->getState() << std::endl;
}
};
适配器模式
适配器模式分为类适配器和对象适配器,主要用于接口的匹配。
适用场景:
- 想要使用一个已经存在的类,但其接口不符合你的需求。
- 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
优点:
- 提高了代码的可复用性。
- 让那些由于接口不兼容而不能一起工作的那些类可以一起工作。
缺点:
- 过多使用适配器,会让系统变得复杂,不易维护。
代码示例:
代码语言:javascript代码运行次数:0运行复制class ITarget {
public:
virtual ~ITarget() {}
virtual void request() = 0;
};
class Adaptee {
public:
void specificRequest() {
std::cout << "Adaptee specific request" << std::endl;
}
};
class Adapter :public ITarget {
private:
Adaptee* adaptee;
public:
Adapter(Adaptee* adaptee) : adaptee(adaptee) {}
void request() override {
adaptee->specificRequest();
}
};
总结
本文所述只是我常用的几种设计模式,也仅仅是抛砖引玉。但是设计原则是值得我们深入理解,并值得在工程中加以运用,熟能生巧。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-15,如有侵权请联系 cloudcommunity@tencent 删除算法对象接口设计设计模式本文标签: 我常用的六种设计模式
版权声明:本文标题:我常用的六种设计模式 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/biancheng/1748251432a2832454.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论