SOLID原则:单一原则、开闭原则、里氏替换原则、接口隔离原则、依赖反转原则
单一原则描述对象是类或模块,要保证职责足够单一,也就是围绕一个对象进行描述。
我们可以先写一个粗粒度的类,满足业务需求,随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们可以将这个粗粒度的类拆分成几个更细粒度的类。这就是所谓的重构。
何时需要拆分,有如下参考条件:
- 类中行数,函数,属性过多,影响到了代码的可读性、可维护性时。
- 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚低耦合的设计思想时。
- 私有方法过多时,我们需要考虑是否将私有方法独立到新的类中并设置为public,供更多类使用,提高代码复用性。
- 比较难给类起一个合适的名字,很难用一个业务名词概括,或者只能用一些笼统的Manager、Context之类的词语进行命名,这就说明类的职责定义个可能不够清晰明确。
- 类中大量的方法都集中操作某几个属性,那几可以考虑将这几个属性和对应方法拆分成新的类。
增加一个新的功能时,应该在已有代码基础上新增模块、类、方法,而不是修复以后代码,对扩展开放,对修改关闭。
这是代码可扩展性的重要表现。需要我们时刻拥有扩展意识,抽象意识,封装意识。
最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)。要为未来修改留好扩展点。
同时要注意代码的可扩展性和可读性是冲突的。
里氏替换原则子类对象能够替换程序中父类对象出现的任何地方,并保证程序原有的逻辑行为不变、正确性不被破坏。
该原则用来指导继承关系中子类应该如何设计,子类的设计要保证其在替换父类的时候不改变原有的逻辑行为,不破坏正确性。
接口隔离原则接口的使用者不应该被强迫依赖它不需要的接口。
依赖反转原则控制反转(IOC):”控制“:程序执行流程的控制,”反转“:把程序的执行控制从程序员手中转移到框架中。是一种设计思想,Spring的控制反转是通过依赖注入实现的。
依赖注入(DI):不在内部new对象,而是在外部创建好对象,再通过构造函数、函数参数等方式传递(注入)给类使用。
依赖反转(DIP):又叫依赖倒置。高层模块不依赖底层模块,高层模块和底层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。拿 Tomcat 这个 Servlet 容器作为例子。
Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。
KISS 原则
尽量保持简单,要怎么满足呢?
- 不要使用同时可能不懂的技术实现代码,比如正则。
- 不要重复造轮子,要善于使用已经有的类库。
- 不要过度优化,会牺牲代码的可读性。
YAGNI 原则
不要设计现在用不到的功能,不要过度设计
DRY 原则(Don’t Repeat Yourself)不要重复,提高代码复用性。怎么提高代码复用性?
- 减少代码耦合
当我们需要服用某个功能的时候往往希望抽取部分内容形成新的模块、类或函数,如果耦合度高则会牵一发而动全身,增加代码复用的难度。
- 满足单一职责
代码粒度越小,耦合度越小,越容易被复用。
- 模块化
将功能独立的代码进行封装,独立的模块更容易复用
- 业务与非业务分离
将非业务代码抽取成通用框架、组件、类库
- 通用代码下沉
通常我们只允许上层代码调用下层代码或者同层代码互相调用,而禁止下层代码调用上层代码。所以通用代码尽量下沉到下层。
- 多用面向对象特性
- 应用模板模式等设计模式
不该有直接依赖关系的类之间,不要有依赖。
public class NetworkTransporter { // 省略属性和其他方法... public Byte[] send(HtmlRequest htmlRequest) { //... } }
我们来看 NetworkTransporter 类。作为一个底层网络通信类,我们希望它的功能尽可能通用,而不只是服务于下载 HTML,所以,我们不应该直接依赖太具体的发送对象 HtmlRequest。从这一点上讲,NetworkTransporter 类的设计违背迪米特法则,依赖了不该有直接依赖关系的 HtmlRequest 类。
我们应该如何进行重构,让 NetworkTransporter 类满足迪米特法则呢?我这里有个形象的比喻。假如你现在要去商店买东西,你肯定不会直接把钱包给收银员,让收银员自己从里面拿钱,而是你从钱包里把钱拿出来交给收银员。
这里的 HtmlRequest 对象就相当于钱包,HtmlRequest 里的 address 和 content 对象就相当于钱。我们应该把 address 和 content 交给 NetworkTransporter,而非是直接把 HtmlRequest 交给 NetworkTransporter。
根据这个思路,NetworkTransporter 重构之后的代码如下所示:
public class NetworkTransporter { // 省略属性和其他方法... public Byte[] send(String address, Byte[] data) { //... } }
有依赖关系的类之间,尽量只依赖必要的接口
就是上文的接口隔离原则
public interface Serializable { String serialize(Object object); } public interface Deserializable { Object deserialize(String text); } public class Serialization implements Serializable, Deserializable { @Override public String serialize(Object object) { String serializedResult = ...; ... return serializedResult; } @Override public Object deserialize(String str) { Object deserializedResult = ...; ... return deserializedResult; } } public class DemoClass_1 { private Serializable serializer; public Demo(Serializable serializer) { this.serializer = serializer; } //... } public class DemoClass_2 { private Deserializable deserializer; public Demo(Deserializable deserializer) { this.deserializer = deserializer; } //... }