二、软件设计原则
一、软件设计原则
1.开闭原则
开闭原则就是对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
比如笔记本会预留有一些USB接口,不管是U盘还是一些什么其他的外接设备,例如鼠标、键盘,我们都可以随插随用了,即实现了一个热插拔的效果,也更加方便我们进行扩展。
2.里氏代换原则
里氏代换原则指任何基类可以出现的地方,子类一定可以出现。可以理解成子类可以扩展父类的功能,但不能改变父类原有的功能,指的就是在Java里面通常都会有父子类的关系,一般而言,我们都会将子类中的功能抽取到父类中,以提高代码的复用性,而在子类中,我们只需要去定义子类特有的功能即可。
换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。为什么呢?因为如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性就会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
你想啊,要是在父类中已经声明了一个方法,而你又在子类中再进行了一个重写,那么在父类中定义的方法是不是就没有任何意义了?如果说父类定义规则,要求子类必须重写,那么在父类中只需要定义成抽象的方法就可以了。
3.依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
4.接口隔离原则
接口隔离原则是指客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上面。
5.迪米法特原则
迪米特法则,又叫最少知识原则。其表示的含义是只和你的直接朋友交谈,不跟"陌生人"说话。
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
举个例子,如果一个人想要租房的话,那么他找的就该是房屋中介了,而不是直接的房主;再来看这样一个例子,明星和粉丝进行见面的话,肯定是由经纪人来进行安排的。
最后,再看一个例子,假设现在有家公司需要一个办公软件,那么这家公司是直接找具体的软件工程师,还是找某家开发软件的公司啊?肯定是软件公司,而软件公司又会有许多软件工程师,所以它会把甲方需要的办公软件交给具体的软件工程师去开发。其实,软件工程师在去开发软件的时候,很多情况下,他是压根就不知道到底是哪个甲方需要这个软件的,因此他是不需要直接去和甲方沟通的。如果要沟通的话,那么可以通过第三方转发进行沟通。也就是说,软件工程师把他在开发中遇到的问题告诉公司,公司再跟甲方进行沟通,这样,就可以降低软件工程师和甲方之间的耦合度了,继而提高模块的相互独立性。
6.合成复用原则
合成复用原则是指尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用可分为继承复用(使用继承实现代码的复用性)和合成复用(使用组合或者聚合实现代码的复用性)两种。
(1)继承复用
优点是简单、易实现。缺点是继承复用破坏了类的封装性。为什么说破坏了类的封装性呢?因为继承会将父类的实现细节暴露给子类,也就是说子类可以直接去继承父类中的功能,这样,子类就可以将父类中的功能给覆盖掉了,所以,父类对子类是透明的。其实,这种复用我们又可称为"白箱"复用。
子类与父类的耦合度高。为什么说耦合度高呢?因为我们之前就已经讲过了,继承它本身就比组合、聚合的耦合度高。这样,父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
继承限制了复用的灵活性。如何理解呢?从父类继承而来的实现是静态的,这是因为在编译时就已经定义好了,所以在运行时是不可能发生变化的。我们总不能说,在程序运行过程中,来解除子类和父类的继承关系吧!况且,这也是无法实现的,所以我们才说继承限制了复用的灵活性
(2)组合或聚合复用
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,那么新对象就可以调用已有对象的功能了。所以,它相比于继承复用有以下优点:维护了类的封装性。为什么说维护了类的封装性呢?因为成员对象的内部细节是对新对象不可见的,也就是说新对象不知道成员对象里面的具体的实现,但是可以调用其功能,所以这种复用又被称为"黑箱"复用。
对象间的耦合度低。我们之前就讲过,组合或者聚合本身就比继承的耦合度低。当我们真正去使用组合或者聚合复用时,我们可以在类的成员位置声明抽象父类或者父接口,这样,我们就能动态地去传递该抽象类或者父接口的子类对象了
复用的灵活性高。这种复用可以在运行时动态进行,也就是说如果我们要给成员变量进行赋值,那么我们就可以在程序运行的时候才对其进行赋值了。若成员位置声明的是抽象的类或者接口,则我们就可以传递该接口或者该类的子类对象了。
总之,这种复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的对象。