<译>C#使用设计模式和软件设计原则构建应用程序 PartI

我写这篇文章,主要是要演示一下如何利用SOLID原则和常用的设计模式从头开始构建一个应用程序。

让我们先从典型的电子商务类型开始。我们需要一个对象来表示订单,订单项,和客户。给定订单对象,有一个方法来计算总订单项的费用,这样做是编码实现的最简单也是最糟糕的方式么?当然是把税费的程序逻辑放在订单类里面

 

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 => item.Cost * item.Quantity); decimal tax; if (customer.StateCode == "TX") tax = total * 0.08m; else if (customer.StateCode == "FL") tax = total * 0.09m; else tax = 0.03m; 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; } }

 

这样做有什么问题?从学术角度上讲,它违反了SOLID编程规则中的第一个原则:单一原则。换句话说,订单对象只应该负责做和订单项管的事情,例如订单项的总成本。它不应该去管理基于不同州的常规税费。可以进一步说,他不应该负责统计订单项,他唯一的职责就是协调统计订单项的流程,增加税费和加运费或者做折扣。但是我们现在先让它保持简单一些。

好的,还有其他的问题么?代码又违反了另外一个SOLID原则:开闭原则。换句话说,只要一个类定义了,他就应该保持永远不变(它是关闭的).只能通过继承来扩展或者在运行时变化(对扩展开放).这看起来很奇怪,或者有点儿不切实际。但是在理想情况下,如果业务规则中的一切,从收集到设计,再到编码都能正确的完成,一旦它通过测试和批准生产,你将永远都不用再去改变类中的源代码。在这里我们看到事实并非如此。任何时候一个州的税费被添加或改变,订单类必须要改变。那怎么去做?让我们改一些代码,开始使用一种设计模式。

namespace Solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum(item => item.Cost * item.Quantity);

            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 * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

我们所做的一切就是创建了一个新类Tax,并把税费的程序逻辑放在这里。现在Order对象不再负责税费程序逻辑,Tax对象来处理它。

这是一个简单的策略模式的例子。简单的定义一下它,就是把所有的单独逻辑都放在它自己的类中。太好了,但是现在又有什么问题呢?我们又违反了另一个SOLID原则:依赖倒置。依赖倒置就是说类应该依赖于抽象,而不应该依赖于具体实现。在C#中,抽象通过一个接口或者一个抽象类来表示。在我们上面的代码中,我们通过 new Tax()的方式依赖了Tax类的具体实现。更不用说只要税费程序逻辑能够被添加或修改,Tax对象依然违反开闭原则,我们还没有做的很好,让我们继续重构。

namespace Solid
{
    public class Order
    {
        List<orderItem> _orderItem = new List<orderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItem.Sum(item => item.Cost * item.Quantity);
            ITax tax = new ITax();
            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 * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
}

我们现在做的就是创建一个tax接口的抽象实现,现在稍微好一点儿了,但是我们还是实例化了一个具体类。Order类不应该为此负责。它不应该关心它在于什么样儿的ITax的对象工作,只是表示带有一个CalculateTax方法的ITax的实例而已。根据上面的代码我们知道它创建了一个Tax实例,这是不好的,我们需要其他的一些东西来负责选择创建什么样儿的ITax对象。

namespace Solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum(item => item.Cost * item.Quantity);

