First we try, then we trust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

英文原文:http://www.martinfowler.com/ap2/quantity.html


Quantity

数量


Represent dimensioned values with both their amount and their unit

由数字和相关单位组成,表示有单位的值

There are many cases where we want computers to represent dimensioned quantities: values such as six feet, or thirty kilograms. Usually these are represented as bare numbers, mainly because that is the best we can do in the limited type systems that languages give us.

我们会遇到很多场合需要计算机描述具有单位信息的数量,例如:六英尺,或三十公斤。 通常这些数量仅由数字表示,因为在编程语言赋予我们的有限数据类型下最多只能做到这样。

But using objects invites us to add new fundamental types whenever they add value, and having a specific type for dimensioned quantities adds quite a lot of value. This is particularly true when the dimensioned value is that of Money. Money is a special case of Quantity, but also one that perhaps the most widely used, as plenty of people are prepared to spend money to watch money.

但对象的使用需要我们每增加一种数量就要添加一种新的基础数据类型,并且为那些拥有多值、多单位的数量设计专门的类型。这在表示具有单位的货币时尤为明显。货币是数量的一种特殊实例,并且也许是使用最广的一种,很多人准备好花钱来照料管理他们的资金(译注:我个人理解在这里Martin很风趣,前一个money指花钱,而后一个money一语双关,可以理解为资金,也可以理解为“货币模式”)。

How it Works

工作原理

The basic idea of Quantity is a class that combines the amount with the unit. In saying that a quantity is a simple representation. However the real fun with Quantity comes with the behavior that you can apply to it.

数量实现的基本思想是构建一个类将数字与单位结合起来。 这么说似乎数量的表示方法非常简单,但是真正让人对数量感兴趣的是你所赋予它的行为。

The first kind of behavior is arithmetic. You should be able to add two quantities together just as easily as you can add two numbers. Furthermore more the quantities should be intelligent about addition and subtraction: at the least preventing you from adding 6 feet to 180 pounds.

第一种行为是算术运算。 您应该能象加起两个数字一样容易的将二个数量加起来。除此之外,数量除了具有加减功能外还应具有智能性:至少它能防止您将6 英尺和180 磅加在一起。

More complicated issues with addition arise where you are trying to add similar units, such as adding 500 meters to 3 kilometers. The simplest approach is to deny that addition, forcing the client to make the conversion. A more sophisticated approach is to see if you can convert one argument to the other, if so you can perform the addition. How well you can do this depends on the sophistication of your Convertor.

当我们试图对相似单位的数量进行相加时,比如将500 米与3 公里相加,我们可能会遇到更复杂的问题。 最简单的解决办法是禁止这种相加,迫使客户先完成单位转换。另外一种较复杂的方法是尝试将一个单位转换成另外一个,如果成功则能够进行相加运算。在这上面你究竟能够做多好取决于你的Convertor(转换器)。

Multiplication has similar variations in sophistication. The simplest approach is to only permit multiplication and division by scalar numbers. A more sophisticated approach is to allow multiplication by other quantities, but then you have to work out the units of the result. So if you divide 150 miles by 2 hours you get 75 miles/hour.

乘法运算也具有相似的复杂性。最简单的办法是我们只允许对标量数字进行乘法和除法运算。另外一种更加复杂的方法是允许对数量进行乘法,但你必须自己推导出结果的单位。例如你用150 英哩除2 个小时将会得到75英里/小时。

Comparison operations are needed so you can find out if six feet is more than five feet. The issues here are much the same as those for addition - you have to choose how much conversion you do.

比较操作也是必须的,我们借此可以发现六英尺大于五英尺。这里的问题同加法一样,那就是你必须选择如何进行单位转换。

It's useful to provide a simple interface to allow conversion on a quantity directly, although the conversion work is usually best left using Convertor. If you only have a few units, however, it's easier to embed them directly in quantity classes.

我们可以提供一个简单的接口允许直接对数量进行转换,但是尽管可以转换,但这种工作最好留给Convertor(转换器)来完成,而且当只有几个单位需要转换的时候,我们也可以很容易的将其并入数量类中。

One of the most useful behaviors you can give to quantities is to provide printing and parsing methods that allow you easily produce strings and to produce a quantity from a string. This simple pattern can do much to simplify a lot of input and output behavior, either to files or in GUI interfaces.

