Depreciation is computed using a variety of formulas that depend upon asset type. Often the same asset can be depreciated using different formulas. The mapping of formula to asset type depends upon tax law, prevailing accounting practices, and local business policies. These laws, practices, and policies are subject to change over time. Depreciation values are computed from the base value of the asset (usually the purchase price) for each of several accounting periods (usually years) in its depreciable life.
Typically depreciation is handled in an OOA/D model something like:
* computed by 1 [Asset] ------------------ [Depreciation] + type R1 + periodCount + baseValue + compute(period) A | R2 +----------+---------+---... | | [Linear] [DoubleDeclining]
where each [Depreciation] subclass implements the compute() method with a different formula. The R1 relationship is instantiated to the correct subclass by applying the local laws, practices, and policies when the Asset is created.
This already extracts an invariant form the problem space in that the computation for each formula has the same interface. However, it has some drawbacks. If a new formula is added, the tree has to be modified and the formula encoded. More important, the factory that instantiates assets and the relationship needs to be modified.
One can fix the factory problem by realizing that all the factory is doing is linking a particular [Asset] type to a [Depreciation] subclass. If we identify the [Depreciation] subclass, we can define the relationship in an external configuration file that simply lists pairs of values for Asset.type and Depreciation.type. [Life is rarely so simple and the links can be more complicated (e.g., "grandfathered" assets). However, the rules of instantiation can always be described in terms of data values; it just makes the configuration file more complicated.] So we can automate that portion of the model by simply recognizing the invariants in the way in which assets and formulas are related.
The next step is to deal with the formulas themselves. The key here is to recognize that no matter what the formula is, one is computing a value of depreciation in a particular accounting period. In particular, that value will always be a fixed percentage of the base asset value. All that varies from one formula to another is the percentage in any given accounting period. This leads us to a quite different model:
* specified by 1 [Asset] --------------------- [DepreciationSpec] + type R1 + periodCount + baseValue + pctArray + compute(period) + formulaType
Now [Asset] computes its own depreciation. That computation is simply:
value = this.baseValue * myDepreciationSpec.pctArray[period] * 100.0
which is exactly the same for all depreciation computations. The R1 relationship is instantiated in exactly the same way as before except the match is between Asset.type and DepreciationSpec.formulaType. In addition, the [DepreciationSpec] instances can be created from an external configuration file at startup. That is, the values in the pctArray are computed externally using the appropriate formula and placed in the configuration file.
This leads to a simpler model with fewer executable statements to calculate depreciation. More important, for most plausible changes in tax laws, accounting practices, or business policies the code will not have to be touched -- everything can be done in the configuration file. The key to this robustness lies in encoding invariants an leaving the detailed differences to external data.