0. UML
统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言。
可以参考统一建模语言
于此不详述,仅展示使用Visual Studio自动生成的类图样式
需要说明的是,VS中生成的类图样式和一般通用的UML是有一些区别的,但是也就仅仅可以称之为风格不同,其整体是一样的
-
类:蓝色方框,上部分为类名和该类的父类,下部分为类中成员,抽象类类名为斜体且方框为虚线
通用的UML中,访问修饰符private(“-”),public(“+”),在VS中直接用单词表示,无需多虑
-
接口:绿色方框,上部分为接口名,下部分为接口成员(方法,属性,事件)
-
聚合关系:使用金色的实线加箭头连接
一个类中拥有另一个类的类型的属性,默认是显示在属性框中,右键->显示为关联,则可以显示
-
继承关系:子类通过实线加箭头指向父类
-
实现接口:实现类的上方使用实线连接一个圆形,并标注实现的接口名(这种表示方法称之为:棒棒糖表示法)
通用的UML中,实现接口一般是使用虚线加箭头指向接口
示例:
图中描述的是:
Person类为抽象类,有三个属性,派生了Student类和Teacher类
ILivable为接口,有两个方法,Student和Teacher类都实现了该接口
Student类中有一个Teacher类型的属性teacher,同时还有一个Class类型的属性@class
Teacher类中有一个List<Student>类型的属性students,同时还有一个Class类型的属性@class
Class类中有List<Student>类型的属性students,同时还有List<Teacher>类型的属性teachers
1. 开闭原则
是什么?
开闭原则(Open Closed Principle,OCP):软件实体应该对扩展开放,对修改关闭。
即软件实体尽量在不修改软件源代码的情况下进行扩展
这里的软件实体主要是指:模块、类、接口、方法
它也是其他面向对象设计原则的基础,也是面向对象的程序设计的终极目标。
怎么做?
在面向对象设计中,可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
为什么?
当需要对软件进行扩展的时候,我们不需要修改抽象层,只需要从抽象类中派生一个实现类即可,从而实现软件具有一定的稳定性和延续性。
开闭原则是面向对象程序设计的终极目标,而里氏转换原则和依赖倒置原则都是实现开闭原则的方式。
这句话你品,你仔细品!
2. 里氏替换原则
是什么?
里氏替换原则(Liskov Substitution Principle,LSP)父类对象出现的地方都可以使用子类对象替换。
换言之,一个父类对象引用可以指向其子类对象。
怎么实现一个父类对象引用可以指向其子类对象呢?那就是遵循——子类可以扩展父类的功能,但是不能改变(重写)父类的原有功能
怎么做?
运用里氏原则时,尽量将需要扩展的类或是存在变化的类设计为抽象类或是接口,并将其作为基类使用,在编程的时候尽量对基类对象进行编程。
为什么?
在程序中声明基类引用,而该引用指向一个子类对象,这样可以随时替换不同的子类对象,从而实现不同的功能,而不需要修改那些使用该基类对象的代码。
之前说过,子类可以扩展父类的功能,但是不要改变父类的原有功能,就是因为在调用一个父类对象中的方法使用,而此时我更换一个其子类对象,原则上是完全可以的,但是若是子类中对其父类中的方法重写了,而且碰巧这里调用这个父类中的方法,则程序会出现异常!
3. 依赖倒置原则
是什么?
依赖倒置原则(Dependence Inversion Principle,DIP)高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
怎么做?
针对接口编程,而不是针对实现编程。
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 使用继承时尽量遵循里氏替换原则。
为什么?
在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或者抽象类,而细节是指具体的实现类
4. 单一职责原则
是什么?
单一职责原则(Single Responsibility Principle,SRP)又称单一功能原则,一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
换言之,一个类只负责一个功能领域中的相应职责
怎么做?
发现并将类的不同职责分离,再封装到不同的类或模块中
为什么?
单一职责是实现高内聚,低耦合的指导方针
在面向对象的程序设计中,如果一个类承担的职责越多,则其可以被复用的可能性就越小。一个类承担多个职责就是将多个职责耦合在一起,当其中的一个职责发生改变的时候,可能会影响到其他职责的功能。
5. 接口隔离原则
是什么?
接口隔离原则(Interface Segregation Principle,ISP)将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
换言之,不要使用单一的总接口,使用具体的有针对性的小接口。
怎么做?
接口尽量要小,每个接口承担相对独立的功能。
为什么?
面向对象中,实现一个接口,则必须实现接口中的所有方法,若是接口过于的庞大,则使用起来不方便,而且可能当前对象只需要该接口中的部分功能。
6. 迪米特法则
是什么?
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP)“只与你的直接朋友交谈,不跟'陌生人'说话”
换言之,一个软件实体应当尽可能少地与其他实体发生相互作用。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
怎么做?
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
典型应用:中介者模式
为什么?
降低了类之间的耦合度,提高了模块的相对独立性,从而提高了类的可复用率和系统的扩展性。
但是严格的遵循迪米特法则,则可能会出现大量的中介者类,从而增加了系统的复杂性,降低了模块之间通信的效率。
所以采用迪米特法则需要权衡利弊,在确保低耦合的情况下,避免增加系统的复杂性
7. 合成复用原则
是什么?
合成复用原则(Composite Reuse Principle,CRP)它要求在软件实体复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
换言之,尽量使用对象组合,而不是使用继承来达到复用的目的。
类与类之间的关系是一种结构性的关系,称之为关联关系,包括:单项关联,双向关联,自关联,聚合关系,组合关系
- 聚合关系:即部分与整体的关系(UML中使用空心的菱形表示),比如计算机包括鼠标,但是鼠标可以单独存在
- 组合关系:也是部分与整体的关系,但是其中的部分与整体具有统一的生命周期(UML中使用实心黑色菱形表示),比如头和嘴,没有头也就没有了嘴
怎么用?
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
新对象通过委托的方式调用已有对象中的方法。(这也是委托的意义)
为什么?
继承关系破坏了类的封闭性,父类对子类是透明的,父类中的成员都暴露给子类(这种复用称之为“白箱”复用)
子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
总结
-
我个人觉得这些所谓的设计原则,包括所有的设计模式都是为了以下目的:
-
实现代码模块的解耦,实现高内聚,低耦合!----不要牵一发而动全身
-
增强代码的可扩展性,实现程序的延续性!----不要无法实现后续新需求
-
提高代码的鲁棒性!----不要一修改,程序就崩溃
-
-
7种设计原则总结
名称 | 定义 | 使用频率 |
---|---|---|
开闭原则( Open Closed Principle,OCP ) | 软件实体应该对扩展开放,对修改关闭 | ★★★★★ |
里氏替换原则(Liskov Substitution Principle,LSP) | 父类对象出现的地方都可以使用子类对象替换 | ★★★★★ |
依赖倒置原则(Dependence Inversion Principle,DIP) | 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象 | ★★★★★ |
单一职责原则(Single Responsibility Principle,SRP) | 一个类只负责一个功能领域中的相应职责 | ★★★★☆ |
接口隔离原则(Interface Segregation Principle ,ISP) | 不要使用单一的总接口,而是使用具体的有针对性的小接口 | ★★☆☆☆ |
迪米特法则(Law of Demeter, LoD) | 一个软件实体应当尽可能少地与其他实体发生相互作用 | ★★★☆☆ |
合成复用原则(Composite Reuse Principle,CRP) | 尽量使用对象组合,而不是使用继承来达到复用的目的 | ★★★★☆ |
注:该表来源于《设计模式实训教程(第二版)-刘伟》
参考
本文主要就是以下链接的阅读笔记,详细可以阅读以下内容: