【设计模式学习笔记】设计模式七大原则介绍(基于C++实现)

简介: 【设计模式学习笔记】设计模式七大原则介绍(基于C++实现)

一、开放封闭原则

OCP,Open For Extension Closed For Modification Principle,简称开闭原则。开闭原则是指软件实体是可以扩展的,但是不可修改。也就是说,模块和函数是对扩展(提供方)开放的,对修改(使用方)关闭的,对于一个新的需求,对软件的改动应该是通过增加代码来实现的,而不是通过改动代码实现的。开闭原则是面向对象的核心,是最基础、最重要的设计原则,开发过程中,应该把可能会频繁变动的部分抽象出来,当需要变动时,只需要去实现抽象即可,也就是面向抽象编程。对于C++类来说,对类的改动是通过增加代码实现的,而不是修改代码实现的,通过虚基类的继承和虚函数的实现来完成一个类功能的扩充,这也是多态在设计模式中重要地位的体现。

举例来说,假如我们要创建一个迪迦奥特曼类,迪迦奥特曼有三种形态,最简单的方式就是在一个类中实现,每次都在类中增删查改

1. //第一层次:直接修改类来实现增加功能
2. class TigaUltraman1
3. {
4. public:
5.  void RedForm() //红色形态
6.  {
7.    cout << "红色形态的迪迦奥特曼" << endl;
8.  }
9. 
10.   void BlueForm() //蓝色形态
11.   {
12.     cout << "蓝色形态的迪迦奥特曼" << endl;
13.   }
14. 
15.   void CompreForm() //综合形态
16.   {
17.     cout << "综合形态的迪迦奥特曼" << endl;
18.   }
19. };
20. 
21. int main()
22. {
23. //1. 直接修改迪迦奥特曼类来增加不同形态
24.   TigaUltraman1* u1 = new TigaUltraman1;
25.   u1->RedForm();
26.   u1->BlueForm();
27.   u1->CompreForm();
28.   delete u1;
29.   cout << endl;
30. }

那么这样的话显然不满足开闭原则,实际上应该定义一个抽象类,这个抽象类只提供一个统一的接口,当需要增加功能时只需要继承这个抽象类,并实现抽象方法即可。

1. //第二层次,创建一个抽象类,通过继承实现形态扩充
2. class TigaUltraman //迪迦奥特曼抽象类
3. {
4. public:
5.  virtual void uForm() = 0;
6. };
7. 
8. class RedTigaUltraman : public TigaUltraman
9. {
10. public:
11.   virtual void uForm()
12.   {
13.     cout << "红色形态的迪迦奥特曼" << endl;
14.   }
15. };
16. 
17. class BlueTigaUltraman : public TigaUltraman
18. {
19. public:
20.   virtual void uForm()
21.   {
22.     cout << "蓝色形态的迪迦奥特曼" << endl;
23.   }
24. };
25. 
26. class CompreTigaUltraman : public TigaUltraman
27. {
28. public:
29.   virtual void uForm()
30.   {
31.     cout << "综合形态的迪迦奥特曼" << endl;
32.   }
33. };
34. 
35. int main()
36. {
37. //2. 使用继承
38.   TigaUltraman* u2 = NULL;
39.   u2 = new RedTigaUltraman;
40.   u2->uForm();
41.   delete u2;
42.   u2 = new BlueTigaUltraman;
43.   u2->uForm();
44.   delete u2;
45.   u2 = new CompreTigaUltraman;
46.   u2->uForm();
47.   delete u2;
48.   cout << endl;
49. }

更进一步,我们可以提供一个接口,直接调用接口,把各种实现类的对象传递给抽象类的指针并产生多态。即使是子类的子类也可以传给抽象类产生多态。

1. //第三层次:使用一个统一接口
2. void get_form(TigaUltraman* u)
3. {
4.  u->uForm();
5. }
6. 
7. //增加功能,进化版的综合形态
8. class EvolutionCompreTigaUltraman : public CompreTigaUltraman
9. {
10. public:
11.   virtual void uForm()
12.   {
13.     cout << "进化版的综合形态的迪迦奥特曼" << endl;
14.   }
15. };
16. 
17. int main()
18. {
19. //3. 使用统一接口
20.   u2 = new RedTigaUltraman;
21.   get_form(u2);
22.   delete u2;
23.   BlueTigaUltraman* u3 = new BlueTigaUltraman;
24.   get_form(u3);
25.   delete u3;
26.   CompreTigaUltraman u4;
27.   get_form(&u4);
28.   cout << endl;
29. 
30.   u2 = new EvolutionCompreTigaUltraman;
31.   get_form(u2);
32.   delete u2;
33.   cout << endl;
34. }

