小酌重构系列[6]——引入参数对象

简述

如果方法有超过3个以上的参数,调用方法时就会显得冗词赘句。这时将多个参数封装成一个对象,调用方法会显得干净整洁。
这就是本文要讲的重构策略“引入参数对象”——将方法的参数封为类,并用这个类的对象替换方法中原有的参数。

引入参数对象

下图演示了这个重构策略,OrderSerivce表示订单服务,GetOrders()方法根据一些条件获取订单信息。
在重构前,GetOrders()方法看起来像长篇大论,调用时也颇为麻烦;在重构后,GetOrders()方法看起来言简意赅,调用时仅需要传入一个参数。

优点

这个策略在我看来至少有两个优点:

1. 可以减少方法的参数个数,调用方法时会更加干净整洁。
2. 当方法需要追加或者删除参数时,不需要修改方法签名。

注意点

1. 意味着你要在项目中追加新的class。
2. 并非死板地将所有的参数都放到一个参数对象中,需要确定这些参数属于某一个“共同的概念”。
3. 方法调用参数减少了,给“参数对象”起个好名字也很重要。
4. 项目开发阶段,如果方法的参数随时可能变更,建议直接将参数设计为对象参数。

例如:在一个平面坐标系统中,基于坐标和半径画圆。

public void CreateCircle(double x, double y, double radius)
{
    // do work
}

如果使用参数对象,我们可以用两种方式来表达。

方式1:将坐标封装为参数对象
方式2:将坐标 + 半径封装为参数对象

使用合适的参数对象命名可以将这两种方式区分开来。
方式1,提炼出“坐标”概念,于是就有了Point对象;方式2,提炼出“圆”概念,故使用了Circle对象作为参数。

public class ShapeTool
{
    // 方式1:封装坐标
    public void CreateCircle(Point point, double radius)
    {
        // do work
    }

    // 方式1:封装坐标 + 半径
    public void CreateCircle(Circle circle)
    {
        // do work
    }
}

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }
}

public class Circle
{
    public Point Point { get; set; }
    public double Radius { get; set; }
}

示例

重构前

这段代码用于“学生注册课程”。

public class Registration
{
    public void Create(decimal amount, Student student, IEnumerable<Course> courses, decimal credits)
    {
        // do work
    }
}

public class Student
{

}

public class Course
{

}

Create()方法的看起来难以理解,它的参数个数很多,并且没有良好的分类,我们应将这些参数封装起来。

重构后

针对“学生注册课程”这个场景,我们提炼出一个概念——”注册上下文“,这个上下文可以包含以上方法的所有参数。
重构后,Create()方法看起来简洁易懂,"RegistrationContext"这个命名恰到好处,所有参数都放到RegistrationContext类里了。

public class RegistrationContext
{
    public decimal Amount { get; set; }
    public Student Student { get; set; }
    public IEnumerable<Course> Courses { get; set; }
    public decimal Credits { get; set; }
}

public class Registration
{
    public void Create(RegistrationContext registrationContext)
    {
        // do work
    }
}

public class Student
{

}

public class Course
{

}

在一些应用场景中,当你的方法拥有很多参数,且这些参数表示不同的意义时,我们很难通过这些参数的名称提炼出一个合适的名词。
例如:为了将HttpRequest和HttpResponse放到一个对象中,若将这个对象命名为"HttpRequestAndResponse"会显得喋喋不休。
这时,我们可以用一种偷懒的做法,把这样的对象命名为“xxx上下文”。
例如,在ASP.NET中HttpContext和ViewContext就是这样的对象。

时间: 2024-12-28 01:24:27

小酌重构系列[6]——引入参数对象的相关文章

小酌重构系列[16]&mdash;&mdash;引入契约式设计

概述 试想这样一个场景,你提供了一些API给客户端调用,客户端传入了一些参数,然后根据这些参数执行了API逻辑,最终返回一个结果给客户端. 在这个场景中,有两个隐患,它们分别是: 客户端调用API时,传入的参数是否准确,参数是否满足API的执行前提 API逻辑执行完时,返回的结果是否准确,结果是否符合客户端的预期 这两个隐患都和"准确性"相关的,API要求(Require)传入的参数是否准确,它也要确保(Ensure)返回的结果是否准确.软件的准确性决定了软件的可靠性.通俗地讲,即用户

小酌重构系列[11]&mdash;&mdash;提取基类、提取子类、合并子类

概述 继承是面向对象中的一个概念,在小酌重构系列[7]--使用委派代替继承这篇文章中,我"父子关系"描述了继承,这是一种比较片面的说法.后来我又在UML类图的6大关系,描述了继承是一种"is a kind of"关系,它更偏向于概念层次,这种解释更契合继承的本质.本篇要讲的3个重构策略提取基类.提取子类.合并子类都是和继承相关的,如果大家对继承的理解已经足够深刻了,这3个策略用起来应该会得心应手. 提取基类 定义:如果有超过一个类有相似的功能,应该提取出一个基类,并