我们可以给数量添加的最有用的行为之一就是为其提供打印和解析方法,这让我们很容易生成数量的字符串表达,也可以将字符串解析成数量。该模式尽管简单,但可以简化很多输入输出行为,不管是到文件还是到GUI(图形用户界面)界面。

For simple printing you can have a default, such as first printing the amount and then the unit. That breaks down when in some cases you want to print the unit before the number and other cases afterwards. Usually this kind of variation will depend on the unit, so in these cases you can put the printing and parsing behavior on the unit and delegate to that.

你可以为简单打印提供一个缺省实现,例如首先打印数额,然后打印单位。但这在某些情况下可能会行不通(单位在数额前或某些单位在数额后的情况)。通常这些变化取决于单位,因此在这些情况下你可以将打印和解析工作放在单位中,然后委派给数量。

Money

货币

I use Quantity on almost every system I work with, but I rarely use it to represent physical units. Most of the time I use it to represent money. Much of the comments for physical units are the same, but money does have its own spin to bear in mind when you're working with it.

我几乎在每个我参与的系统中都使用数量,但我很少使用它表示物理单位。多数时间我用它表示“货币”。将数量使用在“物理单位”上的情况基本上大同小异,然而货币有它自己的特点,当你对货币进行操作时有些东西是要牢记在心的。

The biggest change surrounds the whole area of conversion. While physical units' forms of conversion don't change over time, exchange rates for money are always changing. Obviously this affects conversion operations, but the effect ripples into the addition, subtraction, and comparison operations as well.

最大的区别主要分布在类型转换里面。物理单位的转换不会随时间发生改变,但货币的汇率却总变。显然,这将影响货币的币值转换工作,同时会波及货币的加法、减法以及比较运算。

When you convert money, at the least you need to provide some kind of time reference, whose granularity will depend on the application. But in many cases you may have separate conversions in different contexts - all of which I explore in Convertor

当你进行货币转换时,至少要提供某种时间作为参考,时间的粒度取决于你的应用系统。但在绝大多数情况下,你可以将转换工作分离到一个单独的上下文中-这在Convertor(转换器模式)中有详细介绍。

The upshot of all this is that you need to be much more careful about automatic conversion inside arithmetic or comparison operations with money. So often you'll find they are not allowed. However there is an ingenious solution to addition operations with money that you'll find in Money Bag.

如此这般,你将需要小心的处理货币的自动数值转换工作或币值比较工作。因此,你通常发现这种操作是被禁止的。不过有种巧妙的解决方案可以解决货币的加法运算,这可以参考Money Bag模式。

A particularly useful feature with Money in addition to those for Quantity is the handling of rounding. Money is often expressed with a decimal part, yet you should never use real numbers for handling Money. The reason for this is that the rounding behavior of real numbers almost never corresponds to what's needed for money, and ignoring this fact can easily lead to intermittent bugs which are small in denomination and high in frustration.

相对于数量而言,货币的一个非常有用的特点就是处理四舍五入。货币经常包含一个“小数部分”,你千万不要用“实数”方式来处理货币,这是因为实数的四舍五入方式从来都不考虑货币类型的需要,如果忽略这个事实就很容易导致接连不断的错误发生。也许只差几分钱,但会严重影响你的系统。

A money object, however, can encode its own rules for rounding, which means that most of the time you don't have to be aware of the rounding rules while you are working with money.

货币对象可以封装自己的四舍五入规则,这意味着当你处理货币类型时,通常不用去考虑货币的四舍五入问题。

Connected to this is the knotty matter of division. If you divide $100 by three, what do you get? Usually the answer is not as simple $33.33. The problem is that if you multiply $33.33 you get $99.99 - meaning that a penny goes missing. Accountants don't like pennies to go missing. So you'll have to find out what policy applies to the situation you're looking at. Often the rule is that someone should get extra penny, although it doesn't matter who. So you can put a method in the money object to return a collection of the monies that you need to hand out from the division.

与此相关联的是棘手的除法问题。 如果将100美元除3,你将得到什么?通常的答案不是简单的33.33美元。问题是如果您将33.33美元乘3将得到99.99美元-这意味着一便士“蒸发”了。会计并不喜欢“蒸发”的便士。 因此你必须找出在这种情况下所应采取的策略。通常的规则是:某人应该得到额外的便士,虽然它不事关谁。 因此你可以在货币类中添加一个方法,返回一个除法运算后货币的集合。

Relational Databases

关系型数据库