二、单一职责原则

SRP,Single Responsibility Principle,单一职责原则。对类来说,类的职责应该是单一的,一个类只能对外提供一种功能。换句话说,变动这个类的理由或动机只能有一个,如果第二个改动类的理由,就不是单一职责。单一职责相当于降低了各种职责的耦合度,如果一个类负责多个职责,那么改动类的某一职责时,可能会影响到类行使其他职责的能力。

三、依赖倒置原则

DIP,Dependence Inversion Principle,依赖倒置原则。抽象不应该依赖于细节,细节应该依赖于抽象。换句话说,依赖于抽象接口,而不是依赖于具体的类的实现,也就是面向抽象接口编程。依赖倒置原则是面向对象编程的标志,在具体软件设计时,上层模块不应该依赖于底层模块,底层模块更不应该依赖上层模块,而是上层模块和底层模块都向中间靠拢,共同依赖于二者中间的抽象接口层。整个软件程序设计的依赖关系应该终止于抽象接口层,上层和底层互不关心,甚至使用什么编程语言都不关心。抽象接口层提供一个标准或者协议,它对上提供访问的接口,对下提供实现的标准,抽象接口层本身不执行任何操作,具体的功能由它的实现去完成。

举例来说,假如我们要组装一台电脑,现在要选择硬盘、内存、屏幕。那么电脑类要集成硬盘、内存、屏幕这些组件,但是我们希望电脑类和组件类之间不应该是相互依赖的关系,我们就可以直接给出一套接口,各个组件厂商只要实现这些抽象接口就可以装入我们的电脑中。

1. //各个组件的抽象类
2. class DiskInterface
3. {
4. public:
5.  virtual void infomation() = 0;
6. };
7. 
8. class MemoryInterface
9. {
10. public:
11.   virtual void infomation() = 0;
12. };
13. 
14. class ScreenInterface
15. {
16. public:
17.   virtual void infomation() = 0;
18. };

电脑类定义如下

1. class Computer
2. {
3. public:
4.  Computer(DiskInterface* disk, MemoryInterface* memory, ScreenInterface* screen)
5.  {
6.    this->disk = disk;
7.    this->memory = memory;
8.    this->screen = screen;
9.  }
10. public:
11.   void get_information()
12.   {
13.     this->disk->infomation();
14.     this->memory->infomation();
15.     this->screen->infomation();
16.   }
17. private:
18.   DiskInterface* disk; //使用指针而不能使用变量
19.   MemoryInterface* memory;
20.   ScreenInterface* screen;
21. };

各个厂商根据组件的抽象类去实现,来入围电脑类

1. //各厂商直接继承抽象类,来实现
2. class InterDisk : public DiskInterface
3. {
4. public:
5.  virtual void infomation()
6.  {
7.    cout << "因特尔硬盘" << endl;
8.  }
9. };
10. 
11. class WDMemory : public MemoryInterface
12. {
13. public:
14.   virtual void infomation()
15.   {
16.     cout << "西部数据的内存条" << endl;
17.   }
18. };
19. 
20. class HPScreen : public ScreenInterface
21. {
22. public:
23.   virtual void infomation()
24.   {
25.     cout << "惠普的屏幕" << endl;
26.   }
27. };

这样组件类和电脑类都依赖于抽象接口层,两者都向接口层靠近,这就是面向接口编程,也就是我们的依赖倒置原则。我们直接把各个组件的实现类定义对象并传到电脑类中即可

1. {
2.     InterDisk* idisk = new InterDisk;
3.  WDMemory* wdmem = new WDMemory;
4.  HPScreen* hpscr = new HPScreen;
5.  SamScreen* samscr = new SamScreen;
6. 
7.  Computer* c1 = new Computer(idisk, wdmem, hpscr); //使用惠普的屏幕
8.  c1->get_information();
9. 
10. delete c1;
11.   delete samscr;
12.   delete hpscr;
13.   delete wdmem;
14.   delete idisk;
15. }

假如后来,三星也想入围这个电脑,那么三星直接去实现屏幕的抽象类即可

1. class SamScreen : public ScreenInterface
2. {
3. public:
4.  virtual void infomation()
5.  {
6.    cout << "三星的屏幕" << endl;
7.  }
8. };

我们再直接把三星的屏幕传入电脑类即可

1. { 
2.     Computer* c2 = new Computer(idisk, wdmem, samscr); //使用三星屏幕
3.  c2->get_information();
4. }

这就是面向接口编程。

四、接口隔离原则