小酌重构系列[2]&mdash;&mdash;提取方法、提取方法对象

前言 "艺术源于生活"--代码也源于生活,你在生活中的一些行为习惯,可能会恰如其分地体现在代码中.当实现较为复杂的功能时,由于它包含一系列的逻辑,我们倾向于编写一个"大方法"来实现.为了使项目便于维护,以及增强代码的可读性,我们有必要对"大方法"的逻辑进行整理,并提取出分散的"小方法".这就是本文要讲的两种重构策略:提取方法.提取方法对象. 如何快速地找到想读的书? 在生活中,我是一个比较随意的人,平时也买了不少书去看.我的书

小酌重构系列目录汇总

为了方便大家阅读这个系列的文章,我弄了个目录汇总. 方法.字段重构 移动方法 (2016-04-24) 提取方法.提取方法对象 (2016-04-26) 方法.字段的提升和降低 (2016-05-01) 分解方法 (2016-05-02) 为布尔方法命名 (2016-05-03) 引入对象参数 (2016-05-04) 类.接口重构 使用委派代替继承 (2016-05-07) 提取接口 (2016-05-08) 解除依赖 (2016-05-09) 分离职责 (2016-05-11) 提取基类.提

小酌重构系列[17]&mdash;&mdash;提取工厂类

概述 在程序中创建对象,并设置对象的属性,是我们长干的事儿.当创建对象需要大量的重复代码时,代码看起来就不那么优雅了.从类的职责角度出发,业务类既要实现一定的逻辑,还要负责对象的创建,业务类干的事儿也忒多了点.对象创建也是"一件事",我们可以将"这件事"从业务代码中提取出来,让专门的类去做"这件事",这个专门的类一般是"工厂类",这样使得业务类和工厂类各司其职,代码整洁性得以提高.这就是本文要讲的主题--提取工厂类. 工厂举例

小酌重构系列[20]&mdash;&mdash;用条件判断代替异常

概述 异常处理的关键在于何时处理异常以及如何使用异常,有些开发者会觉得try catch的处理和使用难以把握,于是他们秉承着"您可错杀一千,不可放过一个"的想法,给所有的方法添加try catch. 这种方式会对应用程序造成什么影响吗? 从用户角度出发,用户确实难以察觉到什么,应用程序运行正常,使用的体验好像也没什么差别. 从程序角度出发,大量的try catch会降低代码的可读性,只有在异常触发时才会对程序的性能造成较大的影响. 这两种角度有对错吗? 二者都没有错,第一种角度甚至要远

小酌重构系列[24]&mdash;&mdash;封装集合

概述 当方法返回类型或属性类型为集合时,有些开发者会千篇一律地使用IList<T>集合.然而IList<T>具有集合的所有操作,这意味着调用者不仅可以读取集合信息,还能够修改集合.业务需求本来只是为调用者提供一个可读的集合,例如数据的查询和展示,但当方法返回IList<T>时,无疑隐式地开放了集合可写的权限.此时,我们无法阻止调用者篡改集合元素. 注意:将属性设定为IList<T>类型时,即使声明为只读的,我们仍然无法避免集合元素的篡改.例如public I

小酌重构系列[18]&mdash;&mdash;重命名

概述 代码是从命名开始的,我们给类.方法.变量和参数命名,我们也给解决方案.工程.目录命名.在编码时,除了应该遵守编程语言本身的命名规范外,我们应该提供好的命名.好的命名意味着良好的可读性,读你代码的人无需太多的注释,就能通过名称知道它是什么,它能做什么事儿,以及它应该怎么用. 我们命名.命名,不断地命名.既然有这么多命名要做,我们不妨做好他. 关于命名 取名字的成本 取个名字很简单,取个好的名字就不那么容易了.快速随意地取个名字,还不如花点时间取个好名字,因为好名字省下来的时间要比花掉的多.

小酌重构系列[12]&mdash;&mdash;去除上帝类

关于上帝类 神说:"要有光",就有了光.--<圣经>.上帝要是会写程序,他写的类一定是"上帝类".程序员不是上帝,不要妄想成为上帝,但程序员可以写出"上帝类".上帝是唯一的,上帝的光芒照耀人间,上帝是很爱面子的,他知道程序员写了"上帝类",抢了他的风头,于是他降下神罚要惩戒程序员.--既然你写了"上帝类",那么就将你流放到艰难地修改和痛苦的维护的炼狱中,在地狱之火中永久地熬炼. 你看,上帝也是有