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

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

实验一

考虑一个安全系统。在这个系统中,有一些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 中定义了太多方法,即该接口承担了太多职责,一方面导致该接口的实现类很庞大,在不同的实现类中都不得不实现接口中定义的所有方法,灵活性较差,如果出现大量的空方法,将导致系统中产生大量的无用代码,影响代码质量。

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

相关推荐
小小代码狗28 分钟前
SQLi-Labs 基础注入实战教程(Less-1 ~ Less-5and Less-9)
服务器·python·php
慕伏白30 分钟前
【慕伏白】Linux 系统如何根据端口号关闭进程
linux·运维·服务器
letisgo51 小时前
VMware Workstation + Ubuntu 26.04 LTS 小白运维部署手册
linux·运维·ubuntu
蓝创工坊Blue Foundry1 小时前
图片文字提取到 Excel:批量任务如何先定义要交付的字段
运维·服务器·开发语言·数据库·自动化·ocr·excel
晚风吹长发1 小时前
Docker使用——Docker容器及相关命令
linux·运维·服务器·docker·容器·架构
ShiXZ2131 小时前
Docker Compose 安装与配置指南
运维·docker·容器
麦聪聊数据1 小时前
企业数据市场建设(四):流程闭环与价值运营,让数据市场真正转起来
运维·数据库
是潮汕的灿灿展吖2 小时前
Centos离线部署nfs操作
linux·运维·centos
FII工业富联科技服务2 小时前
从灯塔工厂到AI Factory新模式:AI正在重构制造业的四大核心能力
大数据·运维·人工智能·重构·ar·制造
小五传输2 小时前
自主可控建设指南:Serv-u替代方案,实现平滑迁移业务不中断
大数据·运维·安全