Prefactoring节译 2.6 抽象

(所有关键字,类名和其它标识符保持原样不翻译)

在创建一个UseCase的描述或者一个可能的类的模型时,应该避免使用基本数据类型,装作int和double之类的不存在。几乎所有数字的类型都可以使用“抽象数据类型”(ADT)来描述。某样东西的价格是Dollar型的(如果你希望面向全球的话,也可以是CurrencyUnit)。仓库中某类物品的数量是Count型的。优质客户能享受的折扣可以表示为Percentage类型。CDDisc的尺寸可以被表示成Length型(也可以是LengthInMeters或者LengthInInches,如果你准备把卫星送入太空的话)。CDRelease上的一首歌的长度可以被存储为TimePeriod类型。

使用ADT可以使注意力集中在“可以对这个类型做什么”上,而不是“如何表示这个类”。一个ADT说明你想要对变量做什么。你可以把一个变量声明为基本数据类型,而通过变量命名来反映这种意图。但是被声明为抽象数据类型的变量可以拥有内置的验证逻辑,这是声明为基本数据类型的变量所做不到的。

每一个ADT都需要相关的描述。例如,一个Count表示物品的数量。Count可以是0或者正数。如果一个Count是负数,则代表一个不合法的数量。将一个变量声明为Count能够传达这个信息。你可以创建Count的变种。比如CountWithLimit类型可以有一个上限,如果超过这个上限则会触发一个错误。

你可以将限制施加于不同的数据类型上。比如(人类的)Ages的范围是0到150岁;(机动车的)SpeedLimits在5到80英里/小时之间;(正常飞行)的Elevations(高度)[*]在0到60,000英尺之间。所有这些类型都可以表示为一个int或者double,但这是实现层面的事情,不是抽象的问题(issue)。

    [*] 以前,蒙大拿州除了“合理的速度”以外没有其它速度限制,速度的上限仍然可以是一个合理的数值(比如200)。
   
抽象类型能包含的信息不局限于验证规则。价格可以表示为Dollar。Dollar的字符串表示形式和double的字符串表示形式是不一样的。Dollar的字符串表示至少包含一个货币符号,可能还包含千位分隔符(比如逗号)。一个Dollar和一个数字相乘会得到一个带分币的Dollar,但分币数不会是分数。这里是一个可能的Dollar类:

     class Dollar

        {

        Dollar multiply_with_rounding(double multiplier);

        Dollar add(Dollar another_dollar);

        Dollar subtract(Dollar another_dollar);

        String to_string(  );

        };

如果你使用的语言能够为类定义操作符(比如+和-),你可以为操作使用合适的数学运算符号。你也可以使用合适的方法名使一个Dollar在合适的上下文中自动转换成一个String。你如何表示用于某个数据的抽象数据类型属于实现细节。你可以为每一种类型创建一个类。如果你使用C++,你可以用typedef来定义没有额外功能的简单的(抽象数据)类型。对其它语言来说,你可以把这些简单的(抽象数据)类型转换为基本数据类型。这种情况下,你也可以使变量名中包含类型名(比如double price_in_dollars)。

指导方针:如果你要抽象,就要抽象到底。
不要使用基本数据类型描述数据项。

这个指导方针建议使用明确(explicit)的类型来描述问题以及解决方案。通过从一开始就使用抽象数据类型,你可以有效地获得更高的抽象度。如果你显示地表示出所有参数和属性的类型,那么当这种显示类型带来妨碍的时候,你总是可以将他们转换为隐式的类型。但要反过来做就要困难得多了[**]。
   
    [**]这理是一篇关于使用在隐式数据类型(动态数据类型?)的语言中有力地进行测试(strong testing)的文章:http://www.artima.com/weblogs/viewpost.jsp?thread=4639
   
2.6.1 不仅仅是String

很多原来使用String表示的数据类型都可以表示为更富有表现力的数据类型。例如,虽然姓名可以被声明为String,但你也可以将其声明为组合数据对象:

    class Name

        {

        String first_name;

        String last_name;

        String title;          // e.g. Mr. Mrs.

        String suffix;      // e.g. Jr. III

        };
 
为了避免“任何东西都是字符串”综合症,为那些由字符组成,但不包含验证、格式和其他元信息的变量起一个不同的类型名。例如CommonString。用这个名字代替String来描述属性的数据类型,而把String只用于实现时候的类型。然后问自己“这个属性真的是一个CommonString吗?”

让我们重新来看一下Address类。使用CommonString,我们可以把这个类描述为:

   class Address

        {

        CommonString line1;

        CommonString line2;

        CommonString city;

        CommonString state;

        CommonString zipcode;

        }

CommonString可以包含任何字符,就好像int可以表示任何整数值(只有硬件限制)。在Address中,一些字段绝对不是CommonString。一个state (州)不是CommonString。只有一些特定的值可以表示合法的州。对美国邮递地址来说,如果我们使用缩写来表示州,则必须使用“美国邮政服务正式缩写表中”的缩写。所以state应该被声明为类型State。这个类型可以提供合适的验证机制,它可以检查一个字符串是否包含在正式缩写表中,或者提供可以用于下拉式列表框的所有合法缩写的列表。

美国邮政编码也不仅仅是一个CommonString。你可以将它描述为NumericString类型(全部由数字组成的字符串),或者是FormattedString类型(由5个数字加破折号再加4个数字组成),或者ZipCode类型。如果任何数字的组合都是合法的,那么使用NumbericString或者FormattedString也许是合适的。但是把一个属性声明为ZipCode类型允许我们抽象出它的实际表现形式。

不要简单地因为实现的目的把类合并起来。你可以把SocialSecurityNumber和PhoneNumber都定义为由数字和两个破折号组成的字符串。但这并不表明他们是等价的东西,这只是偶然内聚。它们是具有不同验证规则的完全不相同的两个类。在美国电话号码不能由1或0开始。某些范围的社会保险号码是不能使用的。这些数字可以用同样类型的格式化字符串来输入和显示,但是每个类的内在语义是完全不同的。你永远也不能用一个社会保险号来拨电话,也不能用一个电话号码来记录收入税。(好吧,也许有一天有人能举出一个反例,所以我永远不能说“永不”。)

很多也许是CommonString的数据也可以被赋予自己的数据类型。例如文件名一般是字符串,但是不能包含一些特定的字符。在Windows中,文件名中不能包含 /, /, :, *, ", ?, <, >, or |。FileName类型可以表示一个文件名,同时强制执行这种限制。使用抽象数据类型的某种好处在GUI的开发中就更加明显了。例如,界面代码可以识别FileName类型,并自动在一个文本框的边上插入一个“浏览”按钮。

在Web中,HTTP GET和PUT命令的参数值是编码过的字符串。除了数字和字母的字符都用它们的16进制值编码。编码后的字符串发送给Web服务器。虽然未编码的字符串和未编码的字符串都可以实现为字符串,但它们是不同的。你可能会得到不合法的编码字符串——包含未编码的标点符号,就象一个黑客可能会发送给服务器的那样。你可以在服务器中使用EncodedWebString类表示字符串。如果一个输入不能通过EncodedWebString的验证,它将被抛弃。

指导方针:很多字符串都不仅仅是字符串。
把String当成基本数据类型。用抽象数据类型描述属性,而不用String。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值