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

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

实验一

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

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

相关推荐

  1. 面向对象设计里氏替换原则

    2024-04-12 16:16:01       41 阅读
  2. 面向对象设计单一职责原则

    2024-04-12 16:16:01       47 阅读
  3. 面向对象设计开闭原则

    2024-04-12 16:16:01       41 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-12 16:16:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-12 16:16:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-12 16:16:01       87 阅读
  4. Python语言-面向对象

    2024-04-12 16:16:01       96 阅读

热门阅读

  1. Composer安装与配置详解

    2024-04-12 16:16:01       35 阅读
  2. 蓝桥杯省B组复习(小白篇)

    2024-04-12 16:16:01       40 阅读
  3. C++ 的内存安全与效率

    2024-04-12 16:16:01       41 阅读
  4. 力扣经典150题第十八题:整数转罗马数字

    2024-04-12 16:16:01       44 阅读
  5. 《访问者模式(极简c++)》

    2024-04-12 16:16:01       40 阅读
  6. Erlang 常用数据结构实现

    2024-04-12 16:16:01       45 阅读
  7. OTN和波分的区别

    2024-04-12 16:16:01       38 阅读
  8. vue简单使用五(组件的使用)

    2024-04-12 16:16:01       35 阅读
  9. 安卓手机APP开发的实践_点击行为

    2024-04-12 16:16:01       38 阅读
  10. Android - VideoView需要按两次BACK才能退出解决方法

    2024-04-12 16:16:01       41 阅读
  11. Android MediaPlayer 深入分析与实践

    2024-04-12 16:16:01       35 阅读