当前位置: 首页 > 编程日记 > 正文

设计模式七大原则(C++描述)

前言

最近在学习一些基本的设计模式,发现很多博客都是写了六个原则,但我认为有7个原则,并且我认为在编码中思想还是挺重要,所以写下一篇博客来总结下
之后有机会会写下一些设计模式的博客(咕咕咕........

设计模式的七大原则

1.单一职责原则
2.开放-封闭原则
3.依赖倒置原则
4.里氏替换原则(LSP)
5.接口隔离原则
6.迪米特原则(最少知道原则)
7.合成复用原则

1.单一职责原则

准确解析:就一个类而言,应该仅有一个引起它变化的原因

当一个类职责变化时不会导致另一个类职责的变化.

优点:可以降低类的复杂度,提高可读性

2.开放-封闭原则

准确解析:软件实体(类,模板,函数等等)应该可以扩展,但不可修改

开闭原则是面对对象设计的核心所在;开放人员应该仅对程序中呈现出频繁变化
的那些部分做出抽象.

3.依赖倒置原则

准确解析:A.高层模板(稳定)不应该依赖底层模板(变化).两个都应该依赖抽象(稳定)
B.抽象(稳定)不应该依赖实现细节(变化).细节(变化)应该依赖抽象(稳定).

不论变化还是稳定都应该依赖于稳定

说白了:要面对接口编程,不要对实现编程.

1656870-20190710094248778-2007106224.png

#include<iostream>
class Book
{public:void look(){....}.....
}
class Man
{puclic:void Action(Book book){book.look();}....
}int main()
{Man man=new Man();Book book=new book();Man->Action(book);....
}

上面显示的是人看书的行为

那么假设现有我想要人进行看视频行为,视频类的代码如下:

class Video
{public:void Video(){....}.....
}

那么我不仅要对人这个类中修改,还有对主函数的代码进行修改;如果有大量的需要的话,这个修改过程将会变得非常痛苦,因为书和人的耦合度太高.

接下来使用依赖倒置原则来会解决当前的痛苦,能够降低书和人的耦合度

书和视频我们当作一个可以看的东西ILOOK作为接口类,然后书和视频继承这个类

class ILOOK
{public:virtual void look()=0; 
}class Bookpublic ILOOk
{public:void look(){....}.....
}class Video:public ILOOk
{public:void look(){....}.....
}
class Man
{puclic:void Action(ILOOK ilook){ilook.look();}....
}int main()
{Man man=new Man();ILOOK ilook=new book();Man->Action(ilook);ILOOK ilook2=new video();Man->Action(ilook2);....
}

这样就实现了简单的依赖倒置,人依赖于ILOOK这个类,并且书和视频也都依赖于ILook(即高层和底层都应该依赖抽象

这便是一个简单的面对接口编程.

这个依赖倒置原则将会贯串于所有设计模式,所以对于这个原则一定要有清晰的认识

4.里氏替换原则(LSP)

准确解析:子类型必须能够替换掉它们的父类型
说白了就是一种IS-A的另一种表达

比如说:鸟是一个父类,有 fly()这个虚函数,燕子是一个鸟,因为它能够飞,所以它可以继承鸟类;

企鹅不能飞,所以它不能继承鸟类,即使他在生物学上是鸟类,但它在编程世界中不能够继承鸟类

这里说出LSP的一个特点:只有当子类可以替换掉父类,软件单位的功能不受影响时,父类才能够被复用,而子类也能够在父类的基础上增加新的行为

通俗来说:子类可以扩展父类的功能,但不能改变父类原来的功能。

包括4层含义:1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

4种含义不展开讲,但用下面的一个例子来简单说明

#include<iostream>class A
{
public:int fun1(int a, int b) {return a - b;}
};class B :public A
{
public:int fun1(int a, int b) {return a + b;}int fun2(int a, int b){return fun1(a, b)-100;   //想要a-b-100,但现实是a+b-100}
};
int main()
{int a = 100, b = 20;B* m_b=new B();std::cout << m_b->fun2(a, b) << std::endl;
}

上面显示的结果会是20,因为B类中的fun1()覆盖到了A类中的fun1();所以fun2()中调用的是B类的fun1(),这便违反了里氏替换原则

不遵循里氏替换原则的后果是:出问题的概率会大大提高

5.接口隔离原则

准确解释:不应该强迫客户程序依赖他们不用的方法;接口应该小而完备

1656870-20190710094318138-571986472.jpg

class I
{public:void method1()=0;void method2()=0;void method3()=0;void method4()=0;void method5()=0;
}class A
{public:void depend1(I i){i.method1();}void depend2(I i){i.method2();}void depend3(I i){i.method3();}
}
class B:public I
{public:void method1(){std::cout<<"B实现方法1"<<std::endl;}void method2(){std::cout<<"B实现方法2"<<std::endl;}void method3(){std::cout<<"B实现方法3"<<std::endl;}//B类种方法4和5不是必须的//但方法4和5因为继承的原因仍让需要空实现void method4(){}void method5(){}
}class C
{public:void depend1(I i){i.method1();}void depend2(I i){i.method4();}void depend3(I i){i.method5();}
}
class D:public I
{public:void method1(){std::cout<<"B实现方法1"<<std::endl;}void method4(){std::cout<<"B实现方法4"<<std::endl;}void method5(){std::cout<<"B实现方法4"<<std::endl;}//B类种方法2和3不是必须的//但方法2和3因为继承的原因仍让需要空实现void method2(){}void method3(){}
}
上面便没有使用接口隔离原则
下面便使用了接口隔离,所以一些无关的方法就可以不用去实现

1656870-20190710094332634-797531408.jpg

class I1
{public:void method1()=0;
}
class I2
{public:void method2()=0;void method3()=0;
}
class I3
{public:void method4()=0;void method5()=0;
}
class A
{public:void depend1(I1 i){i1.method1();}void depend2(I2 i){i2.method2();}void depend3(I2 i){i2.method3();}
}
class B:public I1,public I2
{public:void method1(){std::cout<<"B实现I1方法1"<<std::endl;}void method2(){std::cout<<"B实现I2方法2"<<std::endl;}void method3(){std::cout<<"B实现I2方法3"<<std::endl;}
}class C
{public:void depend1(I1 i){i1.method1();}void depend2(I2 i){i3.method4();}void depend3(I2 i){i3.method5();}
}
class D:public I1,public I3
{public:void method1(){std::cout<<"B实现I1方法1"<<std::endl;}void method4(){std::cout<<"B实现I3方法4"<<std::endl;}void method5(){std::cout<<"B实现I3方法4"<<std::endl;}}
使用接口隔离原则时应注意:
1.接口尽量小,但是要有限度。如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
2.为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。
3.提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
这个原则可以在实践多花时间思考,才可以准确地使用它

6.迪米特原则(最少知道原则)

准确解释:一个对象应该对其他对象保持最少的了解

因为类之间的关系最紧密,耦合度越高,一个类变化时对另一个类的影响也大

我们使用迪米特原则就是要降低类之间的耦合度

C++中一个重要的特性:高内聚,低耦合.高内聚,低耦合.高内聚,低耦合.(重要的事情说三遍)
#include<iostream>
#include<list>
#include<string>class Employee
{
private:std::string m_id;
public:Employee(){}Employee(std::string id) :m_id(id) {}std::string get_id(){return m_id;}
};
class SubEmployee
{
private:std::string m_id;
public:SubEmployee() {}SubEmployee(std::string id) :m_id(id) {}std::string get_id(){return m_id;}
};
class SubCompanyManager
{
public:std::list<SubEmployee> getAllEmployee(){std::list<SubEmployee> list(100);for (int i = 0; i < 100; i++){SubEmployee emp("分公司" + std::to_string(i));list.push_back(emp);}return list;}
};
class CompanyManager
{
public:std::list<Employee> getAllEmployee(){std::list<Employee> list(30);for (int i = 0; i < 30; i++){Employee emp("总公司"+std::to_string(i));list.push_back(emp);}return list;}void printALLEmployee(SubCompanyManager sub){std::list<SubEmployee> list1(100);list1 = sub.getAllEmployee();std::list<SubEmployee>::iterator itor= list1.begin();for (; itor != list1.end(); itor++){std::cout << itor->get_id();}std::list<Employee> list2(30);list2= getAllEmployee();std::list<Employee>::iterator itor2 = list2.begin();for (; itor2 != list2.end(); itor2++){std::cout << itor2->get_id();}}
};int main()
{CompanyManager* e = new CompanyManager();SubCompanyManager s;e->printALLEmployee(s);system("pause");return 0;
}
上面的代码违反了迪米特原则

根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。

class SubCompanyManager
{
public:std::list<SubEmployee> getAllEmployee(){std::list<SubEmployee> list(100);for (int i = 0; i < 100; i++){SubEmployee emp("分公司" + std::to_string(i));list.push_back(emp);}return list;}void printALLEmployee(){std::list<SubEmployee> list = getAllEmployee();std::list<SubEmployee>::iterator itor = list.begin();for (; itor != list.end(); itor++){std::cout << itor->get_id();}}
};
class CompanyManager
{
public:std::list<Employee> getAllEmployee(){std::list<Employee> list(30);for (int i = 0; i < 30; i++){Employee emp("总公司" + std::to_string(i));list.push_back(emp);}return list;}void printALLEmployee(SubCompanyManager sub){sub.printALLEmployee();std::list<Employee> list2(30);list2 = getAllEmployee();std::list<Employee>::iterator itor2 = list2.begin();for (; itor2 != list2.end(); itor2++){std::cout << itor2->get_id();}}
};

为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

另外切记不要过分使用迪米特原则,否则会产生大量的这样的中介和传递类,

7.合成复用原则

准确解析:尽量先使用组合后聚合等关联关系来实现,其次才考虑使用继承关系来实现

继承复用:又称"白箱""复用,耦合度搞,不利于类的扩展和维护

组合或聚合复用:又称"黑箱"复用,耦合度低,灵活度高

1656870-20190710113826963-1862154485.png

上面的图使用继承复合产生了大量的子类,如何需要增加新的"动力源"或者"颜色"
都要修改源代码,因为耦合度高,这违背了开闭原则

如果改为组合或聚合复用就可以很好的解决上述问题,如下图所示

1656870-20190710113850918-78160192.png

七点原则总结

单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
我们在实践时应该根据实际情况灵活使运用,才能达到良好的设计

参考博客:
http://www.uml.org.cn/sjms/201211023.asp#2

参考视频:
https://www.bilibili.com/video/av22292899

参考书籍:<>

转载于:https://www.cnblogs.com/Ligo-Z/p/11161911.html

相关文章:

gdb高级调试技巧

1. 反向调试 gdb支持程序反向执行。 record 让程序开始记录反向调试所必要的信息 rn : reverse next rc: reverse continue &#xff0c;Continue program being debugged but run it in reverse record stop: 停止记录 2. 格式化&#xff08;pretty print&#xff09;打…

php代码实现关键词搜索,PHP代码实现百度统计关键词及来路推送

搜索热词勾起我搞这个的兴趣是因为有个卖软件的&#xff0c;老是向我的百度统计后台推送引流软件广告。搜索后发现早就有人做过这方面的研究&#xff0c;然而随着统计代码版本升级&#xff0c;部分功能暂时还未解决。今天这篇 PHP 代码实现提交虚假数据给百度统计就教大家&…

linux跨主机复制文件

scp -r billing10.200.171.111:/billdata2/user/yanhm/redis/* /newboss/billing/user/aabb 其中&#xff1a; 10.200.171.111&#xff1a;远程主机 billing&#xff1a;远程主机的用户名 /billdata2/user/yanhm/redis/&#xff1a;要复制远程主机的文件路径 /newboss/billing/…

delphi使用outputdebugstring调试程序和写系统日志

delphi使用outputdebugstring调试程序和写系统日志 procedure TForm1.btn1Click(Sender: TObject); beginOutputDebugString(dddddd);OutputDebugString(11); end;procedure TForm1.btn2Click(Sender: TObject); varEvtSrcHand: THandle;EvtMsg: String; p:Pointer; i:integer;…

一个下载Windows镜像的地址

https://www.52pojie.cn/thread-633128-1-1.html转载于:https://www.cnblogs.com/blogs-jch/p/11163849.html

perf + 火焰图分析程序性能

From: https://www.cnblogs.com/happyliu/p/6142929.html 1、perf命令简要介绍 性能调优时&#xff0c;我们通常需要分析查找到程序百分比高的热点代码片段&#xff0c;这便需要使用 perf record 记录单个函数级别的统计信息&#xff0c;并使用 perf report 来显示统计结果&a…

jquery 设置css样式

$("#61dh a").css(color, 多个样式属性 var divcss {background: #EEE,width: 478px,margin: 10px 0 0,padding: 5px 10px,border: 1px solid #CCC};$("#result").css(divcss);查看某个元素的css属性值。 $("#61dh a").css("color"…

php改7z,PHP的7z扩展名? - php

我找不到一个&#xff0c;也不知道PHP Compression and Archive Extensions中的任何一个是否可以工作。您认为我可以使用compression stream从7z文件读取数据吗&#xff1f;更新7z forums对php扩展有很多要求参考方案7z文件格式可以使用各种compression algorithms&#xff0c;…

Classloader内存泄露

2019独角兽企业重金招聘Python工程师标准>>> 最近遇到了这个问题&#xff0c;在修改了-Xmx后有时仍然会出现&#xff0c;下文分析的很有启发&#xff0c;看了下文重新分析我的应用&#xff0c;在项目中我使用了spring mvc作为控制层&#xff0c;由于使用到了微信公众…

Springboot + oauth2 单点登录 - 原理篇

OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。授权码模式(authorization code)密码模式(resource owner password credentials)客户端模式(client credentials) 不常用。

Java 类型判断方法

Java 类型判断方法有三种,分别是instanceof是关键字,isInstance和isAssignableFrom是Class中的方法。> cls);

Docker-Compose搭建单体SkyWalking 6.2

SkyWalking简介 SkyWalking是一款高效的分布式链路追踪框架&#xff0c;对于处理分布式的调用链路的问题定位上有很大帮助 有以下特点&#xff1a; 性能好 针对单实例5000tps的应用&#xff0c;在全量采集的情况下&#xff0c;只增加 10% 的CPU开销。支持多语言探针支持自动及手…

gprof 性能优化工具

gprof用于分析函数调用耗时&#xff0c;可用gprof分析最耗时的函数&#xff0c;以便优化程序。 gcc链接时也一定要加-pg参数&#xff0c;以使程序运行结束后生成gmon.out文件&#xff0c;供gprof分析。 gprof默认不支持多线程程序&#xff0c;默认不支持共享库程序。 gcc 编…

基于Metronic的Bootstrap开发框架经验总结(7)--数据的导入、导出及附件的查看处理...

在很多系统模块里面&#xff0c;我们可能都需要进行一定的数据交换处理&#xff0c;也就是数据的导入或者导出操作&#xff0c;这样的批量处理能给系统用户更好的操作体验&#xff0c;也提高了用户录入数据的效率。我在较早时期的EasyUI的Web框架上&#xff0c;也介绍过通过Exc…

resin php,resin竟然开始支持PHP

真是好消息&#xff0c;resin开始支持PHP了&#xff0c;早上查资料&#xff0c;意外的看到了这个消息。由Resin 3.0.17开始&#xff0c;Resin里多了一个称为Quercus的东西&#xff0c;Quercus其实就是用Java实作的PHP语言模组。一直以来WEB语言都是各自为政&#xff0c;都有自己…

NSD WINDOWS--2014.8.11

实验01&#xff1a;不同网段的计算机远程配置交换机实验目标&#xff1a;实现不同网段的计算机远程配置交换机实验环境:实验步骤&#xff1a;一、分别配置pc0和pc1的ip地址网关二、配置交换机的管理ip地址和网关三、配置交换机远程管理密码和enable密码四、配置路由器的ip地址五…

day1-数据库基础

数据库基本概念 数据库是什么 数据库是用来存储数据的仓库&#xff0c;本质上就是一套基于CS架构的服务端和客户端程序&#xff0c;最终将数据存储在服务器端的磁盘中。之前学过的数据存储方式有&#xff1a; 列表 字典......等等&#xff0c;是在内存中的数据&#xff0c;缺点…

双重指针作为函数参数的妙用

双重指针作为函数参数&#xff0c;可以在函数函数内部修改外部指针的值。主要用法包括&#xff1a; 1. 在函数内部分配内存&#xff0c;作为函数参数返回&#xff1b; 2. 在函数内部设置指针为空&#xff1b; #include <stdio.h> #include <stdlib.h> #include …

什么是常函数?

类的成员函数后面加 const&#xff0c;表明这个函数不会对这个类对象的数据成员&#xff08;准确地说是非静态数据成员&#xff09;作任何改变。 在设计类的时候&#xff0c;一个原则就是对于不改变数据成员的成员函数都要在后面加 const&#xff0c;而对于改变数据成员的成…

matlab整型和浮点的区别,技术帖 | 心理学MATLAB初学者教程--简单数据类型介绍(逻辑型数据,整型/浮点型数据,字符型)......

1.2.1 什么是变量和变量名变量这个词似乎在许多地方都有出现&#xff0c;在计算机中变量是指是一段有名字的连续存储空间(摘自百度百科)&#xff0c;而这个所谓的名字就叫做变量名。举个例子说现在我们再MATLAB的命令窗口中输入&#xff1a;A 1 然后按回车&#xff0c;我们会看…

为窗口添加滚动条事件

为窗口添加滚动条事件其实非常的简单&#xff0c; window.οnscrοllfunction(){};注意在获取滚动条距离的时候谷歌不识别document.documentElement.scrollTop&#xff0c;必须要加上document.body.scrollTop&#xff1b;即var scrolltopdocument.documentElement.scrollTop||d…

当远程桌面到Windows终端服务器,出现终端服务器超出了最大允许连接数,怎么办...

如果是老版本的MSTSC则使用 MSTSC /console /v:ip如果是新版本 MSTSC /admin /v:ip转载于:https://www.cnblogs.com/SharkXu/archive/2012/10/08/MSTSC.html

连续地址数据(数组或者malloc的内存)作为函数参数

在编程时&#xff0c;一簇连续的内存单元&#xff0c;比如数组或者malloc的内存块,如下的数组a 或者指针p. int a[4]; int *p malloc(4 * sizeof(int)); 我们要想修改上述连续的内存块&#xff0c;可以写一个函数(一维指针做参数传入起始地址即可&#xff09;来搞定。 If …

Linux 文件系统及 ext2 文件系统

linux 支持的文件系统类型 Ext2:有点像 UNIX 文件系统。有 blocks,inodes,directories 的概念。Ext3:Ext2 的加强版&#xff0c;添加了日志的功能。支持 POSIX ACL(Access control Lists,访问控制列表) 。Isofs(iso9660):CDROM 文件系统。Sysfs:基于 ram 的文件系统&#xff0c…

php每分钟刷新一次的验证码,php如何在进入页面的时候自动刷新一次验证码

页面直接进入login页面的时候无法获取到$_SESSION["auth"]值&#xff0c;只能刷新一次验证码才能获取到值&#xff0c;怎么才能在用户访问的时候自动刷新一次验证码&#xff0c;求大神支招。。。验证码回复讨论(解决方案)哪里获取到$_SESSION["auth"]值&am…

vs2010快捷方式

【窗口快捷键】 CtrlW,W: 浏览器窗口 CtrlW,S: 解决方案管理器 CtrlW,C: 类视图 CtrlW,E: 错误列表 CtrlW,O: 输出视图 trlW,P: 属性窗口 CtrlW,T: 任务列表 CtrlW,X: 工具箱 CtrlW,B: 书签窗口 CtrlW,U: 文档大纲 CtrlD,B: 断点窗口 CtrlD,I: 即时窗口 CtrlTab: 活…

移动端rem屏幕设置

//修改页面title var pageTitledocument.getElementsByTagName("title")[0].innerHTML; if(location.href.indexOf("index.html")>-1 || location.href.indexOf("html")-1){document.getElementsByTagName("title")[0].innerHTML&q…

二维指针操作链表

背景 Linus slashdot: https://meta.slashdot.org/story/12/10/11/0030249 Linus大婶在slashdot上回答一些编程爱好者的提问&#xff0c;其中一个人问他什么样的代码是他所喜好的&#xff0c;大婶表述了自己一些观点之后&#xff0c;举了一个指针的例子&#xff0c;解释了…

php多选框怎么传值,tp3.2如何处理多选框传参和判断状态

创建多选框&#xff1a;(1)普通的多选&#xff1a;123(2)在数据库中遍历出来的多选框,value和data-id都要赋值(大家都懂&#xff0c;我就不说啦)&#xff1a;{$vo.title} 2.我是用jq做的异步,我是使用英文“&#xff0c;”分割字段存储的。我们的前端是自己封装过的,大家根据自…

UIWebView和UIActivityIndicatorView的结合使用

环境&#xff1a;Xcode6.1 UIWebView是iOS开发中常用的一个控件&#xff0c;是内置的浏览器控件&#xff0c;我们可以用它来浏览网页&#xff0c;加载文档等。这篇文件将结合UIActivityIndicatorView控件制作一个小实例(加载apple的官网)。效果如下&#xff1a; 一.建立一个Sin…