一,开闭原则
开闭原则是一开一闭,这一开呢,是对“扩展”开放;这一闭呢,是对“修改”封闭。也就是说,允许扩展,不允许修改。(Software entities should be open for extension,but closed for modification)
这里的闭,是从静态的、稳定的角度去看问题。我们的软件架构应该是相对稳定的,不能老是修改或者容易被程序员修改,不然肯定bug多多,或者到最后整个设计模式和软件架构变得一塌糊涂(颇有点缝三年、补三年、缝缝补补有三年的感觉,你想,这衣服到最后都成啥了)。因此我们应该尽量使得整个架构保持稳定,不能动不动就为了实现一个功能去修改它。
这里的开,是从动态的、发展的角度去看问题。我们的软件架构虽然要保持稳定,但也不能一成不变,总得实现新功能啊。要变,但是得有章法地变。通过“闭原则”,我们把哪些歪门邪道都给堵死了,那么“开原则”,就是要给我们一条康庄大道。理论上,通过开原则来扩展我们的软件架构,应该能使我们的软件架构保持稳定和优美。
那说着好听,怎么实现呢?其实我们可能已经不知不觉在用了,那就是用抽象类或者接口来实现开闭原则。我们把共性的、稳定的部分都抽象到抽象类或者接口中,然后将个性的、可能变化的部分放在具体的实现类中。每次我们要实现新功能时,由于我们只需要改变那些个性的、可能变化的部分,因此我们仍然可以使用原来的抽象类或者方法,只是在具体的实现上有所不同而已。这样,我们就既保证了软件架构的稳定,又提供了合理的对软件架构进行扩展的方法。
二,里氏替换原则
里氏替换原则是用子类继承父类的原则(我们刚刚的开闭原则里刚刚讨论过用继承来解决扩展的问题,可见里氏替换原则与开闭原则联系密切,可以说是针对开闭原则实现细节提出的)。
里氏替换原则之所以叫这个名字,是因为提出它的人姓里斯科夫(Liskov)。她认为,在用子类继承父类时,父类的性质也必须在子类中成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)。也就是说,父类有的性质,子类全都得有,而且得一样。
其实这是很好理解的,因为如果父类的某些性质在子类中改变了,那么随着继承树的长度增加,最后的子类岂不是面目全非?这与“继承”的概念不符啊,毕竟继承就是为了提炼共性的东西而存在的。此外,我们知道类有三个重要的性质:封装、继承和多态。继承我们刚刚说过了,那多态呢?多态简单地说就是父类能做的子类都能做,比如某个函数的某个参数是父类类型的,我们可以传入一个子类的实例,照样可以正确运行。可你一旦不按里氏替换原则来,父类的性质到最后被改的面目全非,那不一定出问题吗?好家伙,违背里氏替换原则后,类的三个性质一下子崩了俩,可见里氏替换原则多么重要。
三,依赖倒置原则
依赖倒置原则呢,就是说具体应该依赖于抽象。(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions,高层模块不应该依赖于低层模块,两者都应该依赖其抽象,抽象不应该依赖于实现细节,相反,实现细节应该依赖于抽象)
这里稍微解释一下“倒置”。我们写代码总是先构建抽象模型而不考虑实现细节,这叫做自顶向下,依赖倒置,就是下层依赖于上层,也就是实现细节依赖于设计抽象。
这个原则的作用也很好理解。抽象是相对稳定的,而细节则可能频繁被修改,因此实现细节应该依赖于我们设计好的抽象模型,这样才能保证整体架构的相对稳定。
那么,怎样才能实现这个原则呢?其实只要多使用接口和抽象类就可以了。我们都知道接口是最抽象的,只定义了规约(功能、名字、标准等等),抽象类则是部分函数给了实现,另外一些函数只给了规约,这两者是最抽象和抽象的关系。更严格地说,只要我们满足:
1、每个类都继承自一个抽象类或者实现一个接口,或者兼而有之,不应该从具体类派生
2、类的继承遵从里氏替换原则(其实父类也是对子类的相对抽象,遵循里氏替换原则就是让继承满足依赖倒置原则)
3、声明变量的类型尽量是接口或者抽象类(这样可以使用不同的实现,相当于变量只需要满足抽象类的要求即可)
就可以完全满足依赖倒置原则(dependence inversion principle,DIP)
网友评论