A common question with Quantity is how to use it for relational databases and other systems without the ability to create new lightweight types. Do you store an amount and a currency code with every monetary value?

关于数量的一个常见问题就如何将其用在关系型数据库中或是其它不支持创建新类型的系统中?您会为币值单独存储一个数值和一个币种信息吗?

The issue here comes when there is a constraint in place that forces all monies to be of the same currency in a certain context. So consider the case where you have an account with many entries. Each entry has a money attribute to show the amount of the entry, yet all the entries on an account have the same currency. Is it reasonable to store the currency once on the account and not duplicate the currency across the entries?

问题是当我们强制所有货币必须币种一样的情况下如何进行处理?假设我们拥有一个有多笔记录的帐户,每笔记录都有一个货币属性标识金额,但所有记录中的币种都是相同的,那我们是在每笔记录中重复存储币种信息还是将币种信息只存储一次呢?

I'm inclined to punt on this question, and leave it to the specifics of your database design. I would still urge you to use money objects in your code: it's up to you whether how you store those in the database. After all the database doesn't give you any behavioral advantages from Quantity. You only get those in the code.

我不准备回答这个问题,它将取决于你的数据库设计。但是我还会建议你在你的代码中使用货币对象,至于你如何将其存储在数据库中由你自己定夺。毕竟数据库并不能象数量一样给你“行为”优势,这种优势只能在代码中获得。

When to Use it

应用时机

As you've gathered I use Quantity, at least in the money variation, a lot. Indeed with an object-oriented environment there's little reason not use it. Often I've noted that people are scared about using small objects like this mostly due to unfamiliarity. Performance is an oft-cited concern, although I've not seen or heard of Quantity being a really problem for performance.

你可能会对我大量使用数量(至少是货币)感到迷惑不解。事实上,在一个面向对象的环境中没有理由不使用它。我经常发现人们由于不熟悉使用这些小对象而被它们吓倒。效率问题往往是这些人援引的理由,但我从来没有看到甚至听到数量的使用成为影响效率的一个严重问题。

There's an argument that says that using Quantity isn't worth it when there's only one unit, so you shouldn't use money when you only have one currency. But much of the value of money comes from its behavior, not its multi-unit capabilities, so I would use it even then.

目前存在一个争论,那就有人认为当数量只有一个单位时不值得使用数量模式,也就是说如果货币只有一种币种的话,就没有必要使用货币模式。但是,货币带来的价值来自于它的行为,而不是它能表示多种单位。因此我仍然会使用它。

Example: Money

示例:货币

Money is my most common use of Quantity, so it makes a good place for an example. First the basic representation:

货币是数量里面最常用的一种,所以最好在这里给出一个示例。我们先看一看基础表示:

