说到 SOLID 原则,相信有过几年工作经验的朋友都有个大概印象,但就是不知道它具体是什么。甚至有些工作了十几年的朋友,它们对 SOLID 原则的理解也停留在表面。今天我们就来聊聊 SOLID 原则以及它们之间的关系。
什么是SOLID原则SOLID 原则其实是用来指导软件设计的,它一共分为五条设计原则,分别是:
单一职责原则(SRP)
开闭原则(OCP)
里氏替换原则(LSP)
接口隔离原则(ISP)
依赖倒置原则(DIP)
单一职责原则(SRP)单一职责原则(Single Responsibility Principle),它的定义是:应该有且仅有一个原因引起类的变更。简单地说:接口职责应该单一,不要承担过多的职责。 用生活中肯德基的例子来举例:负责前台收银的服务员,就不要去餐厅收盘子。负责餐厅收盘子的就不要去做汉堡。
单一职责适用于接口、类,同时也适用于方法。例如我们需要修改用户密码,有两种方式可以实现,一种是用「修改用户信息接口」实现修改密码,一种是新起一个接口来实现修改密码功能。在单一职责原则的指导下,一个方法只承担一个职能,所以我们应该新起一个接口来实现修改密码的功能。
单一职责原则的重点在于职责的划分,很多时候并不是一成不变的,需要根据实际情况而定。单一职责能够使得类复杂性降低、类之间职责清晰、代码可读性提高、更加容易维护。但它的缺点也很明显,就是对技术人员要求高,有些时候职责难以区分。
我们在设计一个类的时候,可以先从粗粒度的类开始设计,等到业务发展到一定规模,我们发现这个粗粒度的类方法和属性太多,且经常修改的时候,我们就可以对这个类进行重构了,将这个类拆分成粒度更细的类,这就是所谓的持续重构。
开闭原则(OCP)开闭原则(Open Closed Principle),它的定义是:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。简单地说:就是当别人要修改软件功能的时候,使得他不能修改我们原有代码,只能新增代码实现软件功能修改的目的。
这听着有点玄乎,我来举个例子吧。
这段代码模拟的是对于水果剥皮的处理程序。如果是苹果,那么是一种拨皮方法;如果是香蕉,则是另一种剥皮方法。如果以后还需要处理其他水果,那么就会在后面加上很多 if else 语句,最终会让整个方法变得又臭又长。如果恰好这个水果中的不同品种有不同的剥皮方法,那么这里面又会有很多层嵌套。
if(type == apple){ //deal with apple } else if (type == banana){ //deal with banana } else if (type == ......){ //...... }可以看得出来,上面这样的代码并没有满足「对拓展开放,对修改封闭」的原则。每次需要新增一种水果,都可以直接在原来的代码上进行修改。久而久之,整个代码块就会变得又臭又长。
如果我们对剥水果皮这件事情做一个抽象,剥苹果皮是一个具体的实现,剥香蕉皮是一个具体的实现,那么写出的代码会是这样的:
public interface PeelOff { void peelOff(); } public class ApplePeelOff implement PeelOff{ void peelOff(){ //deal with apple } } public class BananaPeelOff implement PeelOff{ void peelOff(){ //deal with banan } } public class PeelOffFactory{ private Map<String, PeelOff> map = new HashMap(); private init(){ //init all the Class that implements PeelOff interface } } ..... public static void main(){ String type = "apple"; PeelOff peelOff = PeelOffFactory.getPeelOff(type); //get ApplePeelOff Class Instance. peelOff.pealOff(); }上面这种实现方式使得别人无法修改我们的代码,为什么?
因为当需要对西瓜剥皮的时候,他会发现他只能新增一个类实现 PeelOff 接口,而无法再原来的代码上修改。这样就实现了「对拓展开放,对修改封闭」的原则。
里氏替换原则(LSP)里氏替换原则(LSP)的定义是:所有引用基类的地方必须能透明地使用其子类的对象。简单地说:所有父类能出现的地方,子类就可以出现,并且替换了也不会出现任何错误。 例如下面 Parent 类出现的地方,可以替换成 Son 类,其中 Son 是 Parent 的子类。
Parent obj = new Son(); 等价于 Son son = new Son();这样的例子在 Java 语言中是非常常见的,但其核心要点是:替换了也不会出现任何的错误。这就要求子类的所有相同方法,都必须遵循父类的约定,否则当父类替换为子类时就会出错。 这样说可能还是有点抽象,我举个例子。
public class Parent{ // 定义只能扔出空指针异常 public void hello throw NullPointerException(){ } } public class Son extends Parent{ public void hello throw NullPointerException(){ // 子类实现时却扔出所有异常 throw Exception; } }