前言
软件开发时,一些基本的原则和设计模式还是需要去详细了解的。软件越大越负责,就越需要设计模式等,将软件构造成高扩展、高可用、高维护。更多的将软件结构架构清晰。
软件开发基本原则
不要重复自己(DRY原则)
代码尽量不要重复,少用复制粘贴之类的。2个方法一半以上代码都一样,就尽量考虑抽离出变化的参数,写成通用的方法。
尽量简单、一目了然(KISS原则)
代码的方法尽量写的明确,保证一个方法只做一件基本的事情。当方法过大时,首先,考虑方法的名字参数足够清晰,其次,对于方法中的步骤进行有效注释,保证一步步了解具体的用途和实现。
适可而止(YAGNI原则)
程序功能的设计,尽量包含主要的功能,完成主要的任务,对于次要的功能,分配好时间比例,且考虑不影响主要功能。
基本原则
单一职责原则(SRP)
核心:系统中每个对象保证只有单一的职责存在,对象关注自身职责。对外提供一种功能, 引起类变化的原因应该只有一个。
解读:保证对象的职责划分。例如User,代表的就是用户实体,对于用户之间的关联,应该由其他的服务提供,它只需要提供基本的CRUD,能够建立起userDAO、userService等。这也不绝对,有时候,和user关系特别紧密的方法,也会被划归到userService中。其次,对一个类而言,应该仅有一个引起它变化的原因(ASD),让类的职责单一。当频繁的需求,改动基础类user时,此时就要考虑,这些改动是不是必要的,变化的动机是不是都和user有非要的关系。
开闭原则(OCP)
核心:对外扩展开放,对内修改关闭。保证不修改现有类代码,增加代码实现功能。
解读:一般在设计类时,将需要放开使用的接口或方法,设计成公开的,将需要自己内部方法调用的,不能让其他方法调用的,设计成私有的。如此,对外就值暴漏了必要的接口。保证了类提供的方法单一,同时也不会过多开放无用接口。对于新功能,不可避免的要修改原有类,此时,不进行更好的设计,容易造成后期,类的方法提供已然不单一,无法或者很难添加新功能。所以,刚开始设计类时,以类创建的目的考虑,对外提供接口,保证接口的职责单一。当有新功能时,考虑这对单一的接口有没有影响,(新功能的变化是不是和原接口的变化接近同一个级别的变动)如果有,考虑抽象接口,或分离出此类,或提供多个接口,如此,不断微小改动,适应后期变化。
里氏替换原则(LSP)
核心:子类型必须能够替换掉父类型
解读:重点在于继承,保证良好的继承存在。能够使用父类,那么代码中所有出现父类的地方换成子类,也是完全可运行。子类是父类的扩展存在,不过度影响原父类。
依赖注入原则(DIP)
核心:即依赖倒转。抽象不应该依赖细节,细节应该依赖抽象。
解读:针对接口编程,不要针对实现编程。典型的,就是采取List作为参数或者变量使用,一切方法的调用都是采取List。具体的List实现,可以随时进行更换–ArrayList、LinkedList。编程时也是尽量采取接口编程,调用接口,而不是立刻采取具体的实现方法调用。
迪米特原则(CARP)
核心:即最少知道原则,每个类应当尽量降低成员的访问权限。
解读:主要强调类直接解耦。各个类直接,尽量减少发生实际的互相调用。尽量依赖于接口,保证接口提供出必要功能即可。如此,不同的类内部修改,但是对外提供的方法功能也不会发生太多变化。
接口分离原则(ISP)
核心:不应该强迫客户程序依赖它们不需要使用的方法
解读:接口提供的功能尽量单一。提供服务一般都是service,那么如果和domain相关的service,就尽量提供仅domain内的方法,其他和domain关系不密切的,提供新的接口和service。保证其他类的调用不会过多干扰。
优先使用组合而不是继承原则(CARP)
核心:优先使用组合,而不是继承
解读:采取继承时,如果父类发生一些主要接口的变动,或者字段的变动。那么,所有继承的子类都会受到影响,反过来也会导致你修改父类影响范围过大。不紧密关联的,采取组合,能够更好的应对变化。
程序趋向稳定
整体
进行系统或者程序设计时,要保证程序不断的清晰,不断的明了。比如,一个需求,将它进行拆分,由大的业务方法,不断调用中转,最后保证由每个基本方法实现。保证结构的清晰。
由确定到不确定
方法直接进行调用时,都是隐含一些确定逻辑的。所以设计时,就要考虑到这种隐藏状态,后面的设计都要基于此,尽量不要破坏。
例如:我们有个业务,需要查询user,如果没有,进行insert操作。如果图简便,就是简单的如下方法:
public User queryOrInsert(User user);
次方法就冗余了,包含了查询和插入,本来调用方法时,就知道此方法的状态,而现在就慢慢的隐去了user存在的逻辑,交与了方法内部判断。如果此时,我要只查询用户数据呢,可能写成如下方法:
public User queryOrInsert(User user, Integer isOnlyQuery);
本来就是仅仅查询,由于方法不够解耦,导致再次复用时,再原基础上,增加了系统的不确定性。更好的是应该是将此独立写成如下:
public User query(User user);
public Integer insert(User user);
这个user的查询和新增应该有其他地方来,userService只提供基本的方法。
系统从大方法到最后的基础方法,应该是不断明确出已知的状态。
基本方法的复杂
有时候,方法设计时,需要考虑它本来的基础方法,不要过多的添加常识之外的方法。会造成后期的排查复杂。
例如:
private Integer age; //年龄
public Integer getAge() { //未知的复杂
return (this.age == null || this.age < 0) ? 0 : this.age;
}
public Integer getAge() { //正常
return this.age;
}
如上,getAge()就应该提供基本的返回age方法,不要添加任何逻辑。age是应该<0,但是这是其他地方控制的。如果我们修改了getAge(),如第一个方法那样,虽然少了判断,当时用着方便,以后出现问题或者业务变更时,就是隐藏所在了。 对于此部分,可参考如下文章,从“熵”的角度进行了详细的考虑,值得看看。