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

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2013年4月17日

CPOL

5分钟阅读

viewsIcon

43178

我写这篇文章是为了演示如何从头开始使用 SOLID 原则和常见设计模式构建应用程序

介绍 

这是文章的第二部分,文章的起始部分在这里。你可能想先阅读它,否则你将不知道我在说什么 Smile | <img src=。我将继续使用相同的代码库,同时重构和演示更多编码原则。

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

更多重构、模式和接口 欢呼

下面是我们之前创建的订单类代码片段。你发现它有什么奇怪的地方吗?

public class Order
{
    ITaxFactory _taxFactory;
    

    public Order(Customer c)
        : this(new DateBasedTaxFactory(c, new CustomerBasedTaxFactory(c)))
    {
    }
    public Order(ITaxFactory taxFactory)
    {
        _taxFactory = taxFactory;
    }
    public 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;
        }
    }
}

也许你注意到 CalculateTotal 方法有些不对劲。我们有两个独立的 Customer 对象引用,一个在构造函数中,另一个作为 CalculateTotal 的参数。除了它很笨拙之外,Order 可能还在使用两个不同的客户实例,这会导致一些不稳定的行为。再看看 TXTax 类中的代码。发现什么不对劲了吗?CalculateTax 方法需要一个客户参数,但它并没有使用它。我可能会声称这违反了接口隔离原则。这基本上说,类不应该被迫实现它们不需要的接口中的一堆东西。如果它们不使用它,那么它就不应该在那里。这通常适用于接口方法,但也许也适用于参数。你怎么看?无论如何,如果你从逻辑上思考,一个不需要客户的 Tax 对象是完全合理的,例如我们的 NoTax 类。所以这不应该是每个 ITax 实现都必需的。

所以,解释一下,在代码的第一个版本中,我们需要客户来直接在这个方法中实现税收逻辑,用于一个特定情况(基于州)。嗯,我们现在不需要这个了,因为税收对象本身会处理逻辑。所以让我们进行一些重大的代码更改来适应这一点。这看起来可能很多,但请记住,我们仍然是为了学习而设计和编码,而不是更改生产代码

public class Order
{
    ITaxFactory _taxFactory;

    public Order(Customer c)
        : this( new DateBasedTaxFactory(c, new CustomerBasedTaxFactory(c)))
    {
    }
    public Order(ITaxFactory taxFactory)
    {
        _taxFactory = taxFactory;
    }
    public List<OrderItem> _orderItems = new List<OrderItem>();
    public decimal CalculateTotal()
    {
        decimal total = _orderItems.Sum((item) =>
        {
            return item.Cost * item.Quantity;
        });

        total = total + _taxFactory.GetTaxObject().CalculateTax( total);
        return total;
    }

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

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

好的,完成了,我们继续做别的事情。让我添加一些代码来非常简单地演示实际使用 Order 对象。通常在电子商务中,我们有一个 Basket 的概念,所以我们将使用它并坚持我们在文章 I 中学到的关于面向抽象和工厂等的知识。

public interface IOrderFactory
{
    Order GetOrder(int orderId);
}
public class OrderFactory : IOrderFactory
{
    public Order GetOrder(int orderId)
    {
        Order order = null;//get from repository or wherever
        return order;
    }
}
public class Basket
{
    Order_order;
    public Basket(int sessionId,IOrderFactory orderFactory)
    {
        int orderId = 3333;//Session[sessionId].OrderId;
        _order = orderFactory.GetOrder(orderId);
    }
    public Basket(int sessionId):this(sessionId,new OrderFactory())
    {
    }
} 

太棒了,现在我们拥有了 UI 获取 basket 及其订单所需的一切。

如果我说我们应该创建一个名为 IOrder 的接口,它看起来像这样

public interface IOrder
{
    decimal CalculateTotal();
    int NumberOfItems();
} 

然后我们让 Order 实现它。你可能会想,这有什么意义呢?我们已经有了一个具体的类 Order,除了 THE Order 之外,不会有其他任何东西,为什么我们要为它设置一个接口。好吧,让我们想象一下,我们完成了所有类的编码,测试并通过了,然后将其移至生产环境。但随后(惊喜!)并非所有的业务需求都已提供,我们忘记了一些东西。我们现在需要将运费计算添加到 CalculateTotal 订单中。也许是这样的

public decimal CalculateTotal()
{
    decimal shippingCost = 1.3m * _orderItems.Count;

    decimal total = _orderItems.Sum((item) =>
    {
        return item.Cost * item.Quantity;
    });

    total = shippingCost + total + _taxFactory.GetTaxObject().CalculateTax(total);
    return total;
}

太好了,现在我们必须进入 order 类并更改它,但我们不应该这样做!开闭原则。有什么办法可以解决?我们可以使用一种旨在扩展类行为而不必实际更改它的模式,即装饰器。简单来说,装饰器对象接受并维护对实现相同抽象的其他对象的引用,在本例中是 IOrder 接口。然后它们相互使用,在彼此之上添加功能。令人困惑?让我们看看它是如何工作的。我不想更改 order 类,但我们一开始就应该有 IOrder。所以,让我们假装它从一开始就一直存在。

public class Order:IOrder
{
}

现在我们需要新的功能,当需要新功能时,你总是做什么?当然是创建一个类。所以,让我们创建一个处理运费的类,如下所示

public class ShippingOrder : IOrder
{
    IOrder _order;
    public ShippingOrder(IOrder order)
    {
        _order = order;
    }

