https://blog.csdn.net/chenyiming_1990/article/details/22210045
良好的编程原则与良好的设计工程原则密切相关。本文总结的这些设计原则,帮助开发者更有效率的编写代码,并帮助成为一名优秀的程序员。
- 1.避免重复原则(DRY – Don’t repeat yourself)
编程的最基本原则是避免重复。在程序代码中总会有很多结构体,如循环、函数、类等等。一旦你重复某个语句或概念,就会很容易形成一个抽象体。
- 2.抽象原则(Abstraction Principle )
与DRY原则相关。要记住,程序代码中每一个重要的功能,只能出现在源代码的一个位置。
- 3.简单原则(Keep It Simple and Stupid )
简单是软件设计的目标,简单的代码占用时间少,漏洞少,并且易于修改。
- 4.避免创建你不要的代码 Avoid Creating a YAGNI (You aren’t going to need it)
除非你需要它,否则别创建新功能。
- 5.尽可能做可运行的最简单的事(Do the simplest thing that could possibly work)
尽可能做可运行的最简单的事。在编程中,一定要保持简单原则。作为一名程序员不断的反思“如何在工作中做到简化呢?”这将有助于在设计中保持简单的路径。
- 6.别让我思考(Don’t make me think )
这是Steve Krug一本书的标题,同时也和编程有关。所编写的代码一定要易于读易于理解,这样别人才会欣赏,也能够给你提出合理化的建议。相反,若是繁杂难解的程序,其他人总是会避而远之的。
- 7.开闭原则(Open/Closed Principle)
你所编写的软件实体(类、模块、函数等)最好是开源的,这样别人可以拓展开发。不过,对于你的代码,得限定别人不得修改。换句话说,别人可以基于你的代码进行拓展编写,但却不能修改你的代码。
- 8.代码维护(Write Code for the Maintainer)
一个优秀的代码,应当使本人或是他人在将来都能够对它继续编写或维护。代码维护时,或许本人会比较容易,但对他人却比较麻烦。因此你写的代码要尽可能保证他人能够容易维护。用书中原话说“如果一个维护者不再继续维护你的代码,很可能他就有想杀了你的冲动。”
- 9.最小惊讶原则(Principle of least astonishment)
最小惊讶原则通常是在用户界面方面引用,但同样适用于编写的代码。代码应该尽可能减少让读者惊喜。也就是说,你编写的代码只需按照项目的要求来编写。其他华丽的功能就不必了,以免弄巧成拙。
- 10.单一责任原则(Single Responsibility Principle)
某个代码的功能,应该保证只有单一的明确的执行任务。
- 11.低耦合原则(Minimize Coupling)
代码的任何一个部分应该减少对其他区域代码的依赖关系。尽量不要使用共享参数。低耦合往往是完美结构系统和优秀设计的标志。
- 12.最大限度凝聚原则(Maximize Cohesion)
相似的功能代码应尽量放在一个部分。
- 13.隐藏实现细节(Hide Implementation Details)
隐藏实现细节原则,当其他功能部分发生变化时,能够尽可能降低对其他组件的影响。
- 14.迪米特法则又叫作最少知识原则(Law of Demeter)
该代码只和与其有直接关系的部分连接。(比如:该部分继承的类,包含的对象,参数传递的对象等)。
- 15.避免过早优化(Avoid Premature Optimization)
除非你的代码运行的比你想像中的要慢,否则别去优化。假如你真的想优化,就必须先想好如何用数据证明,它的速度变快了。
“过早的优化是一切罪恶的根源”——Donald Knuth
- 16.代码重用原则(Code Reuse is Good)
重用代码能提高代码的可读性,缩短开发时间。
- 17.关注点分离(Separation of Concerns)
不同领域的功能,应该由不同的代码和最小重迭的模块组成。
- 18.拥抱改变(Embrace Change)
这是Kent Beck一本书的标题,同时也被认为是极限编程和敏捷方法的宗旨。
许多其他原则都是基于这个概念的,即你应该积极面对变化。事实上,一些较老的编程原则如最小化耦合原则都是为了使代码能够容易变化。无论你是否是个极限编程者,基于这个原则去编写代码会让你的工作变得更有意义。
作者简介:Christopher Diggins是加拿大一位有25年编程经验的资深技术人员,曾效力于Microsoft和AutoDesk,并创办过两家赢利的互联网公司。
他是《C++ Cookbook》的作者之一,并自己编写了一门编程语言Heron。
软件设计七大原则
1、开闭原则(OCP) 对扩展开发 对修改关闭 就是在不影响原有功能的基础上进行扩展 实现方法:接口实现
https://www.cnblogs.com/my_life/articles/5464542.html
2、依赖倒置原则(DIP) 抽象不依赖于细节,细节应该依赖抽象 实现方法:先抽象后细节 接口和抽象类
3、单一职责原则(SRP)一个类尽量承担一种职责 如果有多个职责可以通过接口拆分(一个类实现多个接口)
4、接口隔离原则(ISP)设计接口的时候需多个专门的接口,而不是单一的总接口
5、迪米特原则( LOD)一个对象应该对其他对象保持最少的了解,又叫最少知道原则 强调的是降低类与类之间的耦合
6、里氏替换原则(LSP)子类可以扩展父类的功能(意味着可以替换父类,并且原有逻辑不变),但是不能改变父类原有的功能 实现方法:继承
https://juejin.cn/post/6844903940631461902
SOLID: 软件设计原则主要归纳为以下五点
高内聚、低耦合,封装、继承、多态。
简称 | 英文名 | 中文名 |
---|---|---|
SRP | The Single Responsibility Principle | 单一责任原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
- S: 单一责任原则,注重的是职责,主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节。
- O: 开闭原则,对新增开放,对修改关闭。主要是用多态性,面向接口面层。
- L: 里氏替换原则,父类可用的情况下,子类也可以使用。也就是说子类条件更严格。
- I: 接口分离原则,注重对接口依赖的隔离,主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
- D: 依赖倒置原则,高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象,主要是面向接口编程而非面向实现编程。
单一责任原则和接口隔离原则的区别:前者高内聚,后者低耦合。
单一职责原则:注重的是职责,主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节。
接口隔离原则:注重对接口依赖的隔离,主要约束接口,主要针对抽象,针对程序整体框架的构建。
https://segmentfault.com/a/1190000020509327
如上图所示,这就是软件设计的六大原则。
其中我认为最重要的就是开闭原则,可以理解为这是最终目的。其余的五种原则都是为了达到这个目的而存在的。
要理解开闭原则,则需要先看看另外五个原则。
单一职责(Single Responsibility Principle)
单一职责原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。
个人理解
这是单一职责的概念,我对这个原则的理解是,一个类最好只有一种职责,返回来说就是,把相同的职责放到一个类中。这样就实现了高内聚。
职责可以从各种维度去切割,比如以中间件为维度拆分类,或者以业务规则为维度拆分类等等。
想要将职责才分的恰到好处,需要对业务领域的深入理解,和对软件设计的经验。
例子
举个例子,用户模块的业务需要MySql
和Redis
这两个中间件来支持。
如果我创建一个UserRepository
,里面既有访问MySql
存储数据的方法,又有访问Redis
存储数据的方法,那么可以理解为这个类有两个职责,访问MySql
和Redis
。
那么可以创建两个类:UserRepository
封装访问MySql
的方法,UserRedisRepository
封装访问Redis的方法,这种类的职责就单一了。
再举个例子,天气模块中的类WeatherService
有一个方法getWeather()
,该方法会发起HTTP请求访问第三方资源接口获取天气数据(返回JSON格式),随后将JSON数据转换成WeatherEntity
。
这里最好在进行一次拆分,可以把请求第三方接口当作一种职责,提供一个WeatherProxy
类,然后将数据转换作为一种职责,提供一个WeatherConverter
类。
好处
降低类的复杂度、提高可读性、提高可维护性、提高扩展性、降低了变更引起的风险。
里氏替换(Liskov Substitution Principle)
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。所有引用基类的地方必须能透明地使用其子类的对象,通俗的说,就是派生类(子类)对象可以在程序中代替其基类(父类)对象。
个人理解
通俗的理解是,在使用父类的地方可以任意使用其子类,但是在使用子类的地方不一定能使用父类。
例子
最简单的一个例子,在Java中,大家都有使用过List<T>
,它的实现最初可以是ArrayList
,随后也可以修改成LinkedList
。
在做修改时,List<T>
的使用并不会被影响。
List<T> list = new ArrayList<>();
list.add(...);
list.forEach(t -> {...});
上面代码中,可随意切换List<T>
的实现类,对后续使用的代码不会造成任何影响。
List<T> list = new LinkedList<>(); // 修改实现类不会对下面的代码造成影响
list.add(...);
list.forEach(t -> {...});
上面的例子中的list.forEach
就实现了里氏替换原则,该方法接收任何List
对象,此处传递ArrayList
或LinkedList
都是可以的,做到了子类可以在程序中代替其父类。
好处
增强程序的健壮性,即使切换了子类,不会对使用代码造成影响。
依赖倒置原则(Dependence Inversion Principle)
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
个人理解
这里应该指的是面相接口编程,说的是对象与对象之间的依赖关系。
通俗的说就是把业务的功能划分清楚后,定义在接口和抽象类中,然后编写业务类继承或实现该接口或抽象类,如果对外发布封装的业务功能,要发布接口或抽象类,而不是具体的实现类。
例子
依赖倒置在工作中是最常使用的。
项目中常用的分层架构,Controller层,Service层,Dao层。Controller层会调用Service层,Service层会调用Dao层。
假如使用Spring Framework
开发用户模块,编写数据访问层的时候,都会编写一个UserDao
接口,然后编写一个UserDaoImpl
的实现类。UserServiceImpl
会创建一个UserDao
对象,然后使用@Autowired
自动装配。
@Service("userService")
public class UserServiceImpl implements UserService {
....
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
private UserDao userDao;
}
这里就用到了依赖倒置原则,依赖抽象接口而不依赖于具体的实现类。
好处
可以减少类见的耦合性,提高系统的稳定性,可高代码的可读性和可维护性,也可以降低同时开发时引起的风险。
接口隔离原则(Interface Segregation Principle)
拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。
个人理解
设计接口的时候,尽量不要设计出一个拥有非常多的方法的庞大接口,应该尽量使用多个专门提供某种功能的接口。
例子
还是用开发用户模块举例,在编写根据手机号登录,如果登录不存在则注册的时候,需要用到发送短信验证码、验证短信验证码、登录、注册等功能。
此时,如果根据接口隔离原则,就需要登录将一个LoginService
接口拆分成多个专门提供某种功能的接口,例如:
LoginService
接口,里面有登录功能;PinCodeService
接口,里面有发送PinCode和验证PinCode的功能;RegeditService
接口,里面有注册的功能;
那么在登录时有可能会用到这三个接口。
此时,还有一个业务场景,修改手机号时,需要输入旧的手机号,获取Pin码并验证,然后输入新的手机号,获取Pin码并验证,最后调用修改电话号的功能。那么在修改手机号时只需要用到PinCodeService
即可,不需要使用LoginService
和RegeditService
。
这就是接口隔离原则的一个实现例子。
满足接口隔离原则的同时,先满足单一职责原则。
好处
使程序解耦,从而容易重构、更改和重新部署。
最少知识原则(Least Knowledge Principle)
又叫迪米特法则,系统中的类,尽量不要过多的与其他类互相作用,减少类之间的耦合度。
- 每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元;
- 每个单元只能和它的 "朋友" 交谈,不能和 "陌生人" 交谈;
- 只和自己直接的 "朋友" 交谈;
个人理解
一个对象调用另一个对象时,应该不去过分关注这个对象的实现。被调用的对象的内部是如何复杂都和调用者无关,简单的说,调用者就知道被调用者提供的的public方法就行,其他的不需要关心。
例子
假设一个场景,老板要统计公司的人数,可以这个命令委托给主管,由主管去统计人数。
public class Boss {
public void command(Manager manager) {
....
}
}
public class Manager {
private List<Employee> employees;
public Manager(List<Employee> employees) {
this.employees = employees;
}
public int employeeCount() {
return this.employees.size();
}
}
public class Employee {}
public class Client {
public static void mand(String[] args) {
List<Employee> employees = new ArrayList<>();
... 添加20个employee
Boss boss = new Boss();
boss.command(new Manager(employees));
}
}
其中Boss
只与Manager
进行交谈,Manager
只与Employee
进行交谈,Boss
不与Employee
进行交谈,这就是最少知识原则的一个例子。
好处
类负责的职责清晰明了,使类可以高度内聚降低类与类之间的耦合。
开闭原则(Open Close Principle)
软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。
个人理解
我对开闭原则的理解是,程序应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。面向对象可以达到这种方式完成对软件的扩展。
在对软件进行设计时,应该尽可能的考虑到后续会变更的地方,以便在变更真发生时,可以尽可能在不修改或修改很少的情况下,把变更实现。
例子
开闭原则是最基础的设计原则,其它的五个设计原则都是开闭原则的具体形态。
好处
开闭原则可以提高复用性,维护性,减少测试范围。
https://zhuanlan.zhihu.com/p/68274707
0x01.开闭原则
- 定义:一个软件实体如类,模块和函数应该对扩展开放,对修改关闭
- 要点:
- 当变更发生时,不要直接修改类,而是通过继承扩展的方式完成变更
- 用抽象构建框架,用实现扩展细节
- 预先设计好抽象
- 优点:提高软件系统的可复用性及可维护性
- 面向抽象编程,实现抽象化,抽象出业务模型
- 提高内聚,降低耦合
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/openclose
0x02.依赖倒置原则
- 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 这个设计原则比较好理解,首先高层模块只接口或者父类,接口或父类不该依赖其派生子类
- 抽象不应该依赖细节;细节应该依赖抽象。
- 针对接口编程,不要针对实现编程。
- 优点:可以减少类间的耦合性,提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/dependenceinversion
0x03.单一职责原则
- 定义:不要存在多余一个导致类变更的原因,就一个类而言,应该只存在一个导致其变更的原因。
- 一个类/接口/方法只负责一项职责
- 降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/singleresponsibility
0x04.接口隔离原则
- 用多个专门的接口,而不是使用单一的总接口,客户端不应该依赖它不需要的接口
- 一个类对一个类的依赖应该建立在最小的接口上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
- 注意适度原则,一定要适度,否则会造成类爆炸
- 优点:符合我们常说的高内聚低耦合的设计思想,从而使类具有很好的可读性,可扩展性和可维护性
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/interfacesegregation
0x05.迪米特法则(最少知道原则)
- 定义:一个对象应该对其他对象保持最少的了解。又叫最少知道原则
- 尽量降低类与类之间的耦合
- 降低类之间的耦合
- 强调只和朋友交流,不和陌生人说话
- 朋友:出现在成员变量,方法的输入,输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
- 类与类之间的引用,尽量只是直接关系的引用,避免不必要的耦合。
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/demeter
0x06.里氏替换原则
- 定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
- 定义扩展:一个软件实体如果适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
- 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。
- 含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 含义2:子类中可以增加自己特有的方法
- 含义3:当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
- 含义4:当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。
- 优点1:约束继承泛滥,开闭原则的一种体现。
- 优点2:加强程序的健壮性,同事变更时也可以做到非常好的兼容性,提高程序的维护性、扩展性。降低需求变更时引入的风险。
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/liskovsubstitution
0x07.合成/复用原则(组合/复用原则)
- 定义:尽量适用对象组合/聚合,而不是继承关系达到软件复用的目的
- 聚合has-A和组合contains-A
- 优点:可以适用系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少
- 何时适用合成/聚合,继承
- 聚合has-A, 组合contains-A, 继承 is-A
- 样例代码:https://github.com/sigmako/design-pattern/tree/master/design-principle/src/main/java/org/ko/design/principle/compositionaggregation
0x08.代码工程
七大设计原则
: https://github.com/sigmako/design-pattern/tree/master/design-principle
0x09.参考
慕课网设计模式精讲
: https://coding.imooc.com/class/270.html