Developing a Smart Architecture, Part 2

Developing an Application Architecture

Overview

This series of articles will walk through the creation of a simple application architecture that can be utilized in programs of nearly any size. The code posted in these articles is for illustrative purposes of concepts and is not intended for direct production use.

Review

In Part 1 of this series, you did a detailed walkthrough of creating intelligent data fields that reduced the reprtitive coding effort while at the same time providing both (simple) validation and change notification.

Part 2: Building Intelligent Entities

Now that you have the ability to create intelligent fields, you need to expand this to creating useful intelligent classes. To begin this process, you will need to re-work a few of your existing classes. This is primarily being done so that you can treat specialized instances of the generic Field class in a unified way. You will create an interface and then update the class to implement this interface.

public interface IField
{
   event EventHandler<ValueChangedEventArgs<Object>> Changed;
}


public class Field<DATA_TYPE> : IField
{
   // Basic implementation remains the same with the following
   // additions/changes...
   private void Fire_ValueChanged(DATA_TYPE oldValue,
                                  DATA_TYPE newValue)
   {
      if (ValueChanged != null)
         ValueChanged(this, new ValueChangedEventArgs<DATA_TYPE>
                      (oldValue, newValue));
      if (Changed != null)
         Changed(this, new ValueChangedEventArgs<Object>
                 (oldValue, newValue));
   }
   public event EventHandler<ValueChangedEventArgs<Object>> Changed;
}

Because every instance of a Field<T> can now be treaded as an IField, you can create your Entity class. The exposed functionallity is as follows:

public class Entity : IField
{
   public void RegisterField(string name, IField field) {...}
   public Field<DATA_TYPE>GetField<DATA_TYPE>(string name) {}

   public event EventHandler<ValueChangedEventArgs<object>> Changed;
   public event EventHandler<FieldValueChangedEventArgs> FieldChanged;
}

You now have a basic set of abilities, including:

  • Registering Fields with the Entity and assigning them a Name
  • Retrieving a given Field by Name
  • Propogating events from the Fields to the Entity itself

It is important to note that an Entity is itself an IField. This allows for the creations of entities, whereas the fields themselves are entities.

You will also need the ability to create collections of these entities. For right now, you will only implement a few critical methods and properties.

public class EntityCollection<KEY_TYPE, ENTITY_TYPE> : Entity
   where ENTITY_TYPE : Entity
{
   public void Add(KEY_TYPE key, ENTITY_TYPE item) {}
   public bool Remove(KEY_TYPE key) {}
   public event EventHandler<CollectionChangedEventArgs>
      CollectionChanged;
   public void Clear()
   public int Count { get { } }
   public ENTITY_TYPE this[KEY_TYPE key] {}

}

It is important to note that an EntityCollection is itself an Entity (and therefor a IField). This allows any EntityCollection to be a Field member of an Entity.

Both the Entity and EntityCollection classes at this point are still very simplistic, but are already ready to create some powerful examples.

Enhancing the Invoice Sample Code

First, you must update your Invoice and InvoiceLineItem classes, and enhance them to make use of these entity base classes. This will require the Registration of the various Field objects in the constructor.

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.....
}

Now, you can add a series of events that will ensure that all fields are properly updated when their dependancy fields change. Because these “rules” are inherent in the nature of your Invoice, you will implement them internally to the class and install them during object construction.

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);
   }
}

More by Author

Must Read