面向对象设计原则实验之“接口隔离原则”

客户端不应该依赖那些它不需要的接口。

实验一

考虑一个安全系统。在这个系统中,有一些Door对象,可以被加锁和解锁,并且Door对象知道自己是开着还是关着。这个Door编码成一个接口,这样客户程序就可以使用那些符合Door接口的对象,而不需要依赖于Door的特定实现。

现在,考虑一个这样的实现,TimedDoor,如果门开着的时间过长,它就会发出警报声。为了做到这一点,TimedDoor对象需要和另一个名为Timer的对象交互。

如果一个对象希望得到超时通知,它可以调用Timer的Register函数。该函数有两个参数,一个是超时时间,另一个是指向TimerClient对象的引用,其TimeOut函数会在超时到达时被调用。

怎样将TimerClient类和TimedDoor类联系起来,才能在超时时通知到TimedDoor中相应的处理代码呢?比如下面的一种实现:

这种做法的问题是,现在Door类依赖于TimerClient了。可是并不是所有种类的Door都需要定时功能。事实上,最初的Door抽象类和定时功能没有任何关系。如果创建了无需定时功能的Door的派生类,那么在这些派生类中就必须要提供TimeOut方法的退化实现,这就有可能违反LSP。此外,使用这些派生类的应用程序即使不使用TimerClient类的定义,也必须要引入它。

这是一个接口污染的例子,Door的接口被一个它不需要的方法污染了。在Door的接口中加入这个方法只是为了能给它的一个子类带来好处。如果持续这样做的话,那么每次子类需要一个新方法时,这个方法就会加到基类中去。这会进一步污染基类的接口,使它变"胖"。

此外,每次基类中加入一个方法时,派生类中就必须要实现这个方法(或者定义一个默认实现)。事实上,有一种特定的相关实践,可以使派生类无需实现这些方法,该实践的做法是把这些接口合并为一个基类,并在这个基类中提供接口中方法的退化实现。但是我们前面已经学过,这种实践违反了LSP,会带来维护和重用方面的问题。

请根据接口隔离原则,重构上面的设计。

解析(参考):

一个解决方案是**创建一个派生自TimerClient的对象,并把对该对象的请求委托给TimedDoor。**当TimedDoor想要向Timer对象注册一个超时请求时,它就创建一个DoorTimerAdapter并且把它注册给Timer。当Timer对象发送TimeOut消息给DoorTimerAdapter时,DoorTimerAdapter把这个消息委托给TimedDoor。这个解决方案遵循ISP原则,并且避免了Door的客户程序和Timer之间的耦合。即使对代码清单12-3中所示的Timer进行了改变,也不会影响到任何Door的使用者。此外,TimedDoor也不必具有和TimerClient一样的接口。DoorTimerAdapter会将TimerClient接口转换成TimedDoor接口。因此,这是一个非常通用的解决方案。

实验二

某软件公司开发人员针对 CRM 系统的客户数据显示模块设计了如下图所示的 CustomerDataDisplay 接口。其中:

方法 readData() 用于从文件中读取数据;

方法 transformToXML() 用于将数据转换成 XML 格式;

方法 createChart() 用于创建图表;

方法 displayChart() 用于显示图表;

方法 createReport() 用于创建文字报表;

方法 displayReport() 用于显示文字报表。

在实际使用过程中发现该接口很不灵活。例如:如果一个具体的数据显示类无须进行数据转换(源文件本身就是 XML 格式),但由于实现了该接口,将不得不实现其中声明的 transformToXML() 方法(至少需要提供一个空实现);如果需要创建和显示图表,除了需要实现与图表相关的方法外;还需要实现创建和显示文字报表的方法。否则程序在编译时将报错。

现使用接口隔离原则对其进行重构。

解析(参考):

在本实例中,由于在接口 CustomerDataDisplay 中定义了太多方法,即该接口承担了太多职责,一方面导致该接口的实现类很庞大,在不同的实现类中都不得不实现接口中定义的所有方法,灵活性较差,如果出现大量的空方法,将导致系统中产生大量的无用代码,影响代码质量。

另一方面由于客户端针对大接口编程,将在一定程度上破坏程序的封装性,客户端看到了不应该看到的方法,没有为客户端定制接口。因此需要将该接口按照接口隔离原则和单一职责原则进行重构,将其中的一些方法封装在不同的小接口中,确保每一个接口使用起来都较为方便,并都承担某一单一角色,每个接口中只包含一个客户端(如模块或类)所需的方法即可。

相关推荐
Linux运维技术栈9 分钟前
Terraform 从入门到实战:历史、原理、功能与阿里云/Azure 上手指南
运维·阿里云·kubernetes·azure·terraform
wdfk_prog11 分钟前
[Linux]学习笔记系列 -- lib/dump_stack.c 栈回溯打印(Stack Trace Dumping) 内核调试与错误诊断的基石
linux·运维·服务器·c语言·笔记·学习
不可能的是17 分钟前
Docker与Ubuntu环境下apt-get报错完全解决指南
运维
蓝倾97625 分钟前
小红书获取用户作品列表API接口操作指南
java·服务器·前端·python·电商开放平台·开放api接口
bantinghy1 小时前
RPC内核细节(转载)
linux·服务器·网络·网络协议·rpc
雨季西柚1 小时前
Docker网络模式解析
linux·运维·kubernetes
荣光波比1 小时前
Nginx 实战系列(四)—— Nginx反向代理与负载均衡实战指南
运维·nginx·云计算·负载均衡
syty20201 小时前
elastic search 是如何做sum操作的
运维·jenkins
运维成长记1 小时前
linux 100个问答81~101 主要是k8s相关
linux·运维·服务器
旺小仔.2 小时前
Linux--线程
linux·运维·服务器