            total += new TaxFactory().GetTaxObject().CalculateTax(customer, total);
            return total;
        }
    }
    public class Tax
    {
        public decimal CalculateTax(Customer customer, decimal total)
        {
            decimal tax;
            if (customer.StateCode == "TX")
                tax = total * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

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

            return tax;
        }
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

简单的定义一下,这个模式就是说,针对任何给定的场景都应该有一个类,这个类的唯一职责就是根据一些正在变化的条件创建其他的类。利用工厂模式我们创建了类来负责创建Tax对象。现在Order类是完全忽视我们是如何创建Tax对象的或者ITax是具体怎么实现的。它只关心有一个包含CalculateTax方法的ITax对象。这时也许你会好奇这样做的意义是什么,这只是用了更多的代码和一个TaxFactory来返回一种类型的Tax对象而已,不是很有用。
让我们引入一个新的问题:如果一个新的业务逻辑被引入,这个逻辑的意思是,如果一个County存在就用一个特殊的税费程序逻辑,否则就是用State的税费程序逻辑。现在需要改变我们的代码,让我们先用不好的方式来编码,我们也许会这样做:

namespace Solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum(item => item.Cost * item.Quantity);

            total += new TaxFactory().GetTaxObject().CalculateTax(customer, total);
            return total;
        }
    }
    public class Tax
    {
        public decimal CalculateTax(Customer customer, decimal total)
        {
            decimal tax;
            if(!string.IsNullOrEmpty(customer.Country))
            {
                if (customer.Country == "Travis")
                    tax = total * 0.085m;
                else if (customer.Country == "Hays")
                    tax = total * 0.095m;
                else
                    tax = 0.03m;
            }
            else
            {
                if (customer.StateCode == "TX")
                    tax = total * 0.08m;
                else if (customer.StateCode == "FL")
                    tax = total * 0.09m;
                else
                    tax = 0.03m;
            }
            

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

            return tax;
        }
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

我们放置了一些条件在Tax对象里来检查County属性,然而,我们破坏了更多的规则。我们改变了一个本不该被改变的类(开闭原则).现在Tax对象来处理决定是使用County还是State税费程序逻辑的职责(单一原则)。如果业务逻辑更新了我们该怎么做,不得不对这个类再次做出改变。这听起来像是我们需要添加另外一个类。

namespace Solid
{
    public class Order
    {
        List<OrderItem> _orderItems = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItems.Sum(item => item.Cost * item.Quantity);

            total += new 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 * 0.08m;
                else if (customer.StateCode == "FL")
                    tax = total * 0.09m;
                else
                    tax = 0.03m;

            return tax;
        }
    }
   public class TaxByCounty:ITax
   {
       public decimal CalculateTax(Customer customer, decimal total)
       {
           decimal tax;
           if (customer.County == "Travis")
               tax = total * 0.08m;
           else if (customer.County == "Hays")
               tax = total * 0.095m;
           else
               tax = 0.03m;

           return tax;
       }
   }
    public class TaxFactory
    {
        public ITax GetTaxObject()
        {
            return new Tax();
        }
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

这里我们添加了另外一个基于County的来处理税费程序处理的类。太好了,现在我们有另一个类来处理这一切,但是需要一个对象来处理我们是使用TaxCounty对象还是正常的Tax对象,谁来做这件事?TaxFactory看起来是一个不错的候选对象。Factory对象现在来决策和决定哪个类应该返回。但是等一等,现在我们必须要改变TaxFactory,因为为了做这个决策,它没有任何引用的Customer。现在让我们回过头来做一些我们应该做的事。

namespace Solid
{
    public class Order
    {
        List<OrderItem> _orderItem = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItem.Sum(item => item.Quantity * item.Cost);

            ITax tax = new TaxFactory().GetTaxObject();
            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 * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public class TaxByCounty:ITax
    {
        public decimal CalculateTax(Customer customer, decimal total)
        {
            decimal tax;
            if (customer.County == "Travis")
                tax = total * 0.08m;
            else if (customer.County == "Hays")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public interface ITaxFactory
    {
        ITax GetTaxObject();
    }
    public class TaxFactory:ITaxFactory
    {
        public ITax GetTaxObject()
        {
            return new Tax();
        }
    }
    public class CustomBasedTaxFactory:ITaxFactory
    {
        Customer _customer;
        public CustomBasedTaxFactory(Customer customer)
        {
            this._customer = customer;
        }
        public ITax GetTaxObject()
        {
            if (!string.IsNullOrEmpty(_customer.Country))
                return new TaxByCounty();
            else
                return new Tax();
        }
    }

    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

你可能已经注意到一个问题依然存在Order类当中,明白了么?是的,我们还是有一个引用TaxFactory类的实例化对象。让我们创建另外一个抽象工厂类,ITaxFactory。现在我们创建一个新的工厂,它的构造函数有一个Customer参数,CustomerBasedTaxFactory。现在该怎么办呢?我们希望使用CustomerBasedTaxFactory但是我们不被允许创建一个Order实例,因此我们该做点儿什么。这似乎是我们需要另外一个工厂提供工厂然后我们还需要一个工厂。我们越来越陷入无限循环,我们能做写什么?

namespace Solid
{
    public class Order
    {
        ITaxFactory _taxFactory;
        public Order(ITaxFactory taxFactory)
        {
            this._taxFactory = taxFactory;
        }
        List<OrderItem> _orderItem = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItem.Sum(item => item.Quantity * item.Cost);

            ITax tax = new TaxFactory().GetTaxObject();
            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 * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public class TaxByCounty:ITax
    {
        public decimal CalculateTax(Customer customer, decimal total)
        {
            decimal tax;
            if (customer.Country == "Travis")
                tax = total * 0.08m;
            else if (customer.Country == "Hays")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public interface ITaxFactory
    {
        ITax GetTaxObject();
    }
    public class TaxFactory:ITaxFactory
    {
        public ITax GetTaxObject()
        {
            return new Tax();
        }
    }
    public class CustomBasedTaxFactory:ITaxFactory
    {
        Customer _customer;
        public CustomBasedTaxFactory(Customer customer)
        {
            this._customer = customer;
        }
        public ITax GetTaxObject()
        {
            if (!string.IsNullOrEmpty(_customer.Country))
                return new TaxByCounty();
            else
                return new Tax();
        }
    }

    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string Country;
    }
}

依赖注入允许一个类来告诉消费者的类它需要什么来正常操作。在Order类的情况下需要一个Tax工厂来工作,或者一个ITaxFactory对象。DI能够通过三种方式来实现,我比较喜欢通过构造函数的方法,因为他显示的告诉消费者它需要的类。你可以看到在上面的代码中,我们添加了一个构造函数,该函数接收一个ITaxFactory类型参数,然后存储它的引用。在CalculateTotal方法中,它只是通过ITaxFactory的引用来调用GetTaxObject方法。它不知道它用的是什么TaxFactory或者用的是什么Tax对象。无知便是福!但是接下来谁来负责决定哪个TaxFactory使用呢?让我们用几种技术来实现,如下所示

public class Order
    {
        ITaxFactory _taxFactory;
        public Order(ITaxFactory taxFactory)
        {
            this._taxFactory = taxFactory;
        }
        public Order(Customer c):this(new CustomBasedTaxFactory(c))
        { 
        }
        public Order():this(new TaxFactory())
        {
        }
        List<OrderItem> _orderItem = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItem.Sum(item => item.Quantity * item.Cost);

            ITax tax = _taxFactory.GetTaxObject();
            total += tax.CalculateTax(customer, total);

            return total;
        }
    }

又是被称为"穷人的依赖注入"是使用一个构造函数来硬编码一个默认工厂来使用,如果没有指定的话。还有一个叫做IOC的容器会自动注入运行时所需的依赖。这在运行时可以基于一些条件例如配置等建立起来。因此让我们介绍另外一个业务逻辑:也许大多数天你都想用正常的税费逻辑,但是在特殊日期所有的在Texas的税费都被覆盖。

namespace Solid
{
    public class Order
    {
        ITaxFactory _taxFactory;
        public Order(ITaxFactory taxFactory)
        {
            this._taxFactory = taxFactory;
        }
        public Order(Customer c):this(new DateBasedTaxFactory(c, new CustomBasedTaxFactory(c)))
        { 
        }
        public Order():this(new TaxFactory())
        {
        }
        List<OrderItem> _orderItem = new List<OrderItem>();
        public decimal CalculateTotal(Customer customer)
        {
            decimal total = _orderItem.Sum(item => item.Quantity * item.Cost);

            ITax tax = _taxFactory.GetTaxObject();
            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 * 0.08m;
            else if (customer.StateCode == "FL")
                tax = total * 0.09m;
            else
                tax = 0.03m;

            return tax;
        }
    }
    public class TaxByCounty:ITax
    {
        public decimal CalculateTax(Customer customer, decimal total)
        {
            decimal tax;
            if (customer.County == "Travis")
                tax = total * 0.08m;
            else if (customer.County == "Hays")
                tax = total * 0.09m;
            else
                tax = 0.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 CustomBasedTaxFactory:ITaxFactory
    {
        Customer _customer;
        public CustomBasedTaxFactory(Customer customer)
        {
            this._customer = customer;
        }
        public ITax GetTaxObject()
        {
            if (!string.IsNullOrEmpty(_customer.Country))
                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();
        }
    }
    public class OrderItem
    {
        public int Quantity;
        public string Code;
        public decimal Cost;
    }
    public class Customer
    {
        public string StateCode;
        public string ZipCode;
        public string County;
    }
}

 在上面的代码中,你可以看到我们使用非空类型模式来创建一个新的名为NoTax的ITax对象,它总是返回0.我们也使用了装饰器模式来改变TaxFactory的行为。我们创建了一个名为DateBasedTaxFactory的新工厂,需要使用一个默认的ITaxFactory实例和一个Customer对象。DateBasedTaxFactory负责检查时间来决定是否使用NoTax对象。我们为Order的构造函数注入这个工厂。现在他将自动来决策是否使用NoTax对象或者让CustomerBasedTaxFactory做出使用什么的决策。

现在事情看起来好多了,所有的逻辑都被封装在它自己的类中。但是我们进一步思考,看看Tax对象,看出有什么问题么?每次你添加一个新状态,你都要添加更多的if语句,如果逻辑发生变化你必须要更新Tax类。你永远都不想去改变它,因此可以做点儿什么呢?

 

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

 

在上面的代码中你也许会想,哇,刚刚发生了什么。Tax对象哪里去了。所有的工作都在CustomerBasedTaxFactory中么?一些学校的编程思想是你应该尝试使用尽可能少的if语句写代码。这是因为与开闭原则关联了起来。在每次你添加一个新的税费程序逻辑的时候,你都需要修改Tax对象与另外一个if语句。这有可能在Tax类中有非常多的if语句。我们可以怎么做呢?更多的类呢?我们可以把每个State的税费程序逻辑都放在它自己的类中,并摆脱Tax对象。但是如果if语句都被放在了Factory类中,这样做不好。我们可以使用反射基于命名约定来动态获取正确的对象。CustomerBasedTaxFactory将来处理它。有一件事需要考虑,反射导致一些额外的开销,所以我们应该缓存我们创建的每一项,我们使用一个基于StateCode的key的static字典来完成。就这些了!我们也可以使用基于Taxes的County来做这件事。

原文地址:https://www.cnblogs.com/szkk/p/3915341.html