class Money... 
public class Money implements Comparable{
 private BigInteger amount;
 private Currency currency;

Notice that I use a BigInteger. In Java I could equally well use a BigDecimal, but in many languages an integer is the only decent option, so using an integer seems the best for explanation. Don't be afraid to choose your representation of the amount part of a quantity based on performance factors. The beauty of objects is that you can choose any data structure you like on the inside, providing you hide it on the outside.

注意, 我使用了BigInteger。在Java里面我也可以等同的使用BigDecimal,但在许多语言里整数是唯一的得体选择,因此出于演示目的使用整数似乎是最佳的选择。不要过分担心性能因素而影响你选择数量数额部分的表示手段。面向对象最优雅的地方是你可以在对象内部选择你喜欢的数据结构,只要不把它们展现出来就行了(译注:对象的封装)。

So moving to the interface. You'll probably want the obvious getting methods.

让我们到货币的调用接口上看看,你也许最需要的是get方法。

class Money... 
 public double amount() {
  return amount.doubleValue() / 100;
 }
 public Currency currency() {
  return currency;
 }

Although you shouldn't find yourself using them very much. Usually other methods will be better for your purpose. Indeed using the getters on quantity should always spark a thought in your mind as to whether there is a better way. Usually there is.

尽管你会发现不怎么经常使用它们,通常其它的方法调用更能达到你的目的。实际上在数量里面使用get方法经常会让你脑子在思考是否还有什么更好的方法时迸出一些火花,通常都有的。

You'll notice there are no setters. Money is a Value Object and is thus immutable.

你可能注意到了货币里面没有set方法,货币是Value Object(值对象),因此它是不可改变的。

It helps to have a variety of constructors to make it easy to make monies. Constructors that convert from basic numeric types are always useful.

最好我们拥有一系列构造函数以便我们生成货币的实例。将基础类型自动进行转换的构造函数通常都很有用。

class Money... 
 public Money (double amount, Currency currency) {
  this.amount = BigInteger.valueOf(Math.round (amount * 100));
  this.currency = currency;
 }
 public Money (long amount, Currency currency) {
  this.amount = BigInteger.valueOf(amount * 100);
  this.currency = currency;
 }

If you use one currency a lot, you may want a special constructor for that currency.

如果你经常使用某种货币,或许你会考虑为该种货币专门建立一个构造函数。

class Money... 
 public static Money dollars(double amount) {
  return new Money (amount, Currency.USD);
 }

For addition and subtraction I'm not trying to do any fancy conversion. Notice that I'm using a special constructor with a marker argument.

对于加法和减法,我不会尝试去做一些奇特的转换。注意我使用了一个带有标识符的特殊的构造函数(译注:因为Money是值类型,所以需要构造一个新实例)。

class Money... 
 public Money add (Money arg) {
  assertSameCurrencyAs(arg);
  return new Money (amount.add(arg.amount), currency, true);
 }
 public Money subtract (Money arg) {
  return this.add(arg.negate());
 }
 void assertSameCurrencyAs(Money arg) {
  Assert.equals("money math mismatch", currency, arg.currency);
 }
 private Money (BigInteger amountInPennies, Currency currency, boolean privacyMarker) {
  Assert.notNull(amountInPennies);
  Assert.notNull(currency);
  this.amount = amountInPennies;
  this.currency = currency;
 }
 public Money negate() {
  return new Money (amount.negate(), currency, true);
 }

Multiplication is very straightforward.

乘法运算很简单:

class Money... 
 public Money multiply (double arg) {
  return new Money (amount() * arg, currency);
 }

But division is not, as we have to take care of errant pennies. We'll do that by returning an array of monies, such that the sum of the array is equal to the original amount, and the original amount is distributed fairly between the elements of the array. Fairly in this sense means those at the begriming get the extra pennies.

但除法不是,因为我们必须处理好不定的便士。我们通过返回一个货币数组实现,该数组各元素之和是原始值,该原始值被“公平”的分成了多份,每份对应一个数组元素。“公平”是指那些排在前面(译注:原文begriming应当是笔误,我理解为begining)的货币获得了额外的便士。

class Money... 
 public Money[] divide(int denominator) {
  BigInteger bigDenominator = BigInteger.valueOf(denominator);
  Money[] result = new Money[denominator];
  BigInteger simpleResult = amount.divide(bigDenominator);
  for (int i = 0; i < denominator ; i++) {
   result[i] = new Money(simpleResult, currency, true);
  }
  int remainder = amount.subtract(simpleResult.multiply(bigDenominator)).intValue();
  for (int i=0; i < remainder; i++) {
   result[i] = result[i].add(new Money(BigInteger.valueOf(1), currency, true));
  }
  return result;
   }

Next we'll look at comparing monies, in Java the approach is to implement comparable.

下面我们看看货币的比较,在Java里我们靠实现comparable接口完成。

class Money... 
 public int compareTo (Object arg) {
  Money moneyArg = (Money) arg;
  assertSameCurrencyAs(moneyArg);
  return amount.compareTo(moneyArg.amount);
 }

It's also useful to provide some better named operations such as:

最好在里面提供一些名字更好听的方法,例如:

class Money... 
 public boolean greaterThan(Money arg) {
  return (this.compareTo(arg) == 1);
 }
 public boolean lessThan(Money arg) {
  return (this.compareTo(arg) == -1);
 }

That makes methods that need the comparison much easier to read.

这会让比较时调用的方法更易读。

Since money is a value, it should override equals.

既然货币是值类型数据,它应当复写equals方法:

class Money... 
 public boolean equals(Object arg) {
  if (!(arg instanceof Money)) return false;
  Money other = (Money) arg;
  return (currency.equals(other.currency) && (amount.equals(other.amount)));
 }

Since you override equals, don't forget to also override hash (here's a simple suggestion for that).

既然复写了equals方法,不要忘了复写hash(下面是一段提示性代码)。

class Money... 
 public int hashCode() {
  return amount.hashCode();
 }
posted on 2005-08-10 17:23  吕震宇  阅读(6429)  评论(9编辑  收藏  举报