Click here to Skip to main content
Click here to Skip to main content

Building an application using design patterns and principles in C# - Part III

, 23 Apr 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
This one will demonstrate what the point of coding principles are

Introduction

This is part 3 of my series of articles. You’ll probably want to read previous ones first or you wont' know what I'm talking about. In this article I’d like to explain a little bit about what the point of the SOLID coding principles are. So everyone says make your code solid. Well, why?

  1. Part 1 
  2. Part 2 
  3. Part 3  

Dependency Inversion 

So what is the point of this principle. Why can’t you code against hard concrete instances of classes? Let’s focus on a kind of important thing when coding, testing. Hopefully you are aware of unit testing and how important it is. Perhaps you are familiar with Test Driven Development which is where you design your tests first before any coding. You would write tests in order to define the new functionality you are trying to accomplish and then start coding until the test passes.  Let’s look at this code from the previous articles.

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

We have the DateBasedTaxFactory. Well we should probably test that this factory is working properly.  If its April 4th in any year the tax value returned should be 0. We might create a test like this: 

Customer cust = new Customer(){StateCode = "TX",County ="Travis",ZipCode = "78745"};
DateBasedTaxFactory db = new DateBasedTaxFactory(cust, new OCustomerBasedTaxFactory(cust));
ITax tax = db.GetTaxObject();
//test for no tax for a certain date
if (tax.CalculateTax(3m) != 0)
{
    throw new Exception("the value is supposed to be zero");
}

Well what’s wrong here? We can’t really test this! As you can see in the DateBasedTaxFactory, it is directly using the DateTime object’s Now property in order to test what day it is. Well unless you change your system time we can’t make the No Tax situation occur. Changing the system time is not ideal. What else can we do?  This factory class has what is sometimes referred to a hidden dependency. It is hard coded to depend on something that needs to vary. The factory class needs a DateTime object, it does not need that DateTime to be the current date. It really doesn't care what the date given to it is. We need to use dependency injection here in order to tell the outside world what the class needs to work. This will now allow our test to give it whatever date is necessary to test. Such as follows: 

public class DateBasedTaxFactory : ITaxFactory
{
    Customer _customer;
    ITaxFactory _taxFactory;
    DateTime _dt;
    public DateBasedTaxFactory(Customer c, ITaxFactory cb,DateTime dt)
    {
        _customer = c;
        _taxFactory = cb;
        _dt = dt;
    }


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

Now we can adjust our test to send in whatever date we want in order to test. 

Customer cust = new Customer(){StateCode = "TX",County ="Travis",ZipCode = "78745"};
DateBasedTaxFactory db = new DateBasedTaxFactory(cust, new CustomerBasedTaxFactory(cust),
    new DateTime(2001,4,4));
ITax tax = GetTaxObject();
//test for no tax for a certain date
if (tax.CalculateTax(3m) != 0)
{
    throw new Exception("the value is supposed to be zero");
}

Single responsibility /Open closed

So why should your objects only do one thing and why should you not ever change them? Things in real life change so why can’t your code that represents that life change? Let's look at this code from the previous articles which was the first version of the Order class. Well lets say that your company has a firm policy that the main assembly in your system BusinessLogic.dll will only have a release every 2 months. If there is a bug or a change needed before that, it would require a huge undertaking of red tape. But there can be supplemental assemblies that can be defined that can be released as needed with less hassle. If we used the original code:

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

and there was a change in the tax logic for TX or a new state tax was needed, we would have to modify the Order object. This would create a big stink because we now need to test and release the BusinesLogic.dll. And since it has to do with taxes, the law and money it’s a good bet that things will change a lot and it will need to be in production ASAP. So from the other articles we have already done what we need to do, as such: 

public interface ITax
{
    decimal CalculateTax( decimal total);
}

public class TXTax:ITax
{
    public decimal CalculateTax( decimal total)
    {
        return total * .08m;
    }
}   

public class CustomerBasedTaxFactory : ITaxFactory
{
    Customer _customer;
    static Dictionary<string, ITax> stateTaxObjects = new Dictionary<string, ITax>();
    static Dictionary<string, ITax> countyTaxObjects = new Dictionary<string, ITax>();
    public CustomerBasedTaxFactory(Customer customer)
    {
        _customer = customer;
    }
    public ITax GetTaxObject()
    {
        ITax tax;

        if (!string.IsNullOrEmpty(_customer.County))
            if (!countyTaxObjects.Keys.Contains(_customer.StateCode))
            {
                tax = (ITax)Activator.CreateInstance("Tax","solid.taxes." + _customer.County + "CountyTax");
                countyTaxObjects.Add(_customer.StateCode, tax);
            }
            else
                tax = countyTaxObjects[_customer.StateCode];
        else
        {
            if (!stateTaxObjects.Keys.Contains(_customer.StateCode))
            {
                tax = (ITax)Activator.CreateInstance("Tax","solid.taxes." + _customer.StateCode + "Tax");
                stateTaxObjects.Add(_customer.StateCode, tax);
            }
            else
                tax = stateTaxObjects[_customer.StateCode];
        }
        return tax;
    }
}

We have our tax factory creating tax objects and all the tax logic is in a separate class. So now all these ITax classes can go into another assembly just for tax stuff, Tax.dll. When anything changes this is the only assembly that needs to be tested and deployed and since it’s a supplemental assembly it won’t take too much red tape to do. 

Ok that’s it for now. See you next time.

License

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

Share

About the Author

Jon Woo
Software Developer eLeadCrm
United States United States
No Biography provided

Comments and Discussions

 
QuestionMy vote of 5 PinmemberMinThukyaw28-Jan-14 21:12 
GeneralMy vote of 5 Pinmemberstpraveen127-Jul-13 16:37 
GeneralMy vote of 5 PinmemberHumayun Kabir Mamun8-May-13 23:42 
GeneralMy vote of 5 PinmemberKlaus Luedenscheidt22-Apr-13 19:03 
QuestionSome details PinmemberPaulo Zemek22-Apr-13 6:14 
AnswerRe: Some details Pinmembersuriarau28-Apr-13 16:37 
GeneralRe: Some details PinmemberJon Woo29-Apr-13 10:10 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.2 | Last Updated 23 Apr 2013
Article Copyright 2013 by Jon Woo
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid