设计模式六大原则

设计模式六大原则总结

1. 单一职责原则

单一职责原则,Single Responsibility Principle,简称SRP。

(1) 定义

应该有且仅有一个原因引起类的变更。
There should never be more than one reason for a class to change.

(2) 优点
  • 类的复杂性降低,职责定义清晰
  • 可读性提到,复杂性降低
  • 可维护性提高
  • 需求变更时引起的风险降低
(3) 示例

如下有一个IPhone接口用于实现通话,包含了两个职责:一个是协议管理,一个是数
据传送。dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现
的是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递
过来的信号还原成我们听得懂的语言。

public interface IPhone {
	//拨通电话
	public void dial(String phoneNumber);
	//通话
	public void chat(Object o);
	//通话完毕,挂电话
	public void hangup();
}

以上代码对应类图,
重构前
按照单一职责重构以后, 将协议管理和数据传输两个职责拆分开,数据传输依赖协议管理。
重构以后

2. 里式替换原则

里氏替换原则,Liskov Substitution Principle,简称LSP。

(1)定义:

所有引用基类的地方必须能透明地使用其子类的对象。
具体包含4层含义:

  • 子类必须完全实现父类的方法
    如下图,手枪、步枪都继承了shoot()方法,支持射击.
    在这里插入图片描述
    在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明
    类的设计已经违背了LSP原则。
    如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发
    生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

  • 子类可以有自己的个性
    狙击枪继承自Rife,但新增加了狙击枪特有的zoomOut()的方法。
    在这里插入图片描述

  • 覆盖或实现父类的方法时输入参数可以被放大
    子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。子类的输入参数类型的范围扩大了,子类代替父类传递到调用者中,子类的方法永远都不会被执行,即还是执行父类的方法,这个是正确的。当然,如果需要想让子类的方法运行,就必须覆写父类的方法。

  • 覆写或实现父类的方法时输出结果可以被缩小
    父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。
    保证用子类的对象代替父类后,子类对应方法的返回值仍然可以被接收。

3. 依赖倒置原则

依赖倒置原则,Dependence Inversion Principle, 简称DIP。

(1)定义

原则:
High level modules should not depend upon low level modules.Both should depend upon
abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
包含三层含义:
● 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
● 抽象不应该依赖细节;
● 细节应该依赖抽象
高层模块和低层模块容易理解,每一个逻辑的实现都是由原子逻辑组成的,不可分割的
原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象?什么又是细节
呢?在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实
现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是
可以加上一个关键字new产生一个对象。依赖倒置原则在Java语言中的表现就是:
● 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过
接口或抽象类产生的;
● 接口或抽象类不依赖于实现类;
● 实现类依赖接口或抽象类。

依赖的三种实现方式:

  1. 使用构造函数传递依赖对象
    在这里插入图片描述
  2. 使用Setter方法传递依赖对象
    在这里插入图片描述
  3. 接口声明依赖对象
    在这里插入图片描述
(2) 示例

如下图,Client 直接依赖的Driver和Benz都不是抽象的,如果车不是Benz, 换辆车就需要重新实现一次,拓展性很差。
在这里插入图片描述
按照依赖倒置原则进行重构之后, Driver 继承抽象接口IDriver, 实体类车BMW 和 Benz继承ICar,抽象接口IDriver 依赖抽象类ICar.
在这里插入图片描述
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,我们怎么在项目中使用这个规则呢?只要遵循以下的几
个规则就可以:
● 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
● 变量的表面类型尽量是接口或者是抽象类
● 任何类都不应该从具体类派生
● 尽量不要覆写基类的方法(尽可能的使用基类的方法)
● 结合里氏替换原则使用。接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

4. 接口隔离原则

(1)定义

● Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依
赖它不需要的接口。)
● The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。)

