软件设计模式与设计原则
0.0 什么是软件设计模式:
软件设计模式是一套经过前人反复使用并优化过的,用于解决一些特定问题的模版。
模式起源于建筑领域, 由四人组(Gang of Four,简称GoF)将模式的概念引入软件工程领域,他们在1994年归纳发表了23种在软件开发中使用频率较高的设计模式,旨在用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟。
学习软件设计模式可以更好的理解面向对象概念。
使用软件设计模式可以加快软件开发的效率,提升代码复用率,提高程序稳定性及扩展性。
-
创建型模式:常用与类或对象的创建(单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式)
-
结构型模式:常用与组合类或对象(适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式)
-
行为型模式:常用与类或对象之间的交互与职责分配(模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式)
其次,我们也可以根据模式作用于类或对象,将模式分为类模式与对象模式
本系列中所有参考资料如下:
1.《设计模式-可复用面向对象软件的基础》
2.《大话设计模式》
3. https://blog.****.net/lovelion/article/details/17517213
4. https://baike.baidu.com/item/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1212549?fr=aladdin
5.https://www.cnblogs.com/liebrother/p/10941660.html
0.1 什么是设计原则:
设计原则是设计模式的基础,所有设计模式都必须遵循设计原则。
设计原则共有七条:单一职责原则、开放-封闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、组合重用原则、迪米特原则。
1. 单一职责原则(Single Responsibility Principle,简称SRP ):
原则描述:就一个类而言,应该仅有一个引起它变化的原因。
简单来讲就是一个类只干一种事,一个函数只干一件事。
例如:需要编写一个计算器软件,在界面上输入算式,在屏幕上显示最终结果。
小明针对需求做了以下设计:
在该设计中CCalculator类同时做了显示与计算的工作,违反了单一职责原则,日后若更换UI,需要将add与sub操作的代码转移到新类中去,增添了不必要的麻烦。
后来小明意识到了这一点,将设计做了重构:
2.开闭原则(Open-Closed Principle, OCP):
原则描述:一个软件实体应当对扩展开放,对修改关闭。
当一个类需要新增一个新功能时,无需修改类中现有的代码,这点对多人开发尤为重要。
例如:需要编写绘制几何图形的软件,在窗口上绘制圆与矩形。
小明针对需求做了以下设计:
在该设计中CGeometry类承担了所有绘制工作,若日后需要增加绘制三角形的功能,则需要改动CGeometry类,违反了开闭原则。
后来小明意识到了这一点,将设计做了重构:
此时若需要新增一个绘制功能,只需要继承CGeometry,实现虚函数draw即可,无需改用原有代码。
抽象是遵守开闭原则最好的方式。
3.里氏代换原则(Liskov Substitution Principle, LSP):
原则描述:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
简单的来讲就是,在程序运行中,把父类替换成它的子类,程序的行为不会产生变化。
例如:小明在一个程序中做了以下设计:
该设计违反了里氏代换原则,虽然燕子与鸵鸟都是鸟类,但是鸵鸟其实不会飞,在程序运行中用COstrich::fly()替代CBird::fly()时,程序的行为将会发生变化。
后来小明意识到了这一点,将设计做了重构:
除此之外,子类方法的访问权限不能小于父类对应方法的访问权限,否则也会违反里氏代换原则。
4.依赖倒转原则(Dependency Inversion Principle, DIP):
原则描述:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
例如:需要编写一个软件,可以模仿不同动物的叫声,小明做了如下设计
在该设计中,每一个函数都对一种特定的动物实现了拟声,所以该设计针对实现进行编程,也称其依赖于细节。
其次,该设计违反了开闭原则,而依赖倒转原则作为一种实现开闭原则的手段,违背开闭原则通常也违背依赖倒转原则。
后来小明将设计进行了重构:
在修改后的设计中,拟声功能依赖于抽象接口cry()函数,更换与新增动物都不用修改任何函数,我们称之为“依赖于抽象”以及“针对接口编程”
5.接口隔离原则(Interface Segregation Principle, ISP):
原则描述:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
例如:在家庭中分配家务活,男人负责做饭、维修,女人负责洗碗、洗衣服,有时两人还会一起打扫房间,针对这一场景,能够画出一下UML图
该设计中男人不需要洗碗、洗衣服,女人不需要做饭、维修,但他们都需要实现本不需要实现的类,这就违反了接口隔离原则
该设计需要做以下修改:
由于笔者只学过C++没有学过JAVA,搞清楚接口的概念废了一番功夫,在JAVA中有"Interface"关键字而C++没有,所以C++的接口只能通过多继承来实现,下面说明了C++实现接口类的注意事项:
1.接口类中不应有成员变量以及静态成员函数
2.接口类中的所有函数都应声明为纯虚函数
3.接口类中的函数不应使用简短通用的函数名,所有同名函数应有同样的语义(这点在JAVA中也应注意),错误举例:CCook::execute()、CRepair::execute()。
6.组合重用原则(Composite Reuse Principle, CRP):
原则描述:尽量使用对象组合,而不是继承来达到复用的目的。
7.迪米特法则(Law of Demeter, LoD):
原则描述:一个软件实体应当尽可能少地与其他实体发生相互作用。
例如:一个公司目前有财务部、研发部、市场部、生产部、行政部五个部门,各部门之间需要相互联系,具体联系如下:
1.财务部与行政部会给所有部门发送公告
2.研发部会给市场部发送新产品通知、给生产部发送生产说明书
3.市场部会给研发部发送产品需求、给财务部发送销售信息、给生产部发送订单信息
4.生产部会给财务部发送成本预算
5.除财务部以外所有部门都会向财务部发送财务咨询
6.除行政部以外所有部门都会向行政部发送规范咨询
按照以上联系设计如下:
看到这张图相信已经不需要我说什么了吧...
这种设计既不利于维护也不利于扩展。我们使用迪米特法则的概念对其进行重构,我们建立一个平台,由该平台负责通知需要通知的部门,重构后的设计图如下:
重构后的结构就清晰了许多,不论是维护还是扩展都比重构前方便