软件设计原则
软件设计原则——solid
开闭原则
依赖倒置原则
里氏替换原则
单一职责原则
接口分离原则
开闭原则
对扩展开放,对更改关闭。
不需要修改已经实现的模块、类、函数,就可以实现功能扩展,最关键的是抽象。
若上层对下层有依赖,那么当下层修改时,上层会被迫修改;又因为依赖下层,想要上层复用到其他模块时,也难以复用。
开闭原则在应对软件需求变更时,可以扩展新的功能,但不能修改已经写好的代码,通过抽象,定义各种个样的抽象接口,针对抽象接口编程,当变更出现时,变更的是抽象接口的各种各样的实现,而接口本身不变,接口的调用不变,从而实现开闭原则。一个类写完了,一个模块写完了,就应该是完整的,不需要再被重新修改。可以不修改代码,不修改类,不修改组件,就可以实现功能的扩展。扩展新功能时,对抽象进行新的扩展,就可以对既有代码不进行修改,就可以完成功能扩展。
可以通过以下几种设计模式,实现开闭原则。
策略模式
客户端应用程序不依赖于目标对象,而是通过定义策略接口,去依赖策略接口,我们依赖的目标对象实现策略接口,这样当我们需要新的策略时,直接创建新的策略实现类,实现策略接口即可。于是就实现了不修改代码,就扩展了功能。
适配器模式
通过适配器,把一个接口或者方法的调用,适配成另一个方法的调用。
若有不同的情况,调用下层不同的方法,则可以在上层接口和下层实现之间增加多个适配器,用于做接口转换,这样就可以去除switch case或if else条件判断。符合开闭原则。
观察者模式
将观察者注册到被观察者的成员变量中,当被观察者进行某种操作时,通知所有的观察者。这样,当需要添加新的观察者时,也是符合开闭原则的。
依赖倒置原则
高层模块不依赖于低层模块,而依赖于高层模块抽离的接口
低层模块也不依赖于高层模块,也是依赖于高层模块抽离的接口,实际上,是低层模块实现高层模块的接口。
高层模块与底层模块都依赖于抽象,而抽象是属于高层模块的。
抽象是不依赖于实现的,而恰恰相反,是实现依赖于抽象。
故而,应该是先定义高层模块的抽象,再处理实现,而不是先处理低层模块,再抽离接口。
依赖倒置,倒置的是模块或包或类的依赖关系,同时也倒置了开发顺序和职责。
依赖倒置可以使得高层被重用。
在新的环境中,如果高层定义的接口,需要重新实现,那么直接实现对接口的实现层即可,高层更容易被复用。
依赖倒置原则是非常适合于框架的,框架的特点是:框架调用应用程序,而不是应用程序调用框架,同时,框架也不依赖于应用程序。框架只需要制定规范或者给出抽象接口定义,应用程序去实现框架定义的接口即可。框架不依赖于应用程序的代码,但是可以调用应用程序的代码,就是依靠依赖倒置原则实现的。
依赖倒置原则是软件框架设计的指导原则。框架就是一个高层模块,框架调用我们编写的应用程序代码,框架不依赖应用程序代码,框架依赖于接口进行编程,而应用程序实现这个接口就可以了。软件开发中的架构是需要框架来落地的。
框架的核心被称为好莱坞原则,好莱坞的原则是 Don‘t call me, I will call you。
同理,应用程序不会调用框架, Don‘t call 框架,框架will call 业务代码,这是框架设计的核心。
框架会定义一组接口,框架基于这组接口进行开发,进行调用。
做某件事获取某个数据或者消息,传递给目标对象,目标对象是什么不重要,我们只需要传递给目标对象就可以了,就可以通过调用接口来完成,而接口由目标对象实现,从而实现了传递给目标对象,实现依赖倒置。
里氏替换原则
里氏替换原则用于解决继承的问题,衡量继承是否合理的原则。在做继承设计时,判断继承是否正确时,考量的原则是:子类是否是一个父类,是is a的关系,如果子类是一个父类,那我们就认为这个继承是合理的,比如苹果是水果,汽车是车,那我们就认为苹果继承水果,汽车继承车,但这种静态的判断,有可能是错误的,要把继承放到程序运行中的上下文中去看待,当使用父类的地方,能够使用子类来替换,不影响程序正常运行,这个继承是合理的,如果影响了程序,程序不能正常运行了,那就是不合理的设计,不是一个合适的继承。
里氏替换原则说,子类型必须能够替换掉它的基类型,那么子类就可以继承父类。如果子类不能替换父类,或者子类替换父类后,程序不能正常运行,那么这个继承就是不合理的。不符合is a的,那么一定不符合里氏替换原则。使用基类的地方,也一定适用于子类。
is a关系是关于行为的,设定和界定一个类时,应该以行为来区分。
有一些场景下d的继承是不可以的,我们应该怎么做呢?
将继承改为组合。我们为什么要继承父类?我们是需要使用父类中的一些方法吗?所以可以把父类作为子类的成员变量,不通过继承它,而通过组合它,子类不必继承父类,而是将想要继承的这个父类,变成自己的成员变量,就可以调用成员变量的各种方法。
可能违反里氏替换原则的征兆:
父类的方法中是有实现的,子类的方法是空方法,使用父类完成的事情,子类完成不了。
子类抛出了基类中不会产生的异常,使用父类的时候,不会抛出异常,使用子类的时候,抛出异常,那么当使用子类替换父类时,就可能出现问题。
单一职责原则
单一职责原则,也被成为内聚性原则,这个原则是说,一个类,应该只有一个引起它变化的原因。当一个类,引起它变化的原因有很多,那么它的职责就是不单一的。不单一的职责,就会导致程序的类的设计的复杂。内聚性原则是说相同的功能聚合在一起,相同的职责聚合在一起。
违反单一职责原则的后果是:脆弱、不可移植。
当修改其中一个功能时,另一个功能也可能收到损伤,导致程序很脆弱。
比如某一个模块,只需要关注某一个点,但是另外一个点有其他更多的依赖,则导致这个模块不得不包含自己不需要的依赖,导致不好移植和复用。
通常当一个类太大的时候,这个类的职责就不单一了,我们可以把其中一部分的功能拆解出去,把它放到另一个类里面,通过类的组合依赖,实现复杂的功能,而不是把一大堆的功能,放在一个类里面。
接口分离原则
有时,区分一个类包含了几个职责并不那么明显,他们之间有各种依赖和耦合关系,共享一些中间的状态变量。如果强行拆分到几个不同的类,使它变得职责单一,可能反而会带来问题。这种情况下,我们使用接口分离原则进行处理。
接口分离原则说,不应该强迫客户程序,依赖他们不需要的方法。
单一职责原则和接口分离原则,都是关于内聚性的,关于一个类里面应该聚合什么样的功能,关于他们的职责的原则。
设计一个接口,从客户的角度出发,不要让客户看到他们不需要的方法。强迫的后果:使编程更加容易出错,比如调错了方法。
通过接口隔离开,实现类还是有多个方法。
不同的应用程序可以依赖不同的方法。
实现类无法拆解的方法,通过接口,分别暴露给不同的应用程序,虽然实现类的方法还是跟之前一样,但是站在应用程序的角度看,看到的是不一样的方法。通过接口,隔离不同的实现类里的方法,使他们不强迫应用程序依赖他们不需要的方法。
可以通过实现多个接口的方式,实现接口隔离。
版权声明: 本文为 InfoQ 作者【猴子胖胖】的原创文章。
原文链接:【http://xie.infoq.cn/article/8b10a6a1c3d5f960607c53486】。文章转载请联系作者。
评论