ISP,Interface Segegation Principle,接口隔离原则。一个接口对外只提供一种功能,不同功能应该通过不同的接口提供,而不是把多种功能都封装到一个接口中,否则的话,可能有的客户只需要功能A不需要功能B,但是提供A功能的接口内还封装了功能B,这就造成了客户被迫依赖某种他们不需要的功能。

五、里氏替换原则

LSP,Liskov Substitution Principle,里氏替换原则。任何地方出现的抽象类,都可以使用该抽象类的实现类来代替,这其实类似于C++中的类型兼容性原则,所有基类出现的地方都可以用子类对象来代替。实际上,继承增强了类与类之间的耦合性,在继承中应该尽量不要重写(覆盖)基类中的非抽象方法,子类可以有自己的方法,但是不能重新定义或修改基类的方法,否则如果基类中发生改变,所有的子类都可能受影响,应该尽量使用组合或者聚合而不是继承。其实,准确来说,应该是尽量不要继承可实例化的类(非抽象类),而应该是从抽象类中继承。

1. class parent
2. {
3. public:
4.  virtual void function1() = 0;
5.  void function2()
6.  {
7.    cout << "parent function 2" << endl;
8.  }
9. };
10. 
11. class child1 : public parent
12. {
13. public:
14.   virtual  void function1()
15.   {
16.     cout << "function 1" << endl;
17.   }
18.   void function2()
19.   {
20.     cout << "child function 2" << endl;
21.   }
22. };

举例来说,应该去实现function1()这样的抽象方法,而不能去覆盖基类的方法function2()。当子类覆盖或实现基类的方法时,方法的前置条件(形参)要比基类方法的输入参数宽松;当子类实现父类的抽象方法时,方法的后置条件(方法返回值)要比基类更严格。

六、合成复用原则

CARP,Composite/Aggregate Reuse Principle,优先使用对象组合而不是继承原则。使用继承的时候,基类的变化可能会影响到子类,而如果使用组合/聚合就可以降低这种依赖关系。组合/聚合降低了类与类之间的耦合性,一个类的变化对另一个类的影响相对较小,如果要使用继承,必须要遵守里氏替换原则。

七、迪米特法则

LOD,Law of Demeter,迪米特法则,也叫做最少知道原则(The Least Knowledge Principle)。一个类对于其它类知道的越少越好,只和朋友说话,不要和陌生人说话,这里的朋友是指作为成员变量、方法输入输出参数的类(朋友类),如果是出现在一个方法内部的类就不是朋友类。迪米特法则降低了耦合度,各个模块之间通过一个接口来实现调用,而模块之间不需要知道对方的内部实现逻辑,并且一个模块的内部改变也不会影响到另一个模块(黑盒原理)。如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用另一个类的某个方法的话,可以通过第三个类转发这个调用。

朋友圈确定:

  1. 当前对象本身;
  2. 以参量形式传入到当前对象方法中的对象;
  3. 当前对象的实例变量直接引用的对象;
  4. 当前对象的实例变量如果是一个聚集,那么聚集中的元素都是朋友;
  5. 当前对象所创建的对象;

满足条件之一就是朋友,否则就是陌生人。


相关文章
|
4月前
|
C++
c++学习笔记07 结构体
C++结构体的详细学习笔记07,涵盖了结构体的定义、使用、数组、指针、嵌套、与函数的交互以及在结构体中使用const的示例和解释。
40 0
|
2月前
|
设计模式 Java Kotlin
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
Kotlin学习笔记 - 改良设计模式 - 迭代器模式
29 2
|
3月前
|
设计模式 Java 关系型数据库
设计模式——设计模式简介和七大原则
设计模式的目的和核心原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则、合成复用原则
设计模式——设计模式简介和七大原则
|
2月前
|
设计模式 JavaScript Scala
Kotlin学习笔记 - 改良设计模式 - 责任链模式
Kotlin学习笔记 - 改良设计模式 - 责任链模式
42 0
|
2月前
|
设计模式 Java Kotlin
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
Kotlin 学习笔记- 改良设计模式 - 装饰者模式
26 0
|
3月前
|
安全 C语言 C++
C++学习笔记
C++学习笔记
|
4月前
|
C++
【学习笔记】【C/C++】 c++字面值常量
【学习笔记】【C/C++】 c++字面值常量
42 1
|
4月前
|
编译器 C++
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
【C/C++学习笔记】C++声明与定义以及头文件与源文件的用途
49 0
|
4月前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
44 0
|
4月前
|
C++
c++学习笔记09 引用
C++引用的详细学习笔记,解释了引用的概念、语法、使用注意事项以及引用与变量的关系。
44 0