欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件构造——从SOLID五大原则的角度看OOP

程序员文章站 2024-02-10 09:50:40
...

写在前面:由于已经复习了一遍,本文将通过SOLID五大原则以及设计模式的角度来对OO编程与设计做一个感受与总结。
对于OOP,我们有很多关键词在脑海中,我也是。想起OOP,我会想到它的抽象操作,它的分离机制,它对于实现的封装,对于多个类的继承机制,为多个子类的情况而设计的多态机制。

抽象

抽象是啥?

抽象,就是把具体的变成抽象的,把实现隐藏在接口背后。一个小小的接口却能够实现类似规约的作用,对于用户,他们不需要也不关心这个类是如何实现的,只要读懂spec,就明白这个类是怎么用就足够了,这样一方面可以节省用户使用软件的时间,另一方面也保证了用户不会轻易地去修改这个软件内部的代码;而对于实现者而言,他可以大展身手,因为他知道用户不关心这个类的具体实现,他可以去追求代码的高效与简洁,而无外行人的干扰。

抽象的用处

抽象的用处非常多,可以从开闭原则OCP,里氏替换原则LSP、接口倒置原则DIP来窥其一隅。

OCP

这个原则的意思就是对于一个模块,我们要防止其被修改,同时还要实现其功能的扩展。

怎么做?

我们可以运用抽象,我们通过实现接口的方式来遵循这一原则。我们不能修改实现这个接口的子类,这样加载做到了对修改的封闭;同时,我们可以通过实现这个接口的一个新的子类来实现对扩展的开放。

好处?

这么做显然是有好处的,我们既可以保证自己的正确代码不会被修改,同时还为今后增加的需求留了扩展的可能。

LSP

这个原则的意思就是说子类型一定可以替换父类型。

由于抽象

由于抽象,根据接口,用户可能用到父类型,也可能用到子类型的功能,而在这种情况,由于用户是按照父类型的spec去实现自己的需求的,那么显然需要子类型也足以胜任同样的功能,否则一旦引用了子类型,就会导致用户的程序出错,从而导致不必要的麻烦。

协变与逆变

协变:子类型方法的返回值也是父类型的返回值或其子类,即随着父类型到子类型越来越具体,其返回值也要变得越来越具体。
逆变:与协变相反。
严格从LSP的角度看,如果父类型是不可变的,那么其子类型也应该是一个不可变的,你想,如果用户在看到spec后获知这是个不可变对象,因此将其用在多线程编程中,而使用子类型时由于子类型中的mutator存在,因此可能造成线程不安全。然而这在的我们平常的编程中并不会遵守,给一个子类新加一个方法是一件非常正常的事。这也从侧面看出我们要妥协,但也要灵活

由于多态

由于多态,我们可以定义一个接口却引用其不同的实现子类,策略模式应运而生。

DIP

课件上对于接口倒置原则的解释如下:

高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象。

这两句话本身就非常抽象与枯燥,但是结合实际的编程过程还是理解的。

高层、低层

所谓高层,可以理解为框架层面的代码,其复用价值非常高,而所谓低层,可以理解为某一个具体的实现方法,其复用价值相对较低。
为什么高层不应该依赖于低层呢?其实如果低层非常正确、安全且又易于变化,那高等又何必"嫌弃"低层呢?而正是因为修改低层代码需要进入高层模块中修改,而低层模块又是容易出错,又经常需要变化的,因此高层应该依赖于抽象。
那么低层是否可以不依赖抽象呢?从OCP、策略模式的角度,我相信你已经知道怎样才是最优的。

抽象与实现

首先,抽象显然不应该依赖于实现,我们上面讲到,抽象的一个优点就是满足OCP,如果一个接口它依赖于某一个具体实现,那么其他的实现没有任何意义,这个接口也难以成为接口。
其次,实现细节应该依赖于抽象,一是因为我们有spec,我们需要实现spec的要求,这个过程中我们就是在依赖于抽象,二是因为依赖于抽象使得我们的这些实现的子类易于变化、易于扩展。大家都实现了接口的要求,因此当要修改时,我们可以在接口中增加一些需求,然后让需要实现的那些类在内部实现需求即可,而不必在高层模块中进行修改了。大家都实现了接口的要求,谁在这个场景下让程序的表现效果最好那就让谁上。大大增加了可复用性,提高了效率的同时也减少了出错的可能,同时也方便我们定位错误的类。

策略模式的一个深刻例子

第一节课,老师在教我们java一些基础知识的时候,就提到过,要我们用List去定义

List list = new ArrayList();

而非使用ArrayList()去定义

ArrayList list = new ArrayList();

继承

继承估计提到OOP时最容易想起来的一个词。继承需要满足LSP原则;继承可以实现代码的复用;但继承也可能加入不需要的方法,继承需要谨慎;类无法多继承而接口可以,如果需要多个类功能的组合,我们可以通过接口的多继承然后使用委托(delegation)机制来实现个性化组合(CRP)。也可以直接实现多个接口来实现。

分离与封装

我认为,分离与封装意思相近,只是侧重点不同罢了。
分离时需要用到封装,而封装本身就是一种分离。

用处

提高复用,减小耦合,以接口隔离原则ISP和责任单一原则RSP的角度来看。

ISP

什么时候用?

当接口中的一些方法是用户不需要的时,将其分离出来,变成一个接口,对于需要实现这个方法的类,实现这个接口。

好处

避免一个类的方法太过臃肿。

RSP

何时适用?

当一个类中有多个无关的方法时,将它们分离出来,变为接口。

好处?

减少变化,区分(分清)责任,节省资源

相关标签: 20春软件构造