面向对象程序设计原则(Principles of OOD)——SOLID

进行面向对象程序设计的时候,我们需要面对很多问题,比如:

  • 什么时候需要一个类
  • 一个类应该包含哪些功能,不包含哪些功能
  • 类之间的依赖关系设计

还有很多其它的问题,如果不遵照一些原则,而一切按照“本能”来的话,你的程序很快就会成为一坨不可靠,不容易扩展,不容易复用的“屎山”。面向对象设计原则就是为了保证你的代码可靠,易扩展,易复用而人为规定的一系列方法,它们是由大量资深的前辈们总结出来的经验。

这里先介绍一个经典的设计原则:SOLID

SRP The Single Responsibility Principle ''A class should have one, and only one, reason to change.''
OCP The Open Closed Principle You should be able to extend a classes behavior, without modifying it.
LSP The Liskov Substitution Principle Derived classes must be substitutable for their base classes.
ISP The Interface Segregation Principle Make fine grained interfaces that are client specific.
DIP The Dependency Inversion Principle Depend on abstractions, not on concretions.

这里先说一下SRP,参考文档,假设我们在实现一个游戏,需要计算矩形的面积,以及在GUI中显示矩形这两个功能,那么如果你很直接地实现一个Rectangle类,并同时让它实现计算面积跟GUI显示,那么就违反了SRP原则。

首先,如果我们用的是C++(或者java之类,Python不会有这个问题,它是通过解释器运行的,不用link)那么这个Rectangle类需要link GUI的库,相对于单纯只进行几何计算,需要额外的link和compile时间以及内存开销(java中需要加入GUI库的class文件)。其次,如果我们需要修改Rectangle的渲染部分代码,由于它跟计算部分写在同一个类里面,这会导致我们需要对计算部分也进行重编译以及测试等。

所以,一个遵循SRP的设计,应该是把计算逻辑跟渲染逻辑分别做在两个类里面,也就是“每个类只有一个职责”。那么问题来了,我们该如何去定义一个“职责”,也就是功能应该细分到什么粒度?无限细分显然是错误的,我们没有必要给每一个小功能定义一个类,而有时候看似合理的划分其实又是包含了多个职责的,原文给出的说明是:“A class should have one, and only one, reason to change.”看似很好理解,其实有时候也很具有迷惑性,这里有一个例子:
如果我们定义一个Modem类,它具有以下函数:

class Modem
{
  public void dial(String pno);
  public void hangup();
  public void send(char c);
  public char recv();
}

看上去似乎一切完美,但是它事实上是违背了SRP原则的,dialhangup属于连接管理功能,而sendrecv则是数据管理,也就是对于Modem接口,有超过一个以上的理由去修改。

我们可以把Modem拆分成DataChannelConnection两个接口,这样做似乎有点过于繁琐,并且这两个部分经常是需要同时使用的,所以我们可以使用一个ModemImplementation类继承这两个接口。

class DataChannel
{
  public virtual void send(char c) = 0;
  public virtual char recv() = 0;
}

class Connection
{
  public virtual void dial(String pno) = 0;
  public virtual void hangup() = 0;
}

class ModemImplementation : public DataChannel, public Connection
{
  public void send(char c);
  public char recv();
  public void dial(String pno);
  public void hangup();
}

也许看上去这很丑陋且冗余,但这经常是必要的,细分必然会导致繁琐不便(很多场合经常会需要同时使用多个细分功能),接口上细分,类实现再组合保留了便利性和SRP原则的益处,比如系统中有的模块如果需要data部分,那么它可以仅引入DataChannel,通过interface去调用ModemImplementation内部的数据部分功能,没有任何模块需要去依赖ModemImplementation,只有主函数需要知道ModemImplementation的存在。

总的来说,应用SRP原则时需要正确地去区分所谓的“responsibility”,这个没那么容易,同时出于方便考虑,在把功能细分以后重组有时也是必要的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容