    public decimal CalculateTotal()
    {
        decimal shippingCost = 1.3m * _order.NumberOfItems();
        return shippingCost + _order.CalculateTotal();
    }
    public int NumberOfItems()
    {
        return _order.NumberOfItems();
    }
}  

那么,这有什么意义呢?嗯,你有你的默认 Order 类,就像它目前存在的那样,然后你可以将它传递给一个 ShippingOrder 对象,它将“包装”它。它会处理计算运费本身,然后将其添加到普通 Order 对象的 CalculateTotal 值中。然后该工厂可以返回一个 ShippingOrder 而不是一个普通的 Order,消费者将永远不会知道区别。所以让我们把它改成它一开始应该有的样子。

public interface IOrderFactory
{
    IOrder GetOrder(int orderId);
}
public class OrderFactory : IOrderFactory
{
    public IOrder GetOrder(int orderId)
    {
        Order order = null;//get from repository database or wherever
        return new ShippingOrder(order);
    }
}
public class Basket
{
    IOrder _order;
    public Basket(int sessionId,IOrderFactory orderFactory)
    {
        int orderId = 3333;//Session[sessionId].OrderId; whatever you are using to maintain state
        orderFactory.GetOrder(orderId);
    }
    public Basket(int sessionId):this(sessionId,new OrderFactory())
    {
    }
       
}

那么我们这里有什么?Basket 对象正在使用 OrderFactory 来获取对 IOrder 的引用。它不知道该变量是持有对 Order 还是 ShippingOrder 的引用,它也不应该知道。这正是我们想要的。OrderFactory 是唯一了解发生了什么事情的对象。它只是像平常一样从数据库中获取一个 Order 对象,然后将其放入一个 ShippingOrder 对象中,以便添加运费功能,而 Order 对象从未改变。

现在一切都已就位,让我们再看一遍。高层管理者现在希望我们接受优惠券和折扣码。太好了,这将是一个简单的更改,因为我们已经为成功做好了准备。

public class DiscountOrder : IOrder
{
    IOrder _order;
    public DiscountOrder(IOrder order)
    {
        _order = order;
    }

    public decimal CalculateTotal()
    {
        return  _order.CalculateTotal() - 4;
    }
    public int NumberOfItems()
    {
        return _order.NumberOfItems();
    }
}

所以我们只需要创建一个 DiscountOrder 类,除了工厂类之外,什么都不用改变:

public class OrderFactory : IOrderFactory
{
    public IOrder GetOrder(int orderId)
    {
        Order order = null;//get from repository or wherever
        return new ShippingOrder(new DiscountOrder(order));
    }
} 

是的,我知道我遗漏了很多东西。Basket 对象甚至没有使用 CalculateTotal 方法,如果它调用它,由于 tax factory 为 null,将会出现运行时异常,但你的意思你应该明白了。

好的,现在就到这里。希望这有道理并为大家增加了一些价值。

© . All rights reserved.