包含4层含义:

  • 接口要尽量小,但必须满足单一职责原则。
  • 接口要高内聚。高内聚就是提高接口、类、模块的处理能力,减少对外的交互。
    要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
  • 只提供访问者需要的方法。
  • 接口设计是有限度的。粒度越小,系统越灵活,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低。因此要适度。
(2)示例

如下图,IPettyGirl实现的方法应该分为两类,描述美女外在容貌的方法,包含goodLooking() 和 niceFigure();描述内在品行的方法,包含greatTemperament(). 这样实现就没有办法搜索某一类型的美女。
在这里插入图片描述
按照如下图的方式进行重构以后,抽象为两个接口,支持特定类型的美女搜索。
在这里插入图片描述

5. 迪米特法则

迪米特法则,Law of Demeter,简称LoD。也称为最少知识原则 (Least Knowledge
Principle,LKP)。

(1)定义

一个对象应该对其他对象有最少的了解。

4层含义:

  • 只与直接的朋友通信。
    什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的
    耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。
    如下图,Teacher 只需要下发Commond给GoupLeader, 并不需要依赖Girl。正确的依赖关系应该是Teacher -> GroupLeader -> Girl, 大家都和直接的朋友打交道就可以了。
    在这里插入图片描述
    重构后的类图如下:
    在这里插入图片描述
  • 朋友间也是有距离的
    如下图Wizard暴露的三个接口可以封装到一个接口给InstallSoftWare。在这里插入图片描述
    重构后只暴露了一个接口。在这里插入图片描述
  • 是自己的就是自己的
    如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以
提高。

6. 开闭原则

(1)定义

Software entities like classes,modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)

软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。

软件实体包括以下几个部分:
● 项目或软件产品中按照一定的逻辑规则划分的模块。
● 抽象和类。
● 方法

使用开闭原则的优点

  • 减少测试工作量和风险。因为尽量不修改原先逻辑,测试范围集中在新增接口或者新增类。
  • 提高代码的复用性。新的需求实现尽可能复用之前的实现,在之前的实现上进行拓展。
  • 提高可维护性。
    一款软件投产后,维护人员的工作不仅仅是对数据进行维护,还可能要对程序进行扩展,维护人员最乐意做的事情就是扩展一个类,而不是修改一个类,甭管原有的代码写得多么优秀还是多么糟糕,让维护人员读懂原有的代码,然后再修改,是一件很痛苦的事情,不要让他在原有的代码海洋里游弋完毕后再修改,那是对维护人员的一种折磨和摧残。(在理啊~)
  • 更适合面向对象的开发
    万物皆对象,我们需要把所有的事物都抽象成对象,然后针对对象进行操作,但是万物皆运动,有运动就有变化,有变化就要有策略去应对,怎么快速应对呢?这就需要在设计之初考虑到所有可能变化的因素,然后留下接口,等待“可能”转变为“现实”。
(3) 示例

加入NovelBook 如果实现打折计价的功能,最好的方法是派生出一个新类OffNovelBook 并重写其getPrice()方法,这样对系统影响最小。
在这里插入图片描述

(4)应用

开闭原则的应用的方法:

  1. 抽象约束
    抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
    第一,通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;
    第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
    第三,抽象层尽量保持稳定,一旦确定即不允许修改。

  2. 元数据(metadata)控制模块行为
    什么是元数据?用来描述环境和数据的数据,通俗地说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。

  3. 制定项目章程
    在一个团队中,建立项目章程是非常重要的,因为章程中指定了所有人员都必须遵守的约定,对项目来说,约定优于配置。(按照统一的方式进行拓展实现。)

  4. 封装变化
    第一,将相同的变化封装到一个接口或抽象类中;
    第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。
    封装变化,也就是受保护的变化(protected variations),找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口,准确地讲是封装可能发生的变化,一旦预测到或“第六感”发觉有变化,就可以进行封装,23个设计模式都是从各个不同的角度对变化进行封装的,我们会在各个模式中逐步讲解。

——————————————
本文参考《设计模式之禅》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值