Invoiceサンプルコードの拡張
まずは、Invoice
クラスとInvoiceLineItem
クラスを変更して、それぞれのクラスの基本クラスを利用するようにします。そのためには、さまざまなField
オブジェクトの登録が必要です。
public class Invoice : Entity { public Invoice() { RegisterField("InvoiceDate", InvoiceDate); RegisterField("InvoiceID", InvoiceID); RegisterField("InvoiceNumber", InvoiceNumber); RegisterField("Items", Items); RegisterField("ItemsTotalPrice", ItemsTotalPrice); RegisterField("ItemsTotalTax", ItemsTotalTax); RegisterField("ItemsTotalWeight", ItemsTotalWeight); RegisterField("Shipping", Shipping); RegisterField("ShippingRate", ShippingRate); RegisterField("TaxRate", TaxRate); RegisterField("InvoiceTotal", InvoiceTotal); } // Remainder of the Class is identical to previous version..... } public class InvoiceLineItem : Entity { public Invoice() { RegisterField("InvoiceID", InvoiceID); RegisterField("ItemID", ItemID); RegisterField("LineNumber", LineNumber); RegisterField("Quantity", Quantity); RegisterField("Taxable", Taxable); RegisterField("UnitPrice", UnitPrice); RegisterField("Weight", Weight); RegisterField("ExtendedPrice", ExtendedPrice); } // Remainder of the Class is identical to previous version..... }
続いて、特定のフィールドの変更に関係するすべてのフィールドを適切に更新するための一連のイベントを追加します。こうした関係は、インボイスについて回るものなので、クラスの内部に実装してオブジェクトの生成時に用意するようにします。
public class Invoice : Entity { // Called from Constructor after registration private void InstallRules() { Items.CollectionChanged += Items_CollectionChanged; ItemsTotalPrice.ValueChanged += Recalculate_Tax; ItemsTotalTax.ValueChanged += Recalculate_Total; ItemsTotalWeight.ValueChanged += Recalculate_Shipping; ItemsTotalTax.ValueChanged += Recalculate_Total; ShippingRate.ValueChanged += Recalculate_Shipping; TaxRate.ValueChanged += Recalculate_Tax; } private void Items_CollectionChanged(object sender, CollectionChangedEventArgs e) { InvoiceLineItem lineItem = e.Item as InvoiceLineItem; switch (e.ChangeType) { case CollectionChangedEventArgs.CollectionChangeTypes.Added: ItemsTotalPrice.Value += lineItem.ExtendedPrice.Value; ItemsTotalWeight.Value += lineItem.Weight.Value; break; case CollectionChangedEventArgs.CollectionChangeTypes.Removed: ItemsTotalPrice.Value -= lineItem.ExtendedPrice.Value; ItemsTotalWeight.Value -= lineItem.Weight.Value; break; case CollectionChangedEventArgs.CollectionChangeTypes.Cleared: ItemsTotalPrice.Value = 0.0M; ItemsTotalWeight.Value = 0.0M; break; } } private void Recalculate_Tax(object sender, ValueChangedEventArgs<decimal> e) { ItemsTotalTax.Value = ItemsTotalPrice.Value * TaxRate.Value; } private void Recalculate_Shipping(object sender, ValueChangedEventArgs<decimal> e) { Shipping.Value = ItemsTotalWeight.Value * ShippingRate.Value; } private void Recalculate_Total(object sender, ValueChangedEventArgs<decimal> e) { InvoiceTotal.Value = ItemsTotalPrice.Value + ItemsTotalTax.Value + Shipping.Value; } public void AddItem(InvoiceLineItem lineItem) { lineItem.LineNumber.Value = Items.Count + 1; Items.Add(lineItem.LineNumber.Value, lineItem); lineItem.ExtendedPrice.ValueChanged += ExtendedPrice_ValueChanged; } private void ExtendedPrice_ValueChanged(object sender, ValueChangedEventArgs<decimal> e) { ItemsTotalPrice.Value += (e.NewValue - e.OldValue); } }
ようやくインボイスのクラスができました。非常にシンプルな実装によって、簡単なインボイスの情報を扱える論理演算がすべて実現されています。このコードは、メンテナンスもテストもきわめて容易です。
使い方を示すために、サンプルプログラムを載せておきます。
private static void Main() { Invoice invoice = new Invoice(); invoice.InvoiceNumber.Value = "1000"; invoice.TaxRate.Value = 0.07M; invoice.ShippingRate.Value = 1.23M; InvoiceLineItem invoiceLineItem = new InvoiceLineItem(); invoiceLineItem.Quantity.Value = 10; invoiceLineItem.UnitPrice.Value = 19.95M; invoiceLineItem.Weight.Value = 0.75M; invoice.AddItem(invoiceLineItem); Console.WriteLine("Invoice Items Total: {0}", invoice.ItemsTotalPrice.Value); Console.WriteLine("Invoice TAX: {0}", invoice.ItemsTotalTax.Value); Console.WriteLine("Invoice Weight: {0}", invoice.ItemsTotalWeight.Value); Console.WriteLine("Invoice Shipping: {0}", invoice.Shipping.Value); Console.WriteLine("Invoice GRAND TOTAL: {0}", invoice.InvoiceTotal.Value); }
高度なトピック
設計のこの段階で説明するには高度な内容ですが、フィールドのコーディングと登録を明示的に行う方法は他にもたくさんあることを知っておいてください。本格的な機能を実装すれば、リフレクションを使って適用可能なフィールドを自動で登録したり、要求された情報の提供に属性を使ったりできるでしょう。現在取り上げているシンプルなアーキテクチャが完成したら、こうした代替案についても説明したいと思います。
まとめ
このシリーズの解説はまだ初期の段階ですが、最小限のコーディングで、れっきとしたアプリケーションを作成することができます。今後、数回にわたって拡張していけば、このInvoiceサンプルに対する(大きな)変更は一切不要になるでしょう。こうしたスタイルのアーキテクチャと実装の本当の強みはそこにあります。必要に応じてライブラリクラス(Field
、Entity
、EntityCollection
)の機能を強化すれば、その結果を新旧双方のアプリケーションで直ちに利用できるのです。
補足
本シリーズ記事の内容は、Dynamic Concepts Development Corp.が公開している「スマートアーキテクチャ」のガイダンスに基づいています。このアーキテクチャはオープンで、開発者コミュニティが制約なしに利用できる一連の推奨インターフェイスを備えています。商用ベンダは、このアーキテクチャを利用した実装やツールを自由に開発することができます。