设计模式之美 - 面向对象六大原则
设计模式之美 - 面向对象六大原则
- 设计模式之美 - 面向对象六大原则
- 1. 单一职责原则(Single Responsibility Principle)
- 2. 开闭原则(Open Closed Principle)
- 3. 里氏替换原则(Liskov Substitution Principle)
- 4. 接口隔离原则(Interface Segregation Principle)
- 5. 依赖倒置原则(Dependency Inversion Principle)
- 6. 迪米特原则(Least Knowledge Principle)
- KISS 原则(Keep It Simple and Stupid)
- YAGNI 原则(You Ain’t Gonna Need It)
- DRY 原则(Don’t Repeat Yourself)
设计模式之美目录:https://www.cnblogs.com/binarylei/p/8999236.html
这是设计模式系列开篇的第一篇文章。也是我学习设计模式过程中的总结。这篇文章主要讲的是面向对象设计中,我们应该遵循的六大原则。只有掌握了这些原则,我们才能更好的理解设计模式。
- 单一职责原则(SRP):一个类应该仅有一个引起他变化的原因。
- 开闭原则(OCP):对扩展是开放的,但是对修改是关闭的。
- 里式替换原则(LSP):子类可以去扩展父类的功能,但是不能改变父类原有的功能。
- 接口隔离原则(ISP):类间的依赖关系应该建立在最小的接口上。
- 依赖倒置原则(DIP):面向接口编程,而不是具体实现。
- 迪米特原则(LOD):一个对象应该对其他对象保持最小的了解。
1. 单一职责原则(Single Responsibility Principle)
单一职责原则:一个类只负责完成一个职责或者功能。如果一处修改引起了多处功能性质类似的类的变动 ,或者多个类中的代码有重复,可以考虑合并为同一个类。
但实际工作中,评价一个类的职责是否足够单一,很难有一个非常明确的、可以量化的标准,甚至是一件非常主观、仁者见仁智者见智的事情。真正的软件开发中,我们也没必要过于未雨绸缪,过度设计。所以,我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构。
单一职责原则的好处如下:
- 可以降低类的复杂度,一个类只负责一项职责,这样逻辑也简单很多。
- 提高类的可读性,和系统的维护性,因为不会有其他奇怪的方法来干扰我们理解这个类的含义。
- 当发生变化的时候,能将变化的影响降到最小,因为只会在这个类中做出修改。
2. 开闭原则(Open Closed Principle)
开闭原则:对扩展是开放,对修改是关闭。通俗的讲,添加一个新的功能时,尽量在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
如 Spring 的 ConversionService 的实现类 CompositeConversionService,如果要扩展一种新的转换方式,只需要实现 ConversionService 接口即可,不需要修改原来的代码。
private final List<ConversionService> delegates;
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
for (int i = 0; i < this.delegates.size() - 1; i++) {
ConversionService delegate = this.delegates.get(i);
if (delegate.canConvert(sourceType, targetType)) {
return delegate.convert(source, sourceType, targetType);
}
}
return this.delegates.get(this.delegates.size() - 1).convert(source, sourceType, targetType);
}
总结: 开闭原则说起来容易实施起来却很难,这要求我们要时刻具备扩展意识、抽象意识、封装意识。很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的的。特别是 23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。
3. 里氏替换原则(Liskov Substitution Principle)
里氏替换原则:子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。通俗的讲,子类可以去扩展父类的功能,但是不能改变父类原有的功能。包含以下几层意思:
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
- 子类可以增加自己独有的方法。
- 当子类的方法重载父类的方法时候,方法的形参要比父类的方法的输入参数更加宽松。
- 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。
既然有了继承和多态,那什么还会提出里氏替换原则呢?之所以这样要求,是因为继承有很多缺点,虽然是复用代码的一种方法,但同时继承在一定程度上违反了封装。父类的属性和方法对子类都是透明的,子类可以随意修改父类的成员。这也导致了,如果需求变更,子类对父类的方法进行一些复写的时候,其他的子类无法正常工作。所以里氏替换法则被提出来。
如果确保里氏替换原则落地?这要求我们的程序建立抽象,通过抽象去建立规范,然后用实现去扩展细节。里式替换原则更有意义的落地方案是 "Design By Contract"(按照协议来设计)。即子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。
4. 接口隔离原则(Interface Segregation Principle)
接口隔离原则:客户端不应该依赖他不需要的接口。通俗的讲,类间的依赖关系应该建立在最小的接口上。
比如 Spring 将 BeanFactory 暴露给用户,将 ConfigurableListableBeanFactory 暴露给开发者。
5. 依赖倒置原则(Dependency Inversion Principle)
依赖倒置原则:一种特殊的解耦方式,使得高层次的模块不应该依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。这也是一个让人难懂的定义,简单来说就是面向接口编程,而不是具体实现:
- 上层模块不应该依赖于下层模块,两者都应该依赖于抽象;
- 抽象不应该依赖于实现,实现应该依赖于抽象。
在 Java 中抽象指的是接口或者抽象类,两者皆不能实例化。而细节就是实现类,也就是实现了接口或者继承了抽象类的类,是可以被实例化的。上层模块指的是调用端,下层模块是具体的实现类。在 Java 中,依赖倒置原则是指模块间的依赖是通过抽象来发生的,实现类之间不发生直接的依赖关系,其依赖关系是通过接口是来实现的。这就是俗称的面向接口编程。
6. 迪米特原则(Least Knowledge Principle)
迪米特原则:也被称为最小知识原则,一个对象应该对其他对象保持最小的了解。通俗的讲,不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分,一旦发生变化,需要了解这一变化的类就会比较少。
利用这个原则,能够帮我们实现代码的 "高内聚、松耦合"。实际上,"高内聚、松耦合" 是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。"高内聚、松耦合" 能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。高内聚用来指导类本身的设计,松耦合用来指导类与类之间依赖关系的设计。
KISS 原则(Keep It Simple and Stupid)
KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的 "简单" 并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。对于如何写出满足 KISS 原则的代码,有下面几条指导原则:
- 不要使用同事可能不懂的技术来实现代码;
- 不要重复造轮子,要善于使用已经有的工具类库;
- 不要过度优化。
YAGNI 原则(You Ain’t Gonna Need It)
YAGNI 原则直译过来就是:你不需要它。这条原则也算是万金油了。当用在软件开发过程中,不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。
DRY 原则(Don’t Repeat Yourself)
DRY 原则(Don’t Repeat Yourself)三种典型的代码重复:实现逻辑重复、功能语义重复和代码执行重复。这三种代码重复,有的看似违反 DRY,实际上并不违反;有的看似不违反,实际上却违反了。
(1)实现逻辑重复:比如 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 分别处理 @Autowired 和 @Resource 依赖注入时,实现的逻辑都差不多。它们的逻辑虽然重复,但并不违反 DRY 原则,反而有一种重复之美,正如那句老话,好代码千篇一律,烂代码花样百出。
AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
-> findAutowiringMetadata
-> buildAutowiringMetadata
-> InjectionMetadata#checkConfigMembers
AutowiredAnnotationBeanPostProcessor#postProcessProperties
-> InjectionMetadata#inject
-> AutowiredFieldElement#inject
-> AutowiredMethodElement#inject
CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
-> findResourceMetadata
-> buildResourceMetadata
-> InjectionMetadata#checkConfigMembers
CommonAnnotationBeanPostProcessor#postProcessPropertyValues
-> InjectionMetadata#inject
-> ResourceElement#inject
-> WebServiceRefElement#inject
-> EjbRefElement#inject
(2)功能语义重复:比如两个功能相同的方法,虽然代码没有重复,但毫无疑问肯定违反 DRY 原则。
(3)代码执行重复:方法可能被没有必要的多次调用。
每天用心记录一点点。内容也许不重要,但习惯很重要!