面向对象的编程鼓励编程者把功能分散到多个对象中,从而使得每个对象只有唯一的功能(SRP),对象足够的高内聚,这样的代码更容易理解,维护,修改和复用;同时,对象必须互相调用才能共同完成复杂的操作,但如果对象间相互调用太多,又会导致对象间高耦合,使得对一处代码的修改影响到多处看似不相关的代码(散弹效应),为实现高内聚,对象间应该做到少依赖;如果依赖,则依赖于必须的最小接口(ISP)。

本文通过一个小例子,尝试分析下对象间调用时调用者和被调用者互相控制的几种方式。

今天早上坐(哦,应该是站)地铁,边站边想对象间的调用方式,正好想到了北京地铁最近要提价了。于是想到一个根据上车站点和下车站点显示票价的例子。具体而言,用户在屏幕上选择上车站,下车站和乘车策略(站点最少和时间最短),然后屏幕上会显示票价。

实现上,类PriceDisplayer负责显示票价,其display方法接受start(表示上车站),end(下车站)和strategy(乘车策略),然后经过计算,会在屏幕上显示票价;又有一个类,StationManager,其方法getStationDistance同样接受start,end,strategy三个参数,返回两个站点之间的最短站数,最快站数和换乘最少站数。PriceDisplayer.display会调用StationManager.getStationDistance获取站点数,然后根据一种计算方法(例如起始2块,满5站加1块)计算出票价,最后调用一个显示的api来显式票价。在其最外层,则有TicketMachine负责创建PriceDisplayer并调用其display。
这里展示了最常见的对象间控制(调用者到被调用用着)和反控制(被调用者到调用者)的方法。调用者(PriceDisplayer)通过参数来控制被调用者(StationManager),被调用者通过返回值(或者传出参数)来反控调用者。在依赖上,只有调用者依赖于被调用者,而被调用者并不知晓调用者。被调用者不会因调用者的改变而改变(因为没有依赖),其创建非常简单(因为不需要创建其依赖类),复用性也是非常强的。相对而言,调用者依赖于一个具体类,会因为被调用者接口和实现的改变而改变。但如果需要对象间合作,这种单向依赖是必须的。

然后我们想增加一种新的乘车策略,换乘最少。此时一种实现是在StationManager中通过if-else增加对该策略的判断,然后strategy参数也增加一个枚举类型。这时StationManager的修改,可能会导致PriceDisplay的已有行为被改变,例如在增加新策略时,不小心修改了原有两种策略的行为(违反OCP)。此时一种选择是使用strategy模式,即抽取出一个接口,StationManagerInterface,其getStationDistance接受start和end两个参数。然后有三种实现分别针对三种策略:MinStationCountStationManager,MinTimeStationManager和MinTransferStationManager。可以创建PriceDisplayer时使用不同的实现来初始化,这样子PriceDisplayer只依赖于接口StationManagerInterface而不依赖于具体实现。此时如果增加新的策略,PriceDisplayer代码不需要任何变化,也完全不会影响原有的三种策略。
这里展示了调用者通过调用同一接口的不同实现来改变行为的方法。就编译时的静态代码而言,调用者只依赖于一个接口,而不似之前依赖于具体类。只要运行时真实的被调用者实现了该接口,就可以被调用。仍然是单向依赖,只不过现在依赖于接口而非实现。此时被调用者的复用性更强(其各个实现一般不需要修改,增加新的功能只需要增加新的实现,所以使用者更加放心),而调用者(PriceDisplayer)因为可以使用不用的具体被调用者实现,也可以更灵活的被该调用者的调用者(TicketMachine)使用,所以复用性也得到一定加强,但不同于前一种方法,此时调用者的调用者(TicketMachine)必须初始化调用者(PriceDisplayer)和被调用者(StationManagerInterface),所以又使得调用者的调用者(TicketMachine)更加复杂。

现在假设StationManager的计算站点距离算法非常费时,需要放在一台服务器上计算,所以PriceDisplay的现实操作只能够异步完成。此时一种实现方法是传入一个Callback接口的实现,Callback接口中有execute方法,供StationManager计算出站点距离时回调。
这里展示的是通过传入可执行的方法(Js,python中可以直接传入方法,java,c++只能传入带有实现方法的对象),控制被调用者。虽然也是通过参数控制,在第一种方法中,参数只是静态数据,而此时,参数是操作。传输操作不仅限于回调,设计模式中的Command模式也是传递作为命令的操作。通过传入操作,调用者可以改变被调用者的流程中的某些步骤。对于我们的例子,只是改变了最后一步的callback;但它也可以改变流程中的任何一步。例如我可以在参数中不传入start和end,而传入包括getStart和getEnd方法的接口传入。再例如前面第二种方法中TicketMachine将具体的StationManager设置到PriceDisplayer中(通过构造函数或setter),也是一种传入可执行对象的例子.
这种方式仍然是单向依赖。PriceDisplayer依赖于StationManager,StationManager依赖于Callback。虽然Callback是在PriceDisplay中定义的(或者是PriceDisplay实现了Callback),但StationManager并不知道。
这种方式既可以正向控制(例如command模式),调用者给被调用者传递命令;也可以反向控制,例如本例中的被调用者回调改变调用者.

同样是上面的场景,另外一种选择是PriceDisplay把自己传给StationManager(而不是Callback的实现)。此时它可以直接回调PriceDisplay的所有方法。此时则是双向依赖。调用者和被调用者紧紧的耦合在一起了。我们可以通过抽取更小的接口(方法3)或者直接把值给作为参数传过去和返回值传回来(方法1)来尽量避免这种过紧的依赖。
因为是互相调用,同样可以正向控制和反向控制.

转载于:https://www.cnblogs.com/xichengtie/p/3542551.html

对象间相互调用时互相控制的几种方法相关推荐

  1. android应用间相互调用

    android应用间相互调用 调用方: activity中添加:(red:应用包名,green:被调用activity名称,包含包名) Intent intent = new Intent(); Co ...

  2. Bean的生命周期行为控制,初始化与销毁bean时执行操作的三种方法

    Bean的生命周期行为控制,初始化与销毁bean时执行操作的三种方法 一.实现Spring的接口 二.XML配置中使用 init-method和destory-method 三.使用@PostCons ...

  3. shell实例第19讲:一个脚本中调用另一个脚本的3种方法

    在Shell脚本中调用另一个脚本(3种方法) 准备:主脚本是second.sh,主脚本中调用first.sh 方法1:exec(1)执行方式:exec /home/weibo/shell_test/s ...

  4. web集群时session同步的3种方法

    web集群时session同步的3种方法 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话 ...

  5. dede列表页if判断输出html,首页、列表页调用文章body内容的两种方法

    随着源码的开放性,很多SEOER对页面的要求也越来越复杂多样性,很多时候,织梦系统的原有功能并不能满足seoer的页面布置要求,这就需要继续开发页面,做更多的功能调用. 今天徐金华SEO给大家讲的是关 ...

  6. php网站搬家怎么打包,搬家时打包衣柜的5种方法

    搬家时打包衣柜的5种方法 来源:网络      作者:迈思特英语 In most cases moving is an exciting prospect but an incredibly stre ...

  7. 禁止开机时软件自启动的三种方法

    禁止开机时软件自启动的三种方法 目前常用的方法一般有三种:用软件禁止:用Windows命令禁止:直接修改注册表.那么就让我们分别来看一下. 一.使用软件禁止软件的自动启动 现在有很多的软件可以禁止开机 ...

  8. 织梦首页、列表页调用文章body内容的两种方法

    关于首页.列表页调用文章body内容的两种方法,具体方法如下: 第一种方法: {dede:arclist row='1' typeid='1' addfields='body' idlist='1' ...

  9. js之删除对象属性的三种方法 判断对象中是否有某一属性的四种方法

    js之删除对象属性的三种方法 & 判断对象中是否有某一属性的四种方法 示例 1.基础版 var a = { id: 18, age: 20, name: "zhangsan" ...

  10. 【千方百计】更改绑定的数据对象数值后,DOM重新渲染的4种方法

    本篇博客是博主记录在项目开发中遇到的Vue前端界面UI更新问题,界面更新就是对界面元素的更新.下述4中方法均是Vue框架本身提供的更新UI界面的API,按照接口对UI刷新操作后影响的程度进行升级描述. ...

最新文章

  1. 《Microsoft Sql server 2008 Internals》读书笔记--第九章Plan Caching and Recompilation(10)
  2. #if DEBUG与条件(“ DEBUG”)
  3. Xamarin XAML语言教程构建ControlTemplate控件模板
  4. POJ-2391 Ombrophobic Bovines 网络流-拆点构图
  5. 【C++】17. map []操作符、insert()、emplace()
  6. DotNetCommon-搜集.neter开发常用的功能
  7. linux中top工具,Linux命令工具 top详解
  8. TCP中recv解阻塞的两种方式
  9. 2019数据技术嘉年华主会场,数据英雄荟萃一堂共论道
  10. CentOS 7安装配置vsftpd做FTP服务
  11. vmware虚拟机安装CentOS8提示错误:section %package does not end with %end(可用)
  12. 设计模式(2)——观察者模式
  13. 使用Java生成PDF文件
  14. 红包小游戏php源码,H5抢红包 小游戏源码
  15. 计算机电子书 2019 BiliDrive 备份
  16. 好未来 Dolphin-儿童口语表达能力AI自动评测:顶尖AI算法技术与教育理念的有机结合 | 百万人学AI评选
  17. 香奈儿机器人t恤_最hot超模:开场Chanel的机器人 真面目居然是小精灵?
  18. Java课程设计学生考勤管理
  19. SSM实现的影院订票系统-JAVA【数据库设计、源码、开题报告】
  20. 开关电源中电容与电感时间常数

热门文章

  1. Druid(数据库连接池)和JdbcTemplate(封装JDBC)使用
  2. Maven 本地仓库明明有jar包,pom文件还是报错解决办法(Missing artifact...jar)
  3. 报错:Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfr
  4. 使PNG图片在IE6下透明(非背景图片)
  5. 每天一道剑指offer-重建二叉树
  6. linux麒麟安装教程,优麒麟使用教程第四期:Linux平台U盘启动盘制作
  7. python自定义规律绘制_ForMaiR - 自定义规则的邮件自动转发工具
  8. c语言整数转浮点数_浮点数的秘密
  9. 平分七框梨java_php编程- php算法 - 平分七框鱼
  10. python中map和filter区别_Python中map、filter和reduce的使用总结