【问题标题】:Should I incorporate list of fees/discounts into an order class or have them be itemlines我应该将费用/折扣列表合并到订单类别中还是让它们成为项目行
【发布时间】:2009-04-28 17:13:38
【问题描述】:

我没有其他开发人员可以寻求建议或“你怎么看 - 我在想 这个”所以如果你有时间,请阅读并告诉我你的想法.

展示比描述更容易,但该应用本质上就像一个销售点应用,具有 3 个主要部分:商品、订单商品和订单。

项目类是来自数据存储区的数据。

public class Item 
    : IComparable<OrderItem>, IEquatable<OrderItem>
{
    public Int32 ID { get; set; }
    public String Description { get; set; }
    public decimal Cost { get; set; }

    public Item(Int32 id, String description, decimal cost) 
    { 
        ID = id; 
        Description = description; 
        Cost = cost;
    }
    // Extraneous Detail Omitted
}

订单商品类是订单上的商品行。

public class OrderItem 
    : Item, IBillableItem, IComparable<OrderItem>, IEquatable<OrderItem>
{
    // IBillableItem members
    public Boolean IsTaxed { get; set; } 
    public decimal ExtendedCost { get { return Cost * Quantity; } } 

    public Int32 Quantity { get; set; }

    public OrderItem (Item i, Int32 quantity) 
        : base(i.ID, i.Description, i.Cost) 
    {
        Quantity = quantity;

        IsTaxed = false;
    }
    // Extraneous Detail Omitted
}

目前,当您为订单添加费用或折扣时,它很简单:

Order order = new Order();
// Fee
order.Add(new OrderItem(new Item("Admin Fee", 20), 1));

// Discount
order.Add(new OrderItem(new Item("Today's Special", -5), 1));

我喜欢它,这是有道理的,Order 继承的基类会遍历列表中的项目,计算适当的税款,并允许其他 Order 类型的文档(其中有 2 个)从基类继承计算所有这些而不重新实现任何东西的类。如果一个订单类型的文档没有折扣,那就像不添加 -$ 值 OrderItem 一样简单。

我遇到的唯一问题是显示这些数据。进行此操作的表格有一个网格,其中应显示销售项目(即不是费用/折扣)。同样,某些费用和某些折扣也有文本框。我非常希望将这些 ui 元素数据绑定到此类中的字段,以便用户(和我)更轻松。

我的想法

有 2 个接口:IHasFees、IHasDiscounts 并让 Order 实现它们;两者都有一个 List 成员。这样,我就可以只访问 Sale 项目、只访问 Fees 和只访问 Discounts(如果需要,还可以将它们绑定到控件)。

我不喜欢它的地方: - 现在我有 3 种不同的类添加/删除方法(AddItem/AddFee/AddDiscount/Remove...) - 我正在复制(三重复制?)功能,因为它们都是相同类型项目的简单列表,只是每个列表具有不同的含义。

我在正确的道路上吗?我怀疑这对大多数人来说是一个已解决的问题(考虑到这种类型的软件很常见)。

【问题讨论】:

  • IHasFees 和 IHasDiscounts 听起来像 LOLCAT。

标签: c# oop


【解决方案1】:

我将向您指出 Rob Connery 在我不久前听的一个 ALT.net 播客上的评论(我不是 ALT.net 的拥护者,但推理似乎很合理):

什么对“商业用户”有意义(如果你身边有这样的人的话)。

作为一名程序员,您需要考虑项目、费用、折扣等因素,因为它们具有相似的属性和行为。

但是,就模型而言,它们可能是两个完全不同的概念。稍后有人会说“但这没有意义,它们是不同的东西,我需要单独报告它们,并且在这种情况下我需要将这个特定规则应用于折扣”。

DRY 并不意味着限制您的模型,在通过继承或类似方式考虑行为时,您应该注意这一点。

在这种情况下使用的具体示例是购物车。程序员的自然想法是使用未提交状态的订单。这是有道理的,因为它们看起来完全一样。 除了他们不是。这对客户来说毫无意义,因为它们是两个独立的概念,只会让设计变得不那么清晰。

这是一个实践、品味和意见的问题,所以不要盲目地听从网站上发布的建议 :)

对于您的具体问题,我使用的系统使用商品、费用、单品折扣(商品的属性)和订单的全球折扣(虽然它不是订单,但它是 POS 收据,但确实如此在那种情况下并不重要)。

我想原因是,在这些概念背后,物品是库存件的特定实例,它们影响库存数量,它们是可枚举和可量化的。

不收费。他们不共享大部分属性。

在您的情况下可能无关紧要,因为您的域似乎远不止于此,但您可能希望牢记这些问题。

【讨论】:

  • +1:我很难将折扣视为您订购的商品,我可以延长费用,但我们可以通过定义 ILineItem 来建模它,它可能是描述成本和数量,费用可以是订单中的订单项。我仍然会将它们与商品和折扣分开显示在订单上,即使它们是同一类型。
  • 在我使用的系统 (POS) 中,它们实际上不是因为费用不能有数量(它是否列在收据上)。我同意,出于计算目的,他们可以共享一个通用接口实现以用于聚合目的(总计/小计计算),可以说是一种行为特征。不过,我不会让它们成为同一类型(只是一种直觉)。同样,如果您没有完整的背景信息,也无法联系业务分析师,很难给出“正确答案”。
【解决方案2】:

实际上,我会仔细研究您的设计,并尝试找出行为的所在;然后将这些行为中的任何共性提取到一个不同的界面中,并确保它适用于您的设计。

机智;费用可能具有与之关联的相关验证行为。假设您向任何包含 20 件或更多商品的订单添加费用(只是一个随机示例,请与我一起在这个上运行)。现在,当您添加第 20 件商品时,您可能希望将该费用添加到订单中,但是有一个问题;当您从订单中删除商品时,您是否希望每次都检查是否需要从订单中删除该费用?我对此表示怀疑;这意味着存在与费用/折扣相关的行为,这基本上使它们成为完全不同的一类事物。

我会这样看;将费用和折扣分类为“特殊”事物,然后创建一个“ISpecial”接口,费用和折扣都从该接口继承。将任何常用功能提取到 ISpecial 接口(例如,“Validate”)。然后让您的 Order 实现 ISpecial(或其他)接口。

通过这种方式,您可以定义特定的 Fee.Validate() 行为和 Discount.Validate 行为,并借助多态的魔力(foreach of m_specialCollection .validate 这些)正常运行。通过这种方式,您也可以轻松地将 Special 接口扩展为任何其他可能需要的东西(例如,税收)。

【讨论】:

    【解决方案3】:

    我认为您在这里面临的问题的核心是您已将 OrderItem 实现为 Item 的子类,而现在您发现这并不总是合适的。

    根据您的描述,我将尝试执行以下操作:

    创建一个Order 类,该类为您希望向数据绑定公开的每个单值数据元素实现公共属性:订单号、日期、客户、总费用、总折扣等。听起来您可能是需要将特定费用/折扣显示为单个值;如果是这样,请为它们实现公共属性。

    创建一个抽象的OrderItem 类,该类为您希望在网格中绑定的每个数据元素以及您希望对其进行排序的每个数据元素实现公共属性。 (您也可以将其设为IOrderItem 接口;这实际上取决于是否有所有订单项通用的方法。)

    为可以出现在订单上的特定类型的订单项创建OrderItem 的子类(或实现IOrderItem 的类):ProductOrderItemFeeOrderItemDiscountOrderItem 等。

    ProductItem 的实现中,实现Item 类型的属性 - 它看起来像:

    public class ProductItem : OrderItem
    {
       public Item Item { get; set; }
       public string Description { get { return Item.Description; } }
       public int Quantity { get; set; }
       public decimal Amount { get { return Item.Price * Quantity; } }
    }
    

    Order 中实现IEnumerable&lt;OrderItem&gt; 类型的属性,用于存储所有订单项。实现一个AddItem 方法来添加OrderItems,例如:

    public void AddItem(OrderItem item)
    {
       _Items.Add(item); // note that backing field is a List<OrderItem>
    }
    

    你可以很简单地称呼它:

    Order o = new Order();
    o.AddItem(new ProductOrderItem { Item = GetItem(1), Quantity = 2 });
    o.AddItem(new FeeItem { Description = "Special Fee", Amount = 100 });
    o.AddItem(new DiscountItem { DiscountAmount = .05 });
    

    编写需要从该列表中提取值的那些单值字段的实现,例如:

    public decimal TotalFees
    {
       get
       {
          return (from OrderItem item in Items
                  where item is FeeItem
                  select item.Amount).Sum();
       }
    }
    

    如有必要,您可以稍后再回来优化这些属性(例如,一旦完成一次就保存计算)。

    请注意,您也可以将AddItem 限制为添加ProductItems,并使用Order 中的其他方法添加其他类型的项目。例如,如果一个订单只能有一个折扣金额:

    public void SetDiscountAmount(decimal discountAmount)
    {
       DiscountOrderItem item = _Items
          .Where(x => x is DiscountOrderItem)
          .SingleOrDefault();
       if (item == null)
       {
           item = new DiscountOrderItem();
           _Items.Add(item);
       }
       item.DiscountAmount = discountAmount;
    }
    

    如果您想在订单项目网格中的适当位置显示折扣金额,但又希望订单的折扣金额为单个值,则可以使用此方法。 (有争议的是,您可能想让DiscountAmount 成为Order 的属性,在其设置器中创建DiscountOrderItem,并让DiscountOrderItemAmount 获取其Amount。我认为这两种方法都有其优点和缺点。)

    【讨论】:

      【解决方案4】:

      一种选择是将 ItemType 属性添加到 OrderItem

      enum ItemType
      {
         Item,
         Fee,
         Discount
      }
      

      现在你可以在你的订单类中有:

      public IList<OrderItem> Fees
      {
         get
         {
            return _items.Find(i=>i.ItemType==ItemType.Fee);
         }
      }
      

      现在您仍然可以保留单个列表并避免使用额外的接口。您甚至可以使用 IList GetItems(ItemType type) 之类的方法。

      另一个想法是您当前的设计不允许 % 的折扣。今天你可以享受 10% 的折扣。这可能不是一项要求,但避免应用程序必须计算这一点的一种选择是将项目与折扣分开。

      如果我订购 10 件商品可享受 5% 的折扣,那么折扣甚至会成为更多规则。

      【讨论】:

      • +1 我喜欢这个想法,尤其是折扣成为规则的概念(因为使用它的销售人员之一经常做与此非常相似的事情)。
      • 是的,它作为规则要复杂得多,但随着这种复杂性,您可以做很多很酷的事情。特别是如果您可以在不重新编译的情况下注入新规则......
      猜你喜欢
      • 1970-01-01
      • 2017-09-21
      • 1970-01-01
      • 1970-01-01
      • 2016-01-22
      • 1970-01-01
      • 2018-03-31
      • 1970-01-01
      • 2018-05-04
      相关资源
      最近更新 更多