Do I have to include a list of fees / discounts in the order class or have them in points

I do not have other developers to ask for advice or “what do you think, I think about it”, please, if you have time, read and let me know what you think.

This is easier to show than to describe, but the application is essentially like an application for sale with three main parts: Items, OrderItems and Order.

An element class is data coming from a data store.

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 } 

The class of an order item is the line item in the order.

 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 } 

Currently, when you add fees or discounts to an order, it is simple:

 Order order = new Order(); // Fee order.Add(new OrderItem(new Item("Admin Fee", 20), 1)); // Discount order.Add(new OrderItem(new Item("Today Special", -5), 1)); 

I like it, it makes sense and the base class that Order inherits from iterations through the elements in the list, calculates the corresponding taxes and allows other documents of the order type (of which there are 2) to inherit from the base a class that calculates all this without re-introducing anything . If an order type document has no discounts, it is as simple as not adding the $ value OrderItem value.

The only problem I encountered is the display of this data. The form (s) in which this occurs has a grid in which sales items should be displayed (i.e. Not fees / discounts). Similarly, there are text boxes for certain fees and certain discounts. I would very much like to bind these ui elements to the fields of this class so that it becomes easier for the user (and me).

MY THOUGHT

They have 2 interfaces: IHasFees, IHasDiscounts and have the order of their implementation; both of which will have one member of the list. Thus, I could only access sales items, only rewards and only discounts (and bind them to controls, if necessary).

What I don't like about this: - Now I have 3 different add / remove methods for the class (AddItem / AddFee / AddDiscount / Remove ...) - I duplicate (triple?) Functionality, since all of them are just lists of the same and of the same type, just each list has a different meaning.

Am I on the right track? I suspect this is a problem for most people (given that this type of software is very common).

+6
c # oop
source share
4 answers

I will point you to a comment by Rob Connery on the ALT.net podcast that I listened to not so long ago (I am not a supporter of ALT.net, but the reasoning seemed audible):

Which makes sense for a "business user" (if you have any of them).

As a programmer, you will want to use Item, Fee, Discount, etc., because they have similar attributes and behavior.

BUT, they can be two completely separate concepts in terms of the model. And someone will come later, saying: "But it does not make sense, they are different things, I need to report it separately, and I need to apply this specific rule to discounts in this case."

DRY does not mean limiting your model, and you should keep this in sight when factoring behavior through inheritance or something like that.

The specific example that was used in this case was the shopping cart example. The programmer's natural idea was to use order in an unstated state. And that makes sense because they look exactly the same. Except that this is not so. This makes no sense to the client, because they are two separate concepts, and it just makes the design less clear.

This is a matter of practice, taste and opinion, so do not blindly follow the advice posted on the website :)

And for your specific problem, the system I'm working with uses items, fees, a discount discount (item property) and a global discount on an order (although this is not an order, it is a POS, but it does not really matter in this case).

I assume that the reason is that behind these concepts, Items are specific instances of inventory parts, they affect the quantity of stocks, they are listed and quantifiable.

Fees are not. They do not share most of the attributes.

In your case, this may not be the case, because your domain seems much more limited, but you may want to keep these problems.

+3
source share

In fact, I would look at your design in detail and try to figure out where the behavior is ; then extract any similarities in this behavior in a separate interface and make sure that this applies to your design.

For wit; Fees may be associated with valid actions associated with them. Let's say you add a fee to any Order that has 20 items or more (just a random example, run with me on this). Now that you add the 20th element, you can add this fee to the order, but there is a problem; when you remove an item from your order, do you want to check each time whether it is necessary to remove this board from your order? I doubt it; here it is understood that there is a duty / discount behavior, which essentially makes them a completely different class of things.

I would look at him that way; classify duties and discounts as “special” things, and then create an “ISpecial” interface from which both duties and discounts are inherited. Extract any common functionality for the ISpecial interface (for example, "Check"). Then your order implements the ISpecial interface (or any other).

Thus, you can define the specific behavior of Fee.Validate () and the behavior of Discount.Validate and work correctly thanks to the magic of polymorphism (foreach of m_specialCollection.validate the). This way, you can also easily extend the Special interface for anything else that might be required (e.g. Taxes).

+3
source share

I think that the essence of the problem you are facing is that you have implemented OrderItem as a subclass of Item , and now you find that this is really not always appropriate.

Given what you described, here is how I would try to implement this:

Create an Order class that implements public properties for each unique data item that you want to open for data binding: order number, date, customer, general fees, general discounts, etc. It seems like you need to display specific fees / discounts as separate values; if so, implement public properties for them.

Create an abstract OrderItem class that implements public properties for each data item that you want to bind in the grid, and for each data item for which you want to sort the items. (You can also do this with the IOrderItem interface, it really depends on whether there will be methods common to all order elements.)

Subclass OrderItem (or classes that implement IOrderItem ) for specific item types that may appear in order: ProductOrderItem , FeeOrderItem , DiscountOrderItem , etc.

In your implementation of ProductItem , implement a property of type Item - it will look something like this:

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

Add a property of type IEnumerable<OrderItem> inside the Order to store all positions. Implement the AddItem method to add OrderItems , for example:

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

which you can name quite simply:

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

Record implementations of those unique fields that should extract values ​​from this list, for example:

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

You can come back later and, if necessary, optimize these properties (for example, save calculations as soon as you have done this once).

Note that you can also limit AddItem adding ProductItem s and use other methods in Order to add other types of elements. For example, if an order can have only one discount:

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

You would use this approach if you would like to display the discount amount in the appropriate place in the grid of order elements, but also want the discount sum of the order to be the only value. (You might want to set the DiscountAmount property of Order , create a DiscountOrderItem in your setter, and DiscountOrderItem to get Amount from Order.DiscountAmount . I think both approaches have their pros and cons.)

+2
source share

One option is to add the ItemType attribute to OrderItem

 enum ItemType { Item, Fee, Discount } 

Now you can in your order class:

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

Now you can save your only list and avoid additional interfaces. You may even have a method like IList GetItems (type ItemType).

Another thought is that your current design does not allow% discount. Today you get a 10% discount. This may not be a requirement, but one option to avoid using the application to calculate this is to separate items from discounts.

The discount may even become more rules if I order that 10 items take 5%.

+1
source share

All Articles