9、设计模式
设计模式
1、工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时,建议直接new完成一个实例对象的创建。
1.1、简单工厂模式
主要特点是需要在工厂类中做判断,从而创造相应的产品,当增加新产品时,需要修改工厂类。使用简单工厂模式,我们只需要知道具体的产品型号就可以创建一个产品。
缺点:工厂类集中了所有产品类的创建逻辑,如果产品量较大,会使得工厂类变的非常臃肿。
1.2、工厂方法模式
定义一个创建对象的接口,其子类去具体现实这个接口以完成具体的创建工作。如果需要增加新的产品类,只需要扩展一个相应的工厂类即可。
缺点:产品类数据较多时,需要实现大量的工厂类,这无疑增加了代码量。
1 /*2 关键代码:创建过程在其子类执行。3 */4 5 #include <iostream>6 7 using namespace std;8 9 //产品抽象类10 class Tank11 {12 public:13 virtual const string& type() = 0;14 };15 16 //具体的产品类17 class Tank56 : public Tank18 {19 public:20 Tank56():Tank(),m_strType("Tank56")21 {22 }23 24 const string& type() override25 {26 cout << m_strType.data() << endl;27 return m_strType;28 }29 private:30 string m_strType;31 };32 33 //具体的产品类34 class Tank96 : public Tank35 {36 public:37 Tank96():Tank(),m_strType("Tank96")38 {39 }40 const string& type() override41 {42 cout << m_strType.data() << endl;43 return m_strType;44 }45 46 private:47 string m_strType;48 }; 49 50 //抽象工厂类,提供一个创建接口51 class TankFactory52 {53 public:54 //提供创建产品实例的接口,返回抽象产品类55 virtual Tank* createTank() = 0;56 };57 58 //具体的创建工厂类,使用抽象工厂类提供的接口,去创建具体的产品实例59 class Tank56Factory : public TankFactory60 {61 public:62 Tank* createTank() override63 {64 return new Tank56();65 }66 };67 68 //具体的创建工厂类,使用抽象工厂类提供的接口,去创建具体的产品实例69 class Tank96Factory : public TankFactory70 {71 public:72 Tank* createTank() override73 {74 return new Tank96();75 }76 };77 78 79 int main()80 {81 TankFactory* factory56 = new Tank56Factory();82 Tank* tank56 = factory56->createTank();83 tank56->type();84 85 TankFactory* factory96 = new Tank96Factory();86 Tank* tank96 = factory96->createTank();87 tank96->type();88 89 delete tank96;90 tank96 = nullptr;91 delete factory96;92 factory96 = nullptr;93 94 delete tank56;95 tank56 = nullptr;96 delete factory56;97 factory56 = nullptr;98 99 return 0;
100 }
1.3、抽象工厂模式
抽象工厂模式提供创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
当存在多个产品系列,而客户端只使用一个系列的产品时,可以考虑使用抽象工厂模式。
缺点:当增加一个新系列的产品时,不仅需要现实具体的产品类,还需要增加一个新的创建接口,扩展相对困难。
1 /*2 * 关键代码:在一个工厂里聚合多个同类产品。3 * 以下代码以白色衣服和黑色衣服为例,白色衣服为一个产品系列,黑色衣服为一个产品系列。白色上衣搭配白色裤子, 黑色上衣搭配黑色裤字。每个系列的衣服由一个对应的工厂创建,这样一个工厂创建的衣服能保证衣服为同一个系列。4 */5 6 //抽象上衣类7 class Coat8 {9 public:10 virtual const string& color() = 0;11 };12 13 //黑色上衣类14 class BlackCoat : public Coat15 {16 public:17 BlackCoat():Coat(),m_strColor("Black Coat")18 {19 }20 21 const string& color() override22 {23 cout << m_strColor.data() << endl;24 return m_strColor;25 }26 private:27 string m_strColor;28 };29 30 //白色上衣类31 class WhiteCoat : public Coat32 {33 public:34 WhiteCoat():Coat(),m_strColor("White Coat")35 {36 }37 const string& color() override38 {39 cout << m_strColor.data() << endl;40 return m_strColor;41 }42 43 private:44 string m_strColor;45 }; 46 47 //抽象裤子类48 class Pants49 {50 public:51 virtual const string& color() = 0;52 };53 54 //黑色裤子类55 class BlackPants : public Pants56 {57 public:58 BlackPants():Pants(),m_strColor("Black Pants")59 {60 }61 const string& color() override62 {63 cout << m_strColor.data() << endl;64 return m_strColor;65 }66 67 private:68 string m_strColor;69 };70 71 //白色裤子类72 class WhitePants : public Pants73 {74 public:75 WhitePants():Pants(),m_strColor("White Pants")76 {77 }78 const string& color() override79 {80 cout << m_strColor.data() << endl;81 return m_strColor;82 }83 84 private:85 string m_strColor;86 };87 88 //抽象工厂类,提供衣服创建接口89 class Factory90 {91 public:92 //上衣创建接口,返回抽象上衣类93 virtual Coat* createCoat() = 0;94 //裤子创建接口,返回抽象裤子类95 virtual Pants* createPants() = 0;96 };97 98 //创建白色衣服的工厂类,具体实现创建白色上衣和白色裤子的接口99 class WhiteFactory : public Factory
100 {
101 public:
102 Coat* createCoat() override
103 {
104 return new WhiteCoat();
105 }
106
107 Pants* createPants() override
108 {
109 return new WhitePants();
110 }
111 };
112
113 //创建黑色衣服的工厂类,具体实现创建黑色上衣和白色裤子的接口
114 class BlackFactory : public Factory
115 {
116 Coat* createCoat() override
117 {
118 return new BlackCoat();
119 }
120
121 Pants* createPants() override
122 {
123 return new BlackPants();
124 }
125 };
2、策略模式
策略模式是指定义一系列的算法,把它们单独封装起来,并且使它们可以互相替换,使得算法可以独立于使用它的客户端而变化,也是说这些算法所完成的功能类型是一样的,对外接口也是一样的,只是不同的策略为引起环境角色环境角色表现出不同的行为。
相比于使用大量的if...else,使用策略模式可以降低复杂度,使得代码更容易维护。
缺点:可能需要定义大量的策略类,并且这些策略类都要提供给客户端。
2.1、传统的策略模式实现
1 /*2 * 关键代码:实现同一个接口。3 * 以下代码实例中,以游戏角色不同的攻击方式为不同的策略,游戏角色即为执行不同策略的环境角色。4 */5 6 #include <iostream>7 8 using namespace std;9 10 //抽象策略类,提供一个接口11 class Hurt12 {13 public:14 virtual void blood() = 0;15 };16 17 //具体的策略实现类,具体实现接口, Adc持续普通攻击18 class AdcHurt : public Hurt19 {20 public:21 void blood() override22 {23 cout << "Adc hurt, Blood loss" << endl;24 }25 };26 27 //具体的策略实现类,具体实现接口, Apc技能攻击28 class ApcHurt : public Hurt29 {30 public:31 void blood() override32 {33 cout << "Apc Hurt, Blood loss" << endl;34 }35 };36 37 //环境角色类, 游戏角色战士,传入一个策略类指针参数。38 class Soldier39 {40 public:41 Soldier(Hurt* hurt):m_pHurt(hurt)42 {43 }44 //在不同的策略下,该游戏角色表现出不同的攻击45 void attack()46 {47 m_pHurt->blood();48 }49 private:50 Hurt* m_pHurt;51 };52 53 //定义策略标签54 typedef enum55 {56 Hurt_Type_Adc,57 Hurt_Type_Apc,58 Hurt_Type_Num59 }HurtType;60 61 //环境角色类, 游戏角色法师,传入一个策略标签参数。62 class Mage63 {64 public:65 Mage(HurtType type)66 {67 switch(type)68 {69 case Hurt_Type_Adc:70 m_pHurt = new AdcHurt();71 break;72 case Hurt_Type_Apc:73 m_pHurt = new ApcHurt();74 break;75 default:76 break;77 }78 }79 ~Mage()80 {81 delete m_pHurt;82 m_pHurt = nullptr;83 cout << "~Mage()" << endl;84 }85 86 void attack()87 {88 m_pHurt->blood();89 }90 private:91 Hurt* m_pHurt;92 };93 94 //环境角色类, 游戏角色弓箭手,实现模板传递策略。95 template<typename T>96 class Archer97 {98 public:99 void attack()
100 {
101 m_hurt.blood();
102 }
103 private:
104 T m_hurt;
105 };
106
107 int main()
108 {
109 Archer<ApcHurt>* arc = new Archer<ApcHurt>;
110 arc->attack();
111
112 delete arc;
113 arc = nullptr;
114
115 return 0;
116 }
2.2、使用函数指针实现策略模式
1 #include <iostream>2 #include <functional> 3 4 void adcHurt()5 {6 std::cout << "Adc Hurt" << std::endl;7 }8 9 void apcHurt()
10 {
11 std::cout << "Apc Hurt" << std::endl;
12 }
13
14 //环境角色类, 使用传统的函数指针
15 class Soldier
16 {
17 public:
18 typedef void (*Function)();
19 Soldier(Function fun): m_fun(fun)
20 {
21 }
22 void attack()
23 {
24 m_fun();
25 }
26 private:
27 Function m_fun;
28 };
29
30 //环境角色类, 使用std::function<>
31 class Mage
32 {
33 public:
34 typedef std::function<void()> Function;
35
36 Mage(Function fun): m_fun(fun)
37 {
38 }
39 void attack()
40 {
41 m_fun();
42 }
43 private:
44 Function m_fun;
45 };
46
47 int main()
48 {
49 Soldier* soldier = new Soldier(apcHurt);
50 soldier->attack();
51 delete soldier;
52 soldier = nullptr;
53 return 0;
54 }
3、适配器模式
适配器模式可以将一个类的接口转换成客户端希望的另一个接口,使得原来由于接口不兼容而不能在一起工作的那些类可以在一起工作。通俗的讲就是当我们已经有了一些类,而这些类不能满足新的需求,此时就可以考虑是否能将现有的类适配成可以满足新需求的类。适配器类需要继承或依赖已有的类,实现想要的目标接口。
缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
3.1、使用复合实现适配器模式
1 /*2 * 关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。3 * 以下示例中,假设我们之前有了一个双端队列,新的需求要求使用栈和队列来完成。4 双端队列可以在头尾删减或增加元素。而栈是一种先进后出的数据结构,添加数据时添加到栈的顶部,删除数据时先删 除栈顶部的数据。因此我们完全可以将一个现有的双端队列适配成一个栈。5 */6 7 //双端队列, 被适配类8 class Deque9 {
10 public:
11 void push_back(int x)
12 {
13 cout << "Deque push_back:" << x << endl;
14 }
15 void push_front(int x)
16 {
17 cout << "Deque push_front:" << x << endl;
18 }
19 void pop_back()
20 {
21 cout << "Deque pop_back" << endl;
22 }
23 void pop_front()
24 {
25 cout << "Deque pop_front" << endl;
26 }
27 };
28
29 //顺序类,抽象目标类
30 class Sequence
31 {
32 public:
33 virtual void push(int x) = 0;
34 virtual void pop() = 0;
35 };
36
37 //栈,后进先出, 适配类
38 class Stack:public Sequence
39 {
40 public:
41 //将元素添加到堆栈的顶部。
42 void push(int x) override
43 {
44 m_deque.push_front(x);
45 }
46 //从堆栈中删除顶部元素
47 void pop() override
48 {
49 m_deque.pop_front();
50 }
51 private:
52 Deque m_deque;
53 };
54
55 //队列,先进先出,适配类
56 class Queue:public Sequence
57 {
58 public:
59 //将元素添加到队列尾部
60 void push(int x) override
61 {
62 m_deque.push_back(x);
63 }
64 //从队列中删除顶部元素
65 void pop() override
66 {
67 m_deque.pop_front();
68 }
69 private:
70 Deque m_deque;
71 };
3.2、使用继承实现适配器模式
1 //双端队列,被适配类2 class Deque 3 {4 public:5 void push_back(int x)6 {7 cout << "Deque push_back:" << x << endl;8 }9 void push_front(int x)
10 {
11 cout << "Deque push_front:" << x << endl;
12 }
13 void pop_back()
14 {
15 cout << "Deque pop_back" << endl;
16 }
17 void pop_front()
18 {
19 cout << "Deque pop_front" << endl;
20 }
21 };
22
23 //顺序类,抽象目标类
24 class Sequence
25 {
26 public:
27 virtual void push(int x) = 0;
28 virtual void pop() = 0;
29 };
30
31 //栈,后进先出, 适配类
32 class Stack:public Sequence, private Deque
33 {
34 public:
35 void push(int x)
36 {
37 push_front(x);
38 }
39 void pop()
40 {
41 pop_front();
42 }
43 };
44
45 //队列,先进先出,适配类
46 class Queue:public Sequence, private Deque
47 {
48 public:
49 void push(int x)
50 {
51 push_back(x);
52 }
53 void pop()
54 {
55 pop_front();
56 }
57 };
4、单例模式
单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。实现单例模式必须注意一下几点:
-
单例类只能由一个实例化对象。
-
单例类必须自己提供一个实例化对象。
-
单例类必须提供一个可以访问唯一实例化对象的接口。
单例模式分为懒汉和饿汉两种实现方式。
4.1、懒汉单例模式
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化一个对象。在访问量较小,甚至可能不会去访问的情况下,采用懒汉实现,这是以时间换空间。
4.1.1、非线程安全的懒汉单例模式
1 /*2 * 关键代码:构造函数是私有的,不能通过赋值运算,拷贝构造等方式实例化对象。3 */4 5 //懒汉式一般实现:非线程安全,getInstance返回的实例指针需要delete6 class Singleton7 {8 public:9 static Singleton* getInstance();
10 ~Singleton(){}
11
12 private:
13 Singleton(){} //构造函数私有
14 Singleton(const Singleton& obj) = delete; //明确拒绝
15 Singleton& operator=(const Singleton& obj) = delete; //明确拒绝
16
17 static Singleton* m_pSingleton;
18 };
19
20 Singleton* Singleton::m_pSingleton = NULL;
21
22 Singleton* Singleton::getInstance()
23 {
24 if(m_pSingleton == NULL)
25 {
26 m_pSingleton = new Singleton;
27 }
28 return m_pSingleton;
29 }
4.1.2、线程安全的懒汉单例模式
1 std::mutex mt;2 3 class Singleton4 {5 public:6 static Singleton* getInstance();7 private:8 Singleton(){} //构造函数私有9 Singleton(const Singleton&) = delete; //明确拒绝
10 Singleton& operator=(const Singleton&) = delete; //明确拒绝
11
12 static Singleton* m_pSingleton;
13
14 };
15 Singleton* Singleton::m_pSingleton = NULL;
16
17 Singleton* Singleton::getInstance()
18 {
19 if(m_pSingleton == NULL)
20 {
21 mt.lock();
22 if(m_pSingleton == NULL)
23 {
24 m_pSingleton = new Singleton();
25 }
26 mt.unlock();
27 }
28 return m_pSingleton;
29 }
4.1.3、返回一个reference指向local static对象
这种单例模式实现方式多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。这种实现方式的好处是不需要去delete它。
1 class Singleton2 {3 public:4 static Singleton& getInstance();5 private:6 Singleton(){}7 Singleton(const Singleton&) = delete; //明确拒绝8 Singleton& operator=(const Singleton&) = delete; //明确拒绝9 };
10
11
12 Singleton& Singleton::getInstance()
13 {
14 static Singleton singleton;
15 return singleton;
16 }
4.2、饿汉单例模式
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
1 //饿汉式:线程安全,注意一定要在合适的地方去delete它2 class Singleton3 {4 public:5 static Singleton* getInstance();6 private:7 Singleton(){} //构造函数私有8 Singleton(const Singleton&) = delete; //明确拒绝9 Singleton& operator=(const Singleton&) = delete; //明确拒绝
10
11 static Singleton* m_pSingleton;
12 };
13
14 Singleton* Singleton::m_pSingleton = new Singleton();
15
16 Singleton* Singleton::getInstance()
17 {
18 return m_pSingleton;
19 }
5、原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。通俗的讲就是当需要创建一个新的实例化对象时,我们刚好有一个实例化对象,但是已经存在的实例化对象又不能直接使用。这种情况下拷贝一个现有的实例化对象来用,可能会更方便。
以下情形可以考虑使用原型模式:
-
当new一个对象,非常繁琐复杂时,可以使用原型模式来进行复制一个对象。比如创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
-
当需要new一个新的对象,这个对象和现有的对象区别不大,我们就可以直接复制一个已有的对象,然后稍加修改。
-
当需要一个对象副本时,比如需要提供对象的数据,同时又需要避免外部对数据对象进行修改,那就拷贝一个对象副本供外部使用。
1 /*2 * 关键代码:拷贝,return new className(*this);3 */4 #include <iostream>5 6 using namespace std;7 8 //提供一个抽象克隆基类。9 class Clone
10 {
11 public:
12 virtual Clone* clone() = 0;
13 virtual void show() = 0;
14 };
15
16 //具体的实现类
17 class Sheep:public Clone
18 {
19 public:
20 Sheep(int id, string name):Clone(),
21 m_id(id),m_name(name)
22 {
23 cout << "Sheep() id address:" << &m_id << endl;
24 cout << "Sheep() name address:" << &m_name << endl;
25 }
26 ~Sheep()
27 {
28 }
29 //关键代码拷贝构造函数
30 Sheep(const Sheep& obj)
31 {
32 this->m_id = obj.m_id;
33 this->m_name = obj.m_name;
34 cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl;
35 cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl;
36 }
37 //关键代码克隆函数,返回return new Sheep(*this)
38 Clone* clone()
39 {
40 return new Sheep(*this);
41 }
42 void show()
43 {
44 cout << "id :" << m_id << endl;
45 cout << "name:" << m_name.data() << endl;
46 }
47 private:
48 int m_id;
49 string m_name;
50 };
51
52 int main()
53 {
54 Clone* s1 = new Sheep(1, "abs");
55 s1->show();
56 Clone* s2 = s1->clone();
57 s2->show();
58
59 delete s1;
60 s1 = nullptr;
61 delete s2;
62 s2 = nullptr;
63 return 0;
64 }
6、模板模式
模板模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
当多个类有相同的方法,并且逻辑相同,只是细节上有差异时,可以考虑使用模板模式。具体的实现上可以将相同的核心算法设计为模板方法,具体的实现细节有子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
以生产电脑为例,电脑生产的过程都是一样的,只是一些装配的器件可能不同而已。
1 /*2 * 关键代码:在抽象类实现通用接口,细节变化在子类实现。3 */4 5 #include <iostream>6 7 using namespace std;8 9 class Computer
10 {
11 public:
12 void product()
13 {
14 installCpu();
15 installRam();
16 installGraphicsCard();
17 }
18
19 protected:
20 virtual void installCpu() = 0;
21 virtual void installRam() = 0;
22 virtual void installGraphicsCard() = 0;
23
24 };
25
26 class ComputerA : public Computer
27 {
28 protected:
29 void installCpu() override
30 {
31 cout << "ComputerA install Inter Core i5" << endl;
32 }
33
34 void installRam() override
35 {
36 cout << "ComputerA install 2G Ram" << endl;
37 }
38
39 void installGraphicsCard() override
40 {
41 cout << "ComputerA install Gtx940 GraphicsCard" << endl;
42 }
43 };
44
45 class ComputerB : public Computer
46 {
47 protected:
48 void installCpu() override
49 {
50 cout << "ComputerB install Inter Core i7" << endl;
51 }
52
53 void installRam() override
54 {
55 cout << "ComputerB install 4G Ram" << endl;
56 }
57
58 void installGraphicsCard() override
59 {
60 cout << "ComputerB install Gtx960 GraphicsCard" << endl;
61 }
62 };
63
64 int main()
65 {
66 ComputerB* c1 = new ComputerB();
67 c1->product();
68
69 delete c1;
70 c1 = nullptr;
71
72 return 0;
73 }
7、建造者模式
建造者模式:将复杂对象的构建和其表示分离,使得相同的构建过程可以产生不同的表示。
以下情形可以考虑使用建造者模式:
-
对象的创建复杂,但是其各个部分的子对象创建算法一定。
-
需求变化大,构造复杂对象的子对象经常变化,但将其组合在一起的算法相对稳定。
建造者模式的优点:
-
将对象的创建和表示分离,客户端不需要了解具体的构建细节。
-
增加新的产品对象时,只需要增加其具体的建造类即可,不需要修改原来的代码,扩展方便。
产品之间差异性大,内部变化较大、较复杂时不建议使用建造者模式。
1 /*2 *关键代码:建造者类:创建和提供实例; Director类:管理建造出来的实例的依赖关系。3 */4 5 #include <iostream>6 #include <string>7 8 using namespace std;9 10 //具体的产品类11 class Order12 {13 public:14 void setFood(const string& food)15 {16 m_strFood = food;17 }18 19 const string& food()20 {21 cout << m_strFood.data() << endl;22 return m_strFood;23 }24 25 void setDrink(const string& drink)26 {27 m_strDrink = drink;28 }29 30 const string& drink()31 {32 cout << m_strDrink << endl;33 return m_strDrink;34 }35 36 private:37 string m_strFood;38 string m_strDrink;39 };40 41 //抽象建造类,提供建造接口。42 class OrderBuilder43 {44 public:45 virtual ~OrderBuilder()46 {47 cout << "~OrderBuilder()" << endl;48 }49 virtual void setOrderFood() = 0;50 virtual void setOrderDrink() = 0;51 virtual Order* getOrder() = 0;52 };53 54 //具体的建造类55 class VegetarianOrderBuilder : public OrderBuilder 56 {57 public:58 VegetarianOrderBuilder()59 {60 m_pOrder = new Order;61 }62 63 ~VegetarianOrderBuilder()64 {65 cout << "~VegetarianOrderBuilder()" << endl;66 delete m_pOrder;67 m_pOrder = nullptr;68 }69 70 void setOrderFood() override71 {72 m_pOrder->setFood("vegetable salad");73 }74 75 void setOrderDrink() override76 {77 m_pOrder->setDrink("water");78 }79 80 Order* getOrder() override81 {82 return m_pOrder;83 }84 85 private:86 Order* m_pOrder;87 };88 89 //具体的建造类90 class MeatOrderBuilder : public OrderBuilder91 {92 public:93 MeatOrderBuilder()94 {95 m_pOrder = new Order;96 }97 ~MeatOrderBuilder()98 {99 cout << "~MeatOrderBuilder()" << endl;
100 delete m_pOrder;
101 m_pOrder = nullptr;
102 }
103
104 void setOrderFood() override
105 {
106 m_pOrder->setFood("beef");
107 }
108
109 void setOrderDrink() override
110 {
111 m_pOrder->setDrink("beer");
112 }
113
114 Order* getOrder() override
115 {
116 return m_pOrder;
117 }
118
119 private:
120 Order* m_pOrder;
121 };
122
123 //Director类,负责管理实例创建的依赖关系,指挥构建者类创建实例
124 class Director
125 {
126 public:
127 Director(OrderBuilder* builder) : m_pOrderBuilder(builder)
128 {
129 }
130 void construct()
131 {
132 m_pOrderBuilder->setOrderFood();
133 m_pOrderBuilder->setOrderDrink();
134 }
135
136 private:
137 OrderBuilder* m_pOrderBuilder;
138 };
139
140
141 int main()
142 {
143 // MeatOrderBuilder* mBuilder = new MeatOrderBuilder;
144 OrderBuilder* mBuilder = new MeatOrderBuilder; //注意抽象构建类必须有虚析构函数,解析时才会 调用子类的析构函数
145 Director* director = new Director(mBuilder);
146 director->construct();
147 Order* order = mBuilder->getOrder();
148 order->food();
149 order->drink();
150
151 delete director;
152 director = nullptr;
153
154 delete mBuilder;
155 mBuilder = nullptr;
156
157 return 0;
158 }
8、外观模式
外观模式:为子系统中的一组接口定义一个一致的界面;外观模式提供一个高层的接口,这个接口使得这一子系统更加容易被使用;对于复杂的系统,系统为客户端提供一个简单的接口,把负责的实现过程封装起来,客户端不需要连接系统内部的细节。
以下情形建议考虑外观模式:
-
设计初期阶段,应有意识的将不同层分离,层与层之间建立外观模式。
-
开发阶段,子系统越来越复杂,使用外观模式提供一个简单的调用接口。
-
一个系统可能已经非常难易维护和扩展,但又包含了非常重要的功能,可以为其开发一个外观类,使得新系统可以方便的与其交互。
优点:
-
实现了子系统与客户端之间的松耦合关系。
-
客户端屏蔽了子系统组件,减少了客户端所需要处理的对象数据,使得子系统使用起来更方便容易。
-
更好的划分了设计层次,对于后期维护更加的容易。
1 /*2 * 关键代码:客户与系统之间加一个外观层,外观层处理系统的调用关系、依赖关系等。3 *以下实例以电脑的启动过程为例,客户端只关心电脑开机的、关机的过程,并不需要了解电脑内部子系统的启动过程。4 */5 #include <iostream>6 7 using namespace std;8 9 //抽象控件类,提供接口
10 class Control
11 {
12 public:
13 virtual void start() = 0;
14 virtual void shutdown() = 0;
15 };
16
17 //子控件, 主机
18 class Host : public Control
19 {
20 public:
21 void start() override
22 {
23 cout << "Host start" << endl;
24 }
25 void shutdown() override
26 {
27 cout << "Host shutdown" << endl;
28 }
29 };
30
31 //子控件, 显示屏
32 class LCDDisplay : public Control
33 {
34 public:
35 void start() override
36 {
37 cout << "LCD Display start" << endl;
38 }
39 void shutdown() override
40 {
41 cout << "LCD Display shutdonw" << endl;
42 }
43 };
44
45 //子控件, 外部设备
46 class Peripheral : public Control
47 {
48 public:
49 void start() override
50
相关文章:
9、设计模式
设计模式 1、工厂模式 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时&…...

数学专题.
数论 1.判断质数 定义:在大于1的整数中,如果只包含1和本身这两个约数,就称为质数or素数 Acwing 866.试除法判断质数 2.预处理质数(筛质数) Acwing 868.筛质数 3.质因数分解 Acwing 867.分解质因数 4.阶乘分解 5.因…...

如何提升网站的收录率?
要提升网站的收录率,其中一个特别有效的工具就是GPC爬虫池,这个工具通过深度研究谷歌SEO算法,吸引谷歌爬虫。 GPC爬虫池的基本原理是构建一个庞大的站群系统,并创建复杂的内链和外链结构,以吸引并留住谷歌蜘蛛 使用GP…...
HALCON根据需要创建自定义函数
在HALCON中,根据需要创建自定义函数是扩展其图像处理和分析功能的有效方式。HALCON支持通过其高级编程接口(HDevelop和C/C、C#、Python等)来创建自定义函数。这里将主要讨论在HDevelop环境中如何创建自定义函数,因为HDevelop是HAL…...
力扣SQL仅数据库(196~569)
196. 删除重复的电子邮箱 题目:编写解决方案 删除 所有重复的电子邮件,只保留一个具有最小 id 的唯一电子邮件。 (对于 SQL 用户,请注意你应该编写一个 DELETE 语句而不是 SELECT 语句。) (对于 Pandas …...

网络基础:理解IP地址、默认网关与网段(IP地址是什么,默认网关是什么,网段是什么,IP地址、默认网关与网段)
前言 在计算机网络中,IP地址、默认网关和网段(也称为子网)之间有着密切的关系。它们是网络通信中的至关重要的概念,但它们并不相同。这里来介绍一下它们之间的关系,简单记录一下 一. IP地址 1. 介绍 IP 地址…...

windows安装php7.4
windows安装php7.4 1.通过官网下载所需的php版本 首先从PHP官网(https://www.php.net/downloads.php)或者Windows下的PHP官网(http://windows.php.net/download/)下载Windows版本的PHP安装包。下载后解压到一个路径下。 2.配…...
【代码随想录|图论part03之后】
代码随想录|数组 704. 二分查找,27. 移除元素 一、part031、101. 孤岛的总面积1.1 dfs版本1.2 BFS版本2.102. 沉没孤岛3、103. 水流问题4、104.建造最大岛屿二、part041、110. 字符串接龙2、105.有向图的完全可达性3、106. 岛屿的周长三、part05-06 并查集理论1、107. 寻找存在…...
【项目一】基于pytest的自动化测试框架day1
day1不涉及编写代码,只简单梳理接口测试相关的概念。 day1接口测试的本质:功能测试的一部分测试用例的设计与实现接口调试与自动化:从postman到持续集成补充概念 day1 接口测试的本质:功能测试的一部分 接口测试是功能测试的一部…...
如何下载和安装 Notepad++
Notepad 是一款功能强大的开源文本编辑器,广泛用于代码编写和文本编辑。以下是 Notepad 的下载安装教程: 下载 Notepad 访问官方网站 打开你的网络浏览器,访问 Notepad 的官方网站:https://notepad-plus-plus.org/ 选择下载选项…...

笔记:如何使用Process Explorer分析句柄泄露溢出问题
一、目的:如何使用Process Explorer分析句柄泄露溢出问题 使用 Process Explorer 分析句柄泄漏问题是一个非常有效的方法。句柄泄漏通常是由于应用程序在创建系统资源(如文件、注册表项、GDI 对象等)后没有正确释放这些资源。以下是使用 二、…...
HTTP/2
http相关知识点 HTTP/2是超文本传输协议(HTTP)的第二个主要版本,旨在解决HTTP/1.x版本中存在的一些性能限制和效率问题。HTTP/2由互联网工程任务组(IETF)的HTTP工作组开发,最终在2015年作为RFC 7540正式发…...

如何在算家云搭建ComfyUI(AI绘画)
一、ComfyUI简介 ComfyUI 是一个强大的、模块化的 Stable Diffusion 界面与后端项目。该用户界面将允许用户使用基于图形/节点/流程图的界面设计和执行高级稳定的扩散管道。该项目部分其它特点如下: 全面支持 SD1.x,SD2.x,SDXL,…...

公司的企业画册如何制作?
企业画册是公司形象和产品服务展示的重要载体,一个制作精良的企业画册不仅能展示公司的实力,也能提升客户对公司专业度的认可。以下是制作企业画册的步骤和要点,帮助你的公司画册既美观又实用。 1.要制作电子杂志,首先需要选择一款适合自己的…...

13、Django Admin创建两个独立的管理站点
admin文件 from .models import Epic, Event, EventHero, EventVillain from django.contrib.admin import AdminSiteclass EventAdminSite(AdminSite):site_header "Events管理"site_title "欢迎您!"index_title "管理员"even…...

使用docker compose一键部署 Openldap
使用docker compose一键部署 Openldap LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是一种用于访问分布式目录服务的网络协议,OpenLDAP 是 LDAP 协议的一个开源实现,由 OpenLDAP 项目提供&#x…...

网站代运维与建设:HTTP虚拟专线的优势
网站代运维与建设:HTTP虚拟专线的优势 企业和个人越来越依赖于网站来展示品牌形象、提供服务和与客户互动。然而,网站的建设和运维往往需要投入大量时间和资金,尤其是在服务器费用和技术维护方面。本文将探讨如何通过使用HTTP虚拟专线来降低…...
奇异递归模板模式(Curiously Recurring Template Pattern)
奇异递归模板模式(Curiously Recurring Template Pattern) - 知乎 (zhihu.com) 本文来自上面的文章!!!本菜鸡学习和记录一下。 CRTP是C模板编程时的一种惯用法:把派生类作为基类的模板参数。 1.静态多态 #include <iostrea…...

【ArcGIS Pro实操第一期】最小成本路径(Least-cost path)原理及实操案例
ArcGIS Pro实操第一期:最小成本路径原理及实操案例 概述(Creating the least-cost path)1.1 原理介绍1.2 实现步骤1.3 应用案例 2 GIS实操2.1 工具箱简介2.1.1 成本路径(Cost path)2.1.2 成本距离(Cost dis…...
探索C++编程技巧:计算两个字符串的最长公共子串
探索C编程技巧:计算两个字符串的最长公共子串 在C面试中,考官通常会关注候选人的编程能力、问题解决能力以及对C语言特性的理解。一个常见且经典的问题是计算两个字符串的最长公共子串(Longest Common Substring, LCS)。本文将详…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...

Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...