65.9K
CodeProject 正在变化。 阅读更多。
Home

使用设计模式和原则构建 C# 应用程序 - 第三部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (14投票s)

2013 年 4 月 22 日

CPOL

3分钟阅读

viewsIcon

33712

本文将演示编码原则的意义

引言

这是我的系列文章的第三部分。你可能需要先阅读之前的文章,否则你将不知道我在说什么。在本文中,我想稍微解释一下 SOLID 编码原则的意义。所以每个人都说让你的代码 SOLID 化。那么,为什么呢?

  1. 第一部分 
  2. 第二部分 
  3. 第三部分  

依赖倒置 

这个原则的意义是什么?为什么你不能针对类的硬编码具体实例进行编码?让我们专注于编码时的一个重要的事情,测试。希望你了解单元测试及其重要性。也许你熟悉测试驱动开发,即你在任何编码之前先设计你的测试。你将编写测试以定义你试图完成的新功能,然后开始编码直到测试通过。 让我们看看之前文章中的这段代码。

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

我们有DateBasedTaxFactory。我们应该测试这个工厂是否正常工作。 如果在任何年份的 4 月 4 日,返回的税值应为 0。我们可以创建一个像这样的测试: 

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

这里有什么问题?我们无法真正测试它!正如你在DateBasedTaxFactory中看到的,它直接使用DateTime对象的Now属性来测试今天是哪一天。除非你更改系统时间,否则我们无法使免税情况发生。更改系统时间并不理想。我们还能做什么? 这个工厂类有所谓的隐藏依赖。它被硬编码为依赖于需要改变的东西。工厂类需要一个DateTime对象,它不需要DateTime是当前日期。它实际上 不在乎 给它的日期是什么。我们需要在这里使用依赖注入,以便告诉外界该类需要什么才能工作。这将允许我们的测试提供任何必要的日期来进行测试。如下所示: 

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

现在我们可以调整我们的测试以发送我们想要的任何日期来进行测试。 

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

单一职责 / 开闭原则

为什么你的对象只应该做一件事,为什么你不应该改变它们?现实生活中的事物会发生变化,为什么你的代码不能代表这种生活变化?让我们看看之前文章中的这段代码,这是 Order 类的第一个版本。假设你的公司有一项坚定不移的政策,即你系统中的主程序集 BusinessLogic.dll 每 2 个月才发布一次。如果在之前出现错误或需要更改,则需要进行大量的繁文缛节。但是可以定义可以根据需要发布而无需太多麻烦的补充程序集。如果我们使用原始代码

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

并且 TX 的税收逻辑发生了变化,或者需要新的州税,我们将不得不修改 Order 对象。这将引起轩然大波,因为我们现在需要测试和发布BusinesLogic.dll。并且由于它与税收、法律和金钱有关,因此很有可能会发生很多变化,并且需要尽快投入生产。因此,从其他文章中,我们已经完成了我们需要做的事情,如下所示: 

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

我们的税务工厂正在创建税务对象,所有税务逻辑都在一个单独的类中。所以现在所有这些 ITax 类都可以进入另一个仅用于税务的程序集,Tax.dll。 当任何事情发生变化时,这是唯一需要测试和部署的程序集,并且由于它是一个补充程序集,因此不需要太多的繁文缛节。 

好了,今天就到这里。下次再见。

© . All rights reserved.