Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C#

Building an application using design patterns and principles in C#

Rate me:
Please Sign up or sign in to vote.
4.90/5 (76 votes)
23 Apr 2013CPOL9 min read 157.9K   234   33
I am writing this article to demonstrate how to build an application from scratch using SOLID principles and common design patterns

Introduction 

This article will be part I in a series of articles describing how to build a software application in c# using common enterprise architecture principles and design patterns. It will start by giving code examples of how it might be done in a quick and dirty yet not ideal way and then will explain how you re factor the code to make it adhere more to common coding guidelines. I'll assume the reader is familiar with C# and basic Object Oriented Principles. I will be making referencing to SOLID principles and certain design patterns. I will not go into defining those here. There are plenty of articles out there to do that.

So what are we trying to accomplish here. Let me use an analogy. You want to build a solid house. How do you do this? Hopefully the architect and builder know what they are doing: creating a stable foundation, framing the proper way, doing electrical work up to code and so on.  Well, this is what we try to accomplish when coding. We want our code to be S.O.L.I.D. (Look up that acronym for more explanation). I’ll explain what it means as we go along.  How do we accomplish this? Hopefully the architect and developer know what they are doing and use Design Patterns. You use design patterns as a technique to creating solid code. SOLID is the goal, design patterns are a way to accomplish this goal.

Starting out

Let's start with the typical e-commerce type scenario. We need an object to represent an Order, Order Item, and Customer. Given an Order object that has a method to calculate total by applying tax to the sum of the order items' cost , what is the simplest yet worst way you can program it to do so? By putting the tax routine logic in the Order class of course.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item)=>{
                return item.Cost * item.Quantity;
            });
 
            decimal tax;
            if (customer.StateCode == "TX")
                tax = total * .08m;
            else if (customer.StateCode == "FL")
                tax = total * .09m;
            else
                tax = .03m;
 
            total = total + tax;
            return total;
        }
 
  
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

Whats wrong with this? Academically speaking, it is violating one of the first rules of the SOLID programming principles: Single Responsibility. In other words, the Order object should only be responsible for doing things tightly related to the Order, such as totaling the order items cost. It should not have to manage the tax routines on a state by state basis also. You might even go further and say it should not be responsible for totaling the order items and that it's only job is to coordinate the workflow of totaling the order items, adding tax and perhaps adding shipping charges or applying discounts. But we'll keep it simple for now.

Well, what else is wrong? This code is violating another SOLID principal: Open/Closed. In other words once a class is defined it should never change(its closed), only be extended by inheritance or run time variance(open to extension). It may seem strange or unrealistic but ideally, if everything was done correctly from business rules gathering to design to coding, you should never have to change the source code of a class ever again once its tested and approved for production. Well here we can see this is not the case. Any time a State tax routine is added or changed, the Order class code has to change. So what to do? Lets change the code up a bit and start using a pattern. 

Strategy Pattern 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 
            
 
            total = total + new Tax().CalculateTax(customer,total);
            return total;
        }
        public class Tax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        } 
    }   
}

All we did is create a new class called Tax, and put the tax routine logic in there. Now the Order object is no longer responsible for the tax logic and can yield to the tax object to handle this. This is a simple example of the Strategy Pattern. To define it simply, it says to put all separate logic in its own class. Great, but what is wrong now. We are violating another SOLID principal: Dependency Inversion. This says that classes should be dependent on abstractions not concrete implementations. In C#, abstracts are represented by an interface or an abstract class. In our code above we have a dependence on a concrete implementation of Tax by way of

C#
new Tax(); 

Not to mention that the Tax object is still violating the Open/Close principal since tax routines can still be added or changed. We just can't win. Let's continue re factoring.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = new Tax();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 

        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
    }
}

So what we've now done is create a tax interface to abstract the implementation. Its a little bit better now but we are still instantiating a concrete class. The Order class should not be responsible for this. It should not care what kind of ITax object it is working with, only that it is of a instance of ITax with a method CalculateTax. In the above code it knows that its creating a Tax object. This is no good. We need something else to be responsible choosing what kind of ITax object gets created.

Factory Pattern 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = new TaxFactory().GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
       public class TaxFactory
       {
            public ITax GetTaxObject()
            {
                return new Tax();
            }
       } 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
    }
}

Simply defined, this pattern says that for any given scenario there should be a class whose only responsibility is to create other classes based on some changing criteria. Using the factory pattern we create a class responsible for creating the tax object for us. Now the Order object is completely ignorant of how we create the tax object or which concrete implementation of ITax it is working with. It just cares that it has an ITax with a method CalculateTax. At this time you might be wondering what the point of this is, its just more code and the TaxFactory just returns one kind of tax object, not very useful. Lets introduce a new problem: what if a new business rule was introduced that says, there is a specific tax routine to use if a county exists otherwise just use the state tax routine. Now we need to change our code. Lets start by coding the bad way. We might do something like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = new TaxFactory().GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (!string.IsNullOrEmpty(customer.County))
                {
                    if (customer.County == "Travis")
                        tax = total * .085m;
                    else if (customer.County == "Hays")
                        tax = total * .095m;
                    else
                        tax = .03m;
 
                }
                else
                {
                    if (customer.StateCode == "TX")
                        tax = total * .08m;
                    else if (customer.StateCode == "FL")
                        tax = total * .09m;
                    else
                        tax = .03m;
                }
                return tax;
            }
        }
 

        public class TaxFactory
        {
            public ITax GetTaxObject()
            {
                return new Tax();
            }
        } 
    }
}

We have put conditionals into the tax object to check for county. Well, we broke more rules. We changed a class which should not be changing(open/closed). And now the Tax object is responsible for the logic to decide if County or State tax routines should be used (Single Responsibility). What if business rules update again, the class will have to change again. It sounds like we need to add another class

C#
 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
  
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = new TaxFactory().GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
 
                    if (customer.StateCode == "TX")
                        tax = total * .08m;
                    else if (customer.StateCode == "FL")
                        tax = total * .09m;
                    else
                        tax = .03m;
                
                return tax;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
 
        public  class TaxFactory
        {
            public  ITax GetTaxObject()
            {
                return new Tax();
            }
        } 
    }
}

Here we have added another class to handle the tax routines that are based on county, TaxCounty. Great, now we have another class to handle all that, but someone needs to decide whether to use the TaxCounty object or the normal Tax object, who does this? The TaxFactory seems like a good candidate to do this. Factory objects exists to make decisions and decide what class should be returned. But wait, now we have to change the TaxFactory because it doesn't have any reference to customer in order to make the decision.  Let's go back and do something we should have done in the first place. 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
  
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = new TaxFactory().GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
 
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
 
                return tax;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
 
        public interface ITaxFactory
        {
             ITax GetTaxObject();
        }
        public  class TaxFactory:ITaxFactory
        {
            public  ITax GetTaxObject()
            {
                return new Tax();
            }
        }
        public class CustomerBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            public CustomerBasedTaxFactory(Customer customer)
            {
                _customer = customer;
            }
            public ITax GetTaxObject()
            {
                if (!string.IsNullOrEmpty(_customer.County))
                    return new TaxByCounty();
                else
                    return new Tax();
            }
        } 
    }
}

You might have noticed a problem still exists in the Order class. Got it? Yes, we still have a reference to a concrete implementation of the TaxFactory. Let's create another abstraction for the Factory class, ITaxFactory. Now we can create a new factory that takes a Customer object in its constructor, CustomerBasedTaxFactory. Now what? We want to use the CustomerBasedTaxFactory but we aren't allow to instantiate a concrete instance of it in the Order class, so what to do. This would seem like we need another factory to provide the factories which then would need a factory for it. We are getting stuck in an infinite loop. What can we do? 

Dependency Injection 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        ITaxFactory _taxFactory;
        public Order(ITaxFactory taxFactory)
        {
            _taxFactory = taxFactory;
        }
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = _taxFactory.GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
 
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
 
                return tax;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
 
        public interface ITaxFactory
        {
             ITax GetTaxObject();
        }
        public  class TaxFactory:ITaxFactory
        {
            public  ITax GetTaxObject()
            {
                return new Tax();
            }
        }
        public class CustomerBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            public CustomerBasedTaxFactory(Customer customer)
            {
                _customer = customer;
            }
            public ITax GetTaxObject()
            {
                if (!string.IsNullOrEmpty(_customer.County))
                    return new TaxByCounty();
                else
                    return new Tax();
            }
        }
    }
}

Dependency injection allows a class to tell a consumer of the class what it needs to operate properly. In the case of the Order class it needs a tax factory to work, or a ITaxFactory. DI can be done in 3 ways, I prefer the constructor approach because it explicitly tells the consumer of the class what it needs. You can see in the above code we have added a constructor that takes a ITaxFactory type and stores a reference to it.  In the CalculateTotal method it is just using the reference it has to ITaxFactory to call GetTaxObject. It has no idea what Tax Factory it is using or what Tax object it is using. Ignorance is bliss. But then who is responsible for deciding which TaxFactory is used?  lets implement one technique for this as shown below.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        ITaxFactory _taxFactory;
        public Order()
            : this(new TaxFactory())
        {
        }
        public Order(Customer c)
            : this(new CustomerBasedTaxFactory(c))
        {
        }
        public Order(ITaxFactory taxFactory)
        {
            _taxFactory = taxFactory;
        }
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 

            ITax tax = _taxFactory.GetTaxObject();
            total = total + tax.CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
 
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
 
                return tax;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
 
        public interface ITaxFactory
        {
             ITax GetTaxObject();
        }
        public  class TaxFactory:ITaxFactory
        {
            public  ITax GetTaxObject()
            {
                return new Tax();
            }
        }
        public class CustomerBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            public CustomerBasedTaxFactory(Customer customer)
            {
                _customer = customer;
            }
            public ITax GetTaxObject()
            {
                if (!string.IsNullOrEmpty(_customer.County))
                    return new TaxByCounty();
                else
                    return new Tax();
            }
        }
    }
}

What is sometimes termed "poor man's dependency injection" uses a constructor to hard code a default factory to use if none is specified. There is also something called an IOC container the will automatically inject the desired dependency at run time  This can be setup kind of like a configuration based on some criteria at run time. So let's introduce another business rule: Maybe most days you want to use the normal tax logic, but on one special day all tax is covered by the company for people in Texas 

Nullable Object 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        ITaxFactory _taxFactory;
        public Order()
            : this(new TaxFactory())
        {
        }
        public Order(Customer c)
            : this(new DateBasedTaxFactory(c,new CustomerBasedTaxFactory(c)))
        {
        }
        public Order(ITaxFactory taxFactory)
        {
            _taxFactory = taxFactory;
        }
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 
            total = total + _taxFactory.GetTaxObject().CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
        public class Tax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
 
                if (customer.StateCode == "TX")
                    tax = total * .08m;
                else if (customer.StateCode == "FL")
                    tax = total * .09m;
                else
                    tax = .03m;
 
                return tax;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
        public class NoTax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                return 0.0m;
            }
        }
 

        public interface ITaxFactory
        {
            ITax GetTaxObject();
        }
        public class TaxFactory : ITaxFactory
        {
            public ITax GetTaxObject()
            {
                return new Tax();
            }
        }
        public class CustomerBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            public CustomerBasedTaxFactory(Customer customer)
            {
                _customer = customer;
            }
            public ITax GetTaxObject()
            {
                if (!string.IsNullOrEmpty(_customer.County))
                    return new TaxByCounty();
                else
                    return new Tax();
            }
        }
        public class DateBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            ITaxFactory _taxFactory;
            public DateBasedTaxFactory(Customer c, ITaxFactory cb)
            {
                _customer = c;
                _taxFactory = cb;
 
            }
 

            public ITax GetTaxObject()
            {
                if (_customer.StateCode == "TX" && 
                       DateTime.Now.Month == 4 && DateTime.Now.Day == 4)
                {
                    return new NoTax();
                }
                else
                    return _taxFactory.GetTaxObject();
            }
        }
    }
}

In the code above, you can see we are using the nullable object pattern to create a new ITax object called NoTax that always returns 0.  We are also using the decorator pattern to change the behavior of the tax factory. We create a new factory called DateBasedTaxFactory that takes an instance of an ITaxFactory to use as default and a customer object. DateBasedTaxFactory is responsible for checking the date and decided whether the NoTax object should be used. We are injecting this factory into the constructor of Order. Now it will automatically handle the decision of whether the NoTax object should be used or just letting the CustomerBasedTaxFactory make the decision of what to use. 

Things are looking better now, all logic is encapsulated in its own classes, but we could go even further. Look at the Tax object, see something wrong? Every time you add a new state you have to add more if statements and if the logic ever changes you have to update the Tax class. You aren't suppose to have to change it ever! So what can be done? 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace solid
{
 
    public class Order
    {
        ITaxFactory _taxFactory;
        
 
        public Order(Customer c)
            : this(new DateBasedTaxFactory(c, new CustomerBasedTaxFactory(c)))
        {
        }
        public Order(ITaxFactory taxFactory)
        {
            _taxFactory = taxFactory;
        }
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum((item) =>
            {
                return item.Cost * item.Quantity;
            });
 
            total = total + _taxFactory.GetTaxObject().CalculateTax(customer, total);
            return total;
        }
 
        public interface ITax
        {
            decimal CalculateTax(Customer customer, decimal total);
        }
 
        public class TXTax:ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                return total * .08m;
            }
        }
        public class FLTax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                return total * .09m;
            }
        }
        public class TaxByCounty : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                decimal tax;
                if (customer.County == "Travis")
                    tax = total * .08m;
                else if (customer.County == "Hays")
                    tax = total * .09m;
                else
                    tax = .03m;
                return tax;
            }
        }
        public class NoTax : ITax
        {
            public decimal CalculateTax(Customer customer, decimal total)
            {
                return 0.0m;
            }
        }
 

        public interface ITaxFactory
        {
            ITax GetTaxObject();
        }
 
        public class CustomerBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            static Dictionary<string, ITax> stateTaxObjects = new Dictionary<string, ITax>();
            public CustomerBasedTaxFactory(Customer customer)
            {
                _customer = customer;
            }
            public ITax GetTaxObject()
            {
                ITax tax;
                if (!string.IsNullOrEmpty(_customer.County))
                    tax = new TaxByCounty();
                else
                {
                    if (!stateTaxObjects.Keys.Contains(_customer.StateCode))
                    {
                        tax = (ITax)Activator.CreateInstance(Type.GetType("solid.Order+" + _customer.StateCode + "Tax"));
                        stateTaxObjects.Add(_customer.StateCode, tax);
                    }
                    else
                        tax = stateTaxObjects[_customer.StateCode];
                }
                return tax;
            }
           
        }
        public class DateBasedTaxFactory : ITaxFactory
        {
            Customer _customer;
            ITaxFactory _taxFactory;
            public DateBasedTaxFactory(Customer c, ITaxFactory cb)
            {
                _customer = c;
                _taxFactory = cb;
            }
            public ITax GetTaxObject()
            {
                if (_customer.StateCode == "TX" && DateTime.Now.Month == 4 && DateTime.Now.Day == 4)
                {
                    return new NoTax();
                }
                else
                    return _taxFactory.GetTaxObject();
            }
        } 
    }
}

Looking at the above code you might be thinking, whoa, what just happened. Where did the Tax object go. Whats all that stuff in the CustomerBasedTaxFactory? Some schools of thought in programming say that you should try to write code with as few if statements as possible. This is related to the open/close principal in that each time you add a new state tax routine you have to modify the Tax object with another if statement (or switch). There are probably going to be a ton of if statements in the Tax class. What can be done? More classes! We can put the tax routine logic of each state in its own class and get rid of the Tax object. But then the if statements will just be put into the factory class. Not so fast. We can use reflection to dynamically get the correct object based on a naming convention. The CustomerBasedTaxFactory will be responsible for this. One thing to consider is that reflection causes some additional overhead so we should cache each item we created, we will do this with a static dictionary with the key based on State Code. No more IFS! We could also do this for the county based taxes.  

I guess that's it for now. Hopefully this was of some value to somebody. I'll pickup up where we left off in the next article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer eLeadCrm
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSimple to understand Pin
Ovidiu Bularda4-Feb-15 22:34
Ovidiu Bularda4-Feb-15 22:34 
QuestionGood Article Pin
Gihan Liyanage16-Jul-14 20:03
professionalGihan Liyanage16-Jul-14 20:03 
QuestionWonderful Article Pin
Sanzida Yeasmin25-Jun-14 18:53
Sanzida Yeasmin25-Jun-14 18:53 
GeneralMy vote of 4 Pin
paymerich23-Oct-13 23:42
professionalpaymerich23-Oct-13 23:42 
QuestionIsnt't the CalculateTotal violating the single responsibility principle? Pin
Fabio Franco16-May-13 1:21
professionalFabio Franco16-May-13 1:21 
AnswerRe: Isnt't the CalculateTotal violating the single responsibility principle? Pin
Jon Woo16-May-13 5:50
Jon Woo16-May-13 5:50 
GeneralRe: Isnt't the CalculateTotal violating the single responsibility principle? Pin
Fabio Franco16-May-13 7:02
professionalFabio Franco16-May-13 7:02 
GeneralRe: Isnt't the CalculateTotal violating the single responsibility principle? Pin
Jon Woo16-May-13 7:05
Jon Woo16-May-13 7:05 
QuestionShould Tax aso. really be a inner class of Order? Pin
ASchabus14-May-13 8:17
ASchabus14-May-13 8:17 
AnswerRe: Should Tax aso. really be a inner class of Order? Pin
Jon Woo14-May-13 11:26
Jon Woo14-May-13 11:26 
GeneralGreat article series! Pin
Mario Mars14-May-13 1:17
Mario Mars14-May-13 1:17 
GeneralMy vote of 5 Pin
Abhinesh M13-May-13 23:35
Abhinesh M13-May-13 23:35 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA10-May-13 18:20
professionalȘtefan-Mihai MOGA10-May-13 18:20 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun8-May-13 23:41
Humayun Kabir Mamun8-May-13 23:41 
GeneralMy vote of 5 Pin
Suresh Apparau28-Apr-13 22:17
Suresh Apparau28-Apr-13 22:17 
GeneralRe: My vote of 5 Pin
Jon Woo29-Apr-13 10:09
Jon Woo29-Apr-13 10:09 
GeneralMy vote of 5 Pin
Himanshu Thawait25-Apr-13 5:32
Himanshu Thawait25-Apr-13 5:32 
GeneralRe: My vote of 5 Pin
Jon Woo25-Apr-13 17:02
Jon Woo25-Apr-13 17:02 
GeneralMy vote of 5 Pin
Patrick Skelton22-Apr-13 22:20
Patrick Skelton22-Apr-13 22:20 
GeneralRe: My vote of 5 Pin
Jon Woo25-Apr-13 17:02
Jon Woo25-Apr-13 17:02 
GeneralMy vote of 5 Pin
adityaswami8922-Apr-13 7:33
professionaladityaswami8922-Apr-13 7:33 
GeneralRe: My vote of 5 Pin
Jon Woo25-Apr-13 17:02
Jon Woo25-Apr-13 17:02 
GeneralSIMPLE AN SWEET Pin
MainFrameMan_ALIVE_AND_WELL$$19-Apr-13 7:24
MainFrameMan_ALIVE_AND_WELL$$19-Apr-13 7:24 
GeneralRe: SIMPLE AN SWEET Pin
Jon Woo25-Apr-13 17:02
Jon Woo25-Apr-13 17:02 
GeneralMy vote of 5 Pin
MainFrameMan_ALIVE_AND_WELL$$19-Apr-13 7:17
MainFrameMan_ALIVE_AND_WELL$$19-Apr-13 7:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.