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.
Part 1: Giving Your Data Some Brains
Every application is made up of a set of objects, and most of these objects will expose a set of properties that will be manipulated. The use of properties rather than directly exposing data members provides two benefits. First, there is the ability to implement implicit operations within the getter and setters. Second, it provides a common basis for tools such are reflection even when there are no implicit operations. Start with a simple set of classes that represent an invoice.
public class Invoice { Invoice() { Items = new List<InvoiceLineItem>(); } public Guid InvoiceID { get; set; } public string InvoiceNumber { get; set; } public string InvoiceDate { get; set; } public ICollection<InvoiceLineItem> Items { get; private set; } public decimal ItemsTotalPrice { get; set; } public decimal ItemsTotalTax { <get; set; } public decimal ItemsTotalWeight { <get; set; } public decimal Shipping { get; set; } public decimal ShippingRate { get; set; } public decimal TaxRate { get; set; } public decimal InvoiceTotal { get; set; } } public class InvoiceLineItem { public Guid ItemID { get; set; } public Guid InvoiceID { get; set; } public int LineNumber { get; set; } public decimal Quantity { get; set; } public bool Taxable { get; set; } public decimal UnitPrice { get; set; } public decimal Weight { get; set; } public decimal ExtendedPrice { get; set; } }
By using the .NET 2.0 capability of automatic properties, you have eliminated the need to manually implement the mechanics of the get and set operations. However, this savings comes at the cost of not being able to place any logic in them. As soon as you want to put in an operation on either the get or the set, you have to revert back to:
private decimal m_ExtendedPrice; public decimal ExtendedPrice { get { return m_ExtendedPrice; } set { m_ExtendedPrice = value; } }
You are now up to 6 lines (even with the compressed formatting) for each property. This bloats out source code up to well over 110 lines for just the properties, instead of the current 19. To reduce this, you can write a simple class that encapsulates the syntax of a property.
public class Field<DATA_TYPE> { private DATA_TYPE m_Value; public DATA_TYPE Value { get { return m_Value; } set { m_Value = value; } } }
For (temporary) simplicity, you will re-implement your invoicing classes using this helper with [read only ] fields rather than properties.
public class Invoice { Invoice() { Items = new List<InvoiceLineItem>(); } public readonly Field<Guid> InvoiceID = new Field<Guid>(); public readonly Field<string> InvoiceNumber = new Field<string>(); public readonly Field<string> InvoiceDate = new Field<string>(); public ICollection<InvoiceLineItem> Items { get; private set; } public readonly Field<decimal> ItemsTotalPrice = new Field<decimal>(); public readonly Field<decimal> ItemsTotalTax = new Field<decimal>(); public readonly Field<decimal> ItemsTotalWeight = new Field<decimal>(); public readonly Field<decimal> Shipping = new Field<decimal>(); public readonly Field<decimal> ShippingRate = new Field<decimal>(); public readonly Field<decimal> TaxRate = new Field<decimal>(); public readonly Field<decimal> InvoiceTotal = new Field<decimal>(); } public class InvoiceLineItem { public readonly Field<Guid> ItemID = new Field<Guid>(); public readonly Field<Guid> InvoiceID = new Field<Guid>(); public readonly Field<int> LineNumber = new Field<int>(); public readonly Field<decimal> Quantity = new Field<decimal>(); public readonly Field<bool> Taxable = new Field<bool>(); public readonly Field<decimal> UnitPrice = new Field<decimal>(); public readonly Field<decimal> Weight = new Field<decimal>(); public readonly Field<decimal> ExtendedPrice = new Field<decimal>(); }
This will require that you specifically use the Value property of a field when setting or retrieving information. At first glance, it looks like you have made the class more complicated to declare, and also more complicated to use. But, as you start to expand the capabilities of your Field class, you will begin to see significant benefits. First, add the ability to validate and assignment to a Field instance. For this example, you will create the base class, and then a specific validator to ensure that a decimal quantity is within a range.
public class Validator<DATA_TYPE> { public virtual void Validate(DATA_TYPE value) { return; } } public class RangeValidator : Validator<decimal> { public RangeValidator(decimal min, decimal max) { m_Min = min; m_Max = max; } public override void Validate(decimal value) { if ((value < m_Min) || (value > m_Max)) throw new ArgumentException(); } private readonly decimal m_Min; private readonly decimal m_Max; }