CSC/ECE 517 Fall 2011/ch7 7a kr
CSC/ECE 517 Fall 2011/ch7 7a kr
7a. Representing money. Skrien Chapter 6 gives an example of a class that can be used to represent money. But how is it done in real programs? Investigate, and report on the advantages and disadvantages of other approaches vs. Skrien's.
Introduction
A common task in real world application programs is conducting operations on and manipulating money values. The base data type used to represent money can be one of several possible implementations. Object oriented languages have the ability to represent this information using classes. Primitive data types such as integers, floating point numbers can also be used to represent money. There are certain inherent advantages and disadvantages with each of these methods of representation. This wiki article explores these different implementations and analyzes their relative merits and demerits. We also focus on the various operations that can be performed with money values and analyze how different languages and schemes help while working with money values.
Money Gem<ref name = moneygemwebsite /> (Ruby)
Overview
The Ruby Money gem borrows quite a bit from the Skrien<ref name = skrien /> text methodologies while using the dynamic properties of Ruby to allow for more flexibility. However, it should be noted that this implementation does not support an object containing mixed currencies as mentioned in the Skrien text and some behavior implemented goes against it's principles.
The Money gem consists of a main Money class which houses several sub-classes and modules which allow for everything to be done from the main Money class directly without the users knowledge. This implementation also mostly sticks to the idea that a Money object is immutable and thus any conversions and operations result in a new Money object. The only exception to this is that the currency identifier for a Money object can be changed once it has been created.
Internally, a Money object stores money as an integer in the form of cents. It should be noted that while the variable is called cents, it can apply to all currencies which have a subunit of denomination. Similar to the way the Skrien text uses the Currency and CurrencyConverter classes, the Money class uses internal classes for currency information and currency conversion as well, called Currency and Bank respectively. The Money object stores a bank and currency as well as default bank and currency types at the class level.
The Currency class contains information such as the ISO4217<ref name = iso4217 /> three letter and number codes for common currencies as well as number of subunits in a denomination (ie, cents in a dollar) and delimiters for units. Through the use of the currency class, Money objects can be initialized with a Currency value and have specific output formats based on currency. The currency class also internally extends the CurrencyLoader class which allows for specifying different locations for loading currency information to allow for flexible currency management. Below is an example of the USD currency information loaded by a default JSON configuration file:
"usd": { "priority": 1, "iso_code": "USD", "name": "United States Dollar", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 100, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": ",", "iso_numeric": "840" },
The Bank class is also an internal class to the Money class but it resides at the class level. This means that the Bank object follows the Singleton pattern in that only one Bank object exists across all of the currency. The purpose of the Bank is to maintain information related to currency values such that currency conversion can take place. Since the Bank class inherits from an interface, this allows for extensible money conversion schemes such as being able to 'scrape' data from the internet and use it to populate conversion ratios.
The Money class also has multiple modules internally that expand the operations that can be performed on Money objects, including converting to and from numbers, strings and symbols. Through the use of Ruby's dynamic nature, to_money() methods are added to the Numeric, String and Symbol types.
Below is an example from the official website <ref name = moneygemwebsite />:
require 'money' # 10.00 USD money = Money.new(1000, "USD") money.cents #=> 1000 money.currency #=> Currency.new("USD") # Comparisons Money.new(1000, "USD") == Money.new(1000, "USD") #=> true Money.new(1000, "USD") == Money.new(100, "USD") #=> false Money.new(1000, "USD") == Money.new(1000, "EUR") #=> false Money.new(1000, "USD") != Money.new(1000, "EUR") #=> true # Arithmetic Money.new(1000, "USD") + Money.new(500, "USD") == Money.new(1500, "USD") Money.new(1000, "USD") - Money.new(200, "USD") == Money.new(800, "USD") Money.new(1000, "USD") / 5 == Money.new(200, "USD") Money.new(1000, "USD") * 5 == Money.new(5000, "USD") # Currency conversions some_code_to_setup_exchange_rates Money.new(1000, "USD").exchange_to("EUR") == Money.new(some_value, "EUR")
Advantages
- The internal CurrencyLoader class allows for easily extensible currency information sources, even allowing currency information to be dynamically loaded
- Due to the Bank implementation, additional class implementations can be loaded and changed out at runtime to allow for more complex, an potentially real-time, currency conversion information
- The fact that the Currency and Bank classes are internal to the Money class, the user doesn't have to know about their implementation
- Ruby's dynamic nature allows for classes to be internal to Money, yet defined in separate files from the Money class, helping to keep the code modular from a developers point of view
- The addition of to_money() methods to basic Ruby primitives makes it easier to create money from just about any type
- Since Ruby supports arbitrarily large integer values, there is theoretically no upper bound to the amount of money that can be stored in a Money object
Disadvantages
- Doesn't support multiple currency types in a single object
- A Money object is immutable except in one way, ability to change currency type, which is an inconsistency that could easily have been avoided
- While the addition of to_money() methods to primitive types makes some things easier, it adds the additional ability to incorrectly combine an object of type Money with one that is not
- Since the Bank object is a singleton contained within the Money class, any modifications to currency exchange rates require locking of the Bank object which could result in performance degradation if exchange rates are modified often
python-money<ref name = pymoneywebsite /> (Python)
Overview
The python-money implementation of storing money in an object is a relatively simple library written in Python. As such, it does not have as much functionality nor the complexities that many other implementations have.
The python-money module consists of two classes, Money and Currency. Much like other implementations, the Money object contains an amount and currency internally. The amount is specified as type Decimal<ref name = pydecimal />, which allows storing the amount in a format similar to a float but without all of the precision issues that arise with using floats. When a Money object is created, either an identifier or Currency object is passed. Below is an example of the Currency data used for USD:
CURRENCY['USD'] = Currency(code='USD', numeric='840', name='US Dollar', countries=['AMERICAN SAMOA', 'BRITISH INDIAN OCEAN TERRITORY', 'ECUADOR', 'GUAM', 'MARSHALL ISLANDS', 'MICRONESIA', 'NORTHERN MARIANA ISLANDS', 'PALAU', 'PUERTO RICO', 'TIMOR-LESTE', 'TURKS AND CAICOS ISLANDS', 'UNITED STATES MINOR OUTLYING ISLANDS', 'VIRGIN ISLANDS (BRITISH)', 'VIRGIN ISLANDS (U.S.)'])
Within the namespace containing these classes, a default list of currency objects are created, indexed by their ISO4217<ref name = iso4217 /> standard three character and number identifiers. This is what allows specifying currency by identifier rather than currency object. Unfortunately this limits the currency types to those statically defined in the source code. Additionally, since there is not a separate class to handle conversions, the exchange rate information is contained within the Currency object. This limits the way that Money objects can be converted.
Advantages
- Much like an integer value in Ruby, a Decimal<ref name = pydecimal /> has a theoretically infinite precision and maximum value.
- As long as the user does not access the internal structure of the Money object directly, it behaves as an immutable object
Disadvantages
- This implementation does not support an arbitrary number of decimal points
- Since the currency conversion data is set within the Currency object itself
- There is no locking present for the Currency object and there could be race conditions when users are simultaneously setting and getting exchange rates
- The current implementation can only convert to and from the default currency type
- Since this implementation can take multiple types of arguments as the monetary value but does not add methods to primitives for converting them to Money objects, type checking has to be done on arguments to determine how to handle them. This is something that we want to avoid as part of object oriented design<ref name = skrien />.
References
<references> <ref name = skrien> Object Oriented Design Using Java / Dale Skrien -- 1st Ed. </ref> <ref name = moneygemwebsite> http://money.rubyforge.org/ </ref> <ref name = pymoneywebsite> http://code.google.com/p/python-money/ </ref> <ref name = pydecimal> http://docs.python.org/library/decimal.html </ref> <ref name = iso4217> http://www.abstracttechnology.com/standard/iso4217.html </ref> </references>