常用设计模式

使用设计模式的前提:具体需求既有稳定点,又有变化点。期望修改少量的变化点,就可以适应需求的变化。比如整齐的房间与好动的猫,设计模式就是要把猫关在笼子里。

策略模式

定义一系列算法,把它们一个一个封装起来,并且使它们可以互相替换,该模式使算法可独立于使用它们的客户程序而变化。
这里的稳定点是:客户程序与算法的调用关系。变化点是新增算法或者算法内容发生改变或者需要动态的切换算法。
例子:商场不同的促销活动。

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
// 抽象基类,具体的活动需要实现这个基类
class ProStategy {
public:
virtual double CalcPro(const Context &ctx) = 0;
virtual ~ProStategy();
};

// 接口隔离原则
class Promotion {
public:
Promotion(ProStategy *s_ = nullptr) : s(s_) {}
~Promotion() {}
void Choose(ProStategy *s_) {
if (s_ != nullptr) {
s = s_;
}
}

double CalcPromotion(const Context &ctx) {
if (s != nullptr) {
return s->CalcPro(ctx);
}
return 0.0L;
}
private:
ProStategy *s;
};

应用案例:日志容错恢复机制(通常情况下,日志是记录在数据库中,数据库可能连接不上,此时可以将日志记录在文件中,在合适的时间转录到数据库中,把日志记录在数据库和记录在文件当成两种记录日志的策略)。

观察者模式

定义对象间的一种一对多的依赖关系,这是稳定点,以便当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。
变化点为多的增加或者减少,不能影响依赖关系。
例子:气象站发布气候信息。

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
class IDisplay {
public:
virtual void Show(float temperature) = 0;
virtual ~IDisplay();
};

class DisplayA : public IDisplay {
public:
virtual void Show(float temperature) {
cout << "DisplayA Show" << endl;
}
};

class DisplayB : public IDisplay {
public:
virtual void Show(float temperature) {
cout << "DisplayB Show" << endl;
}
};

class DataCenter {
public:
void Attach(IDisplay *ob) {
obs.push(ob);
}
void Detach(IDisplay *ob) {
auto it = obs.find(ob);
if (it != obs.end()) {
obs.earase(it);
}
}
void Notify() {
float temper = CalcTemperature();
for (auto &ob : obs) {
ob->Show(temper);
}
}
private:
set<IDisplay *> obs;
};

游戏业务开发的场景:用户对象发生改变,与之相关联的系统模块(如装备系统)也要更新里面的用户信息。

责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者与接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止(可打断),这是稳定点。
客户发出一个请求,会有很多对象都可以处理这个请求,并且每个对象的处理逻辑都不一样。对于客户而言,谁处理它都无所谓,有对象处理就可以了,希望处理流程可以灵活多变,比如处理顺序,增加或减少处理流程,这就是变化点。
例子:领导审批流程。

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
class IHandler {
public:
IHandler() : next(nullptr) {}
virtual ~IHandler();
void SetNextHandler(IHandler *_next) {
next = _next;
}
bool Handle(const Context &ctx) { // 处理流程是稳定点,所以不要写成虚函数
if (CanHandle(ctx)) {
return HandleRequest(ctx);
else if (GetNextHandler()) {
return GetNextHandler()->Handle(ctx);
} else {
// err
}
return false;
}
protected:
virtual bool HandleRequest(const Context &ctx) {
return true;
}
virtual bool CanHandle(const Context &ctx) {
return true;
}
IHandle *GetNextHandler() {
return next;
}
private:
IHandler *next;
};

class HandlerByBoss : public IHandler {
protected:
virtual bool HandleRequest(const Context &ctx) {
return true;
}
virtual bool CanHandle(const Context &ctx) {
return true;
}
};

应用场景:http请求的11个阶段处理,11个阶段是通过链表关系链接的,如黑名单可以通过这个处理,中间进行打断过程。

工厂模式

简单工厂模式

简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。

工厂方法模式

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

抽象工厂模式

为了更清晰地理解工厂方法模式,需要先引入两个概念:

  • 产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
  • 产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
    当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。

单例模式

单例模式的实现需要注意以下几点:

  • 构造函数私有化,防止外部直接创建实例对象。
  • 在类内部创建唯一的实例对象,采用静态变量和静态方法实现。
  • 提供一个全局访问的静态方法,供外部程序获取该实例对象。

单例模式可以采用以下几种方式进行实现:

  • 饿汉式单例模式:在应用程序启动时即创建实例对象。
  • 懒汉式单例模式:在第一次调用时再创建实例对象。
  • 双重检查锁定单例模式:在懒汉式单例模式的基础上增加了同步锁,保证线程安全。
  • 静态内部类单例模式:使用静态内部类创建单例对象,保证懒加载和线程安全。
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
template <typename T>
class singleton
{
public:
static T &instance() // 注意返回引用
{
static T _instance;
return _instance;
}
private:
singleton() = default;
~singleton() = default;

// disallow copy and assign
singleton(const singleton &) = delete;
singleton &operator=(const singleton &) = delete;
};

// 使用new实现的饿汉模式
class Singleton {
public:
static Singleton& getInstance() {
return *instance;
}

private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

private:
static std::unique_ptr<Singleton> instance;
};

std::unique_ptr<Singleton> Singleton::instance(new Singleton());

// 懒汉模式,双重检测锁
#include <mutex>

class Singleton {
public:
static Singleton& getInstance() {
if (instance == nullptr) { // 如果两个线程同时进了这个条件
std::lock_guard<std::mutex> lockGuard(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
}
return *instance;
}

private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;

private:
static Singleton* instance;
static std::mutex mutex;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

UML类图

泛化关系

A继承自B

实现关系

聚合关系

聚合关系用于表示实体对象之间的关系,表示整体由部分构成的语义;例如一个部门由多个员工组成;
与组合关系不同的是,整体和部分不是强依赖的,即使整体不存在了,部分仍然存在;例如, 部门撤销了,人员不会消失,他们依然存在;
A聚合到B上,或者说B由A组成:

组合关系

与聚合关系一样,组合关系同样表示整体由部分构成的语义;比如公司由多个部门组成;
但组合关系是一种强依赖的特殊聚合关系,如果整体不存在了,则部分也不存在了;例如, 公司不存在了,部门也将不存在了;
A组成B,或者B由A组成:

关联关系

关联关系是用一条直线表示的;它描述不同类的对象之间的结构关系;它是一种静态关系, 通常与运行状态无关,一般由常识等因素决定的;它一般用来定义对象之间静态的、天然的结构; 所以,关联关系是一种“强关联”的关系;

比如,乘车人和车票之间就是一种关联关系;学生和学校就是一种关联关系;

关联关系默认不强调方向,表示对象间相互知道;如果特别强调方向,如下图,表示A知道B,但 B不知道A;
注:在最终代码中,关联对象通常是以成员变量的形式实现的;

依赖关系

依赖关系是用一套带箭头的虚线表示的;如下图表示A依赖于B;他描述一个对象在运行期间会用到另一个对象的关系;

与关联关系不同的是,它是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化; 依赖关系也可能发生变化;

显然,依赖也有方向,双向依赖是一种非常糟糕的结构,我们总是应该保持单向依赖,杜绝双向依赖的产生;

注:在最终代码中,依赖关系体现为类构造方法及类方法的传入参数,箭头的指向为调用关系;依赖关系除了临时知道对方外,还是“使用”对方的方法和属性;

简述设计模式的分类

  1. 创建型模式:在创建对象的同时隐藏创建逻辑,不使用 new 直接实例化对象。有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式:通过类和接口间的继承和引用实现创建复杂结构的对象。有适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式:通过类之间不同通信方式实现不同行为。有策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

简述工厂模式
工厂方法模式指定义一个创建对象的接口,让接口的实现类决定创建哪种对象,让类的实例化推迟到子类中进行。

简述模板模式
模板模式定义了一个操作中的算法的骨架,并将一些步骤延迟到子类,适用于抽取子类重复代码到公共父类。 可以封装固定不变的部分,扩展可变的部分。但每一个不同实现都需要一个子类维护,会增加类的数量。

简述代理模式
代理模式为其他对象提供一种代理以控制对这个对象的访问。优点是可以增强目标对象的功能,降低代码耦合度,扩展性好。缺点是在客户端和目标对象之间增加代理对象会导致请求处理速度变慢,增加系统复杂度。

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!