《重构改善既有代码的设计》之重构列表--在对象之间搬移特性(三)

2023-12-16 14:58

本文主要是介绍《重构改善既有代码的设计》之重构列表--在对象之间搬移特性(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

六、Remove Middle Man(移除中间人)

某个类做了过多的简单委托动作。

让客户直接调用受托类。

动机

Hide Delegate的“动机”一节中,我谈到了“封装受托对象”的好处。但是这层封装也是要付出代价的,它的代价就是:每当客户要使用受托类的新特性时,你就必须在服务端添加一个简单委托函数。随着受委托类的特性(功能)越来越多,这一过程会让你痛苦不已。服务类完全变成一个“中间人”,此时你就应该让客户直接调用受托类。

做法

1、建立一个函数,用以获得受托对象。

2、对于每个委托函数,在服务类中删除该函数,并让需要调用该函数的客户转为调用受托对象。

3、处理每个委托函数后,编译、测试。

七、Introduce Foreign Method(引入外加函数)

你需要为提供服务的类增加一个函数,但你无法修改这个类。

在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。

动机

这种事情发生过太多次了:你正在使用一个类,它真的很好,为你提供了需要的所有服务。而后,你又需要一项新服务,这个类就无法供应。于是你开始咒骂:“为什么不能做这件事?”如果可以修改源码,你便可以自行添加一个新函数;如果不能,你就得在客户端编码,补足你要的那个函数。

如果客户类只使用这项功能一次,那么额外编码工作也没什么大不了的,甚至可能根本不需要原本提供服务的那个类。然而,如果你需要多次使用这个函数,就得不断重复这些代码。还记得吗,重复代码是软件万恶之源。这些重复代码应该被抽出来放进同一个函数中。进行本项重构时,如果你以外加函数实现一项功能,那就是一个明确信号:这个函数原本应该在提供服务的类中实现。

如果你发现自己为一个服务类建立了大量外加函数,或者发现有许多类需要同样的外加函数,就不应该再使用本项重构,而应该使用Introduce Local Extension

做法

1、在客户类中建立一个函数,用来提供你需要的功能。

?  这个函数不应该调用客户类的任何特性。如果它需要一个值,把该值当做参数传给它。

2、以服务类实例作为该函数的第一个参数。

3、将该函数注释为“外加函数(foreign method),应在服务类实现”。

?  这么一来,如果将来有机会将外加函数搬移到服务类中时,你便可以轻松找出这些外加函数。

八、Introduce Local Extension (引入本地扩展)

你需要为服务类提供一些额外函数,但你无法修改这个类。

建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。

动机

很遗憾,类的作者无法预知未来,他们常常没能为你预先准备一些有用的函数。如果你可以修改源码,最好的办法就是直接加入自己需要的函数。但你经常无法修改源码。如果只需要一两个函数,你可以使用Introduce Foreign Method。但如果你需要的额外函数超过两个,外加函数就很难控制它们了。所以,你需要将这些函数组织在一起,放到一个恰当地方去。要达到这一目的,两种标准对象技术----子类化(subclassing)和包装(wrapping----是显而易见的办法。这种情况下,我把子类化或包装统称为本地扩展(local extension)。

所谓本地扩展是一个独立的类,但也是被扩展类的子类型:它提供源类的一切特性,同时额外添加新特性。在任何使用源类的地方,你都可以使用本地扩展取而代之。

使用本地扩展使你得以坚持“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类中的代码零散地放置于其他类中,最终只会让其他这些类变得过分复杂,并使得其中函数难以被复用。

在子类和包装直接做选择时,我通常选子类,因为这样的工作量比较少。制作子类的最大障碍在于,它必须在对象创建期实施。如果我可以接管对象创建过程,那当然没问题;但如果你想在对象创建之后再使用本地扩展,就有问题了。此外,子类化方案还必须产生一个子类对象,这种情况下,如果有其他对象引用了旧对象,我们就同时有两个对象保存了原数据!如果原数据是不可修改的,那也没问题,我可以放心复制;但如果原数据允许被修改,问题就来了,因为一个修改动作无法同时改变两个副本。这时候我就必须改用包装类。使用包装类时,对本地扩展的修改会波及原对象,反之亦然。

做法

1、建立一个扩展类,将它作为原始类的子类或包装类。

2、在扩展中加入转型构造函数。

?  所谓“转型构造函数”是指“接受原对象作为参数”的构造函数。如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。

3、在扩展类中加入新特性。

4、根据需要,将原对象替换为扩展对象。

5、将针对原始类定义的所有外加函数搬移到扩展类中。


这篇关于《重构改善既有代码的设计》之重构列表--在对象之间搬移特性(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/500879

相关文章

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

Python 常用数据类型详解之字符串、列表、字典操作方法

《Python常用数据类型详解之字符串、列表、字典操作方法》在Python中,字符串、列表和字典是最常用的数据类型,它们在数据处理、程序设计和算法实现中扮演着重要角色,接下来通过本文给大家介绍这三种... 目录一、字符串(String)(一)创建字符串(二)字符串操作1. 字符串连接2. 字符串重复3. 字

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

Vue实现路由守卫的示例代码

《Vue实现路由守卫的示例代码》Vue路由守卫是控制页面导航的钩子函数,主要用于鉴权、数据预加载等场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、概念二、类型三、实战一、概念路由守卫(Navigation Guards)本质上就是 在路

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni

JAVA实现Token自动续期机制的示例代码

《JAVA实现Token自动续期机制的示例代码》本文主要介绍了JAVA实现Token自动续期机制的示例代码,通过动态调整会话生命周期平衡安全性与用户体验,解决固定有效期Token带来的风险与不便,感兴... 目录1. 固定有效期Token的内在局限性2. 自动续期机制:兼顾安全与体验的解决方案3. 总结PS

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型

Python屏幕抓取和录制的详细代码示例

《Python屏幕抓取和录制的详细代码示例》随着现代计算机性能的提高和网络速度的加快,越来越多的用户需要对他们的屏幕进行录制,:本文主要介绍Python屏幕抓取和录制的相关资料,需要的朋友可以参考... 目录一、常用 python 屏幕抓取库二、pyautogui 截屏示例三、mss 高性能截图四、Pill