0

I've been having some trouble coming up with a design that would alleviate most, if not all, the issues I've been running into, and I'm wondering if it is my base design.

Our company accepts orders. We accept different types of orders based on the product line they belong to, but all orders share some functionality and properties. My goal is to inject any order implementing a specific interface into a OrderRepository class.

In order to fulfill these needs, I created an OrderBase class:

public class OrderBase {
    public string order_number {get; set;}
    public string order_type {get; set;}
    public string order_date {get; set;}
    public List<IOrderItem> order_items { get; set; }
    public virtual bool Validate();
}

I created an interface that mimics the base class so I can utilize the base class as exactly that... the base functionality for an order.

public interface IOrder {
    string order_number {get; set;}
    string order_type {get; set;}
    string order_date {get; set;}
    List<IOrderItem> order_items { get; set; }
    bool Validate();
}

One type of order is implemented as such:

public class TXOrder : OrderBase, IOrder {

    // Specific to TXOrder only
    public bool isOnHold {get; set; }

    public override bool Validate() {
        // Enter TXOrder-specific validations here.
        return base.Validate();
    }
}

TXOrder inherits the base class, getting all those properties and methods and the implemented interface is fulfilled by the base class properties and methods. This works well right now with ModelBinders that map to the correct object per the data coming in.

The repository looks like this:

interface IRepository<T, U>
        where U : IParameters
    {
        T SelectSingle(long id);
        List<T> Select(U parameters);
        T Insert(T entity);
        T Update(T entity);
        T Delete(T entity);
    }

public  class OrderRepository: IRepository<IOrder, OrderParameters> {}

If you need the gory details of the OrderRepository, don't hesitate to ask. It accepts any order I throw at it of type IOrder. That's the key point to take away.

Is this a pattern that can continue to work long-term as I add specific functionality for each order type? Is there another pattern I can look at?

5
  • Out of curiosity, am I following correctly when I think you're keeping the ability to manipulate their data on the Order objects? IE, no other class will know how to change/modify a member of IOrder?
    – Adam Wells
    Commented Sep 26, 2016 at 17:57
  • @AdamWells Not exactly true. When I send a payload to my MVC Controller, I have a model binder that interrogates the order type, creates the instance needed, and returns it. We can then manipulate the order as a specific type, not an interface type. Is that what you mean?
    – jlrolin
    Commented Sep 26, 2016 at 18:05
  • Sort of. I'm interested in what objects are going to work on these objects. IE, if I take an Order to say, an OrderProcessor or something, is he calling methods from the object itsself like TXOrder.CalculateCharges() or is the OrderProcessor going to directly change TXOrder.Charges itsself after calculations?
    – Adam Wells
    Commented Sep 26, 2016 at 20:15
  • @AdamWells I'm passing the IOrder into an OrderRepository that accepts the parameter as IOrder. So, I'm running certain operations on the order itself such as TXOrder.Validate and I have a method called TXOrder.Transform that holds specific logic. I can, however, manipulate the TXOrder itself if needed within the Repo.
    – jlrolin
    Commented Sep 28, 2016 at 1:53
  • @jlrolin, can you give an example how you will use order types, for example where you calling Validate method?
    – Fabio
    Commented Oct 8, 2016 at 2:47

2 Answers 2

1

I think you should stop using the base class and the order type and just go with interfaces. I would define interfaces that represent the various characteristics that orders can have, and implement the interfaces that make sense for each order type. In this way your logic is based on a set of properties and methods being available rather than an order type value.

For example if you defined interfaces like this

public interface IOrder
{
    Int OrderId { get; set; }
    DateTime OrderDate { get; set; }
}

public interface IValidatable
{
    void Validate();
}

public interface IDiscountable
{
    void Discount();
}

Then you can create different order types like this

public class OrderA: IOrder, IValidatable
{ ... etc ... }

public class OrderB: IOrder, IDiscountable
{ ... etc ... }

public class OrderC: IOrder
{ ... etc ... }

When you pass an IOrder into your order manager rather than having logic based on order type, you can base your logic on the whether the order implements a specific interface, for example

public void Process(IOrder order)
{
    var validatable = order as IValidatable;
    If (validatable != null)
        validatable.Validate();

    var discountable = order as IDiscountable;
    if (discountable != null)
        discountable.Discount();
}
0

I think you don't need interface at all.
If OrderBase class used only as container of basic functionality, then make it abstract

public abstract class OrderBase 
{
    public virtual string order_number {get; set;}
    public virtual string order_type {get; set;}
    public virtual string order_date {get; set;}
    public virtual List<IOrderItem> order_items { get; set; }
    public virtual bool Validate();
}

And derived class will inherit only abstract class without interface.

If you want keep interfaces and basic logic, then make base class implement interface

public class OrderBase : IOrder
{
    public string order_number {get; set;}
    public string order_type {get; set;}
    public string order_date {get; set;}
    public List<IOrderItem> order_items { get; set; }
    public virtual bool Validate();
}

Specific class will inherit only from base class

public class TXOrder : OrderBase
{
    public bool isOnHold {get; set; }

    public override bool Validate() 
    {
        // Specific validation
    }
}

But remember that when you pass different types of order to the "handler" method as basic class or interface, then you will not have access to the specific properties or methods.

You can use them only through method you override.

public override bool Validate() 
{
    if(isOnHold == true) return false;
}

This approach will work until you will need somehow to get access to the specific properties or methods by consumer of orders.

5
  • I agree that this approach will work. Ideally, I'd have an OrderBase and an interface that contains the same properties and methods. That way, I can inherit and implement both on a derived class, and the derived class can then access all the base properties and methods. This gives me ultimate flexibility in that I can override, but also just use the base implementation.
    – jlrolin
    Commented Oct 12, 2016 at 2:43
  • With virtual methods you have as much flexibility as if you implementing interface. Interfaces are "hack" which was used because classes cannot inherit from multiply parent classes.
    – Fabio
    Commented Oct 12, 2016 at 4:45
  • Having a base class and a matching interface strikes me as wrong. If the interface is JUST what's on the base class, then there's no need for it. If someone adds a new class that implements that interface and DOESN'T use the base class, and might surprise someone else working in the software that's up to now been assuming all IFoos are BaseFoos.
    – GHP
    Commented Nov 7, 2016 at 14:27
  • @Graham, it is not wrong. Interface is part of high level abstraction and base class is part of implementation. Application will use interface and will have dependency only on interface without knowing anything about implementation details(our base class). YOu cannot assume that IFoo is BaseFoos because in your main code you not even see(have reference) to the implementation project with BaseFoos
    – Fabio
    Commented Nov 7, 2016 at 17:37
  • @Fabio I guess to me its a violation of YAGNI to think you need to abstract your IFoo when as of right now, every implementation of IFoo IS actually a BaseFoo. We CAN make our applications completely decoupled in every way at every connection point, but I doubt that's always the best approach. Specifically for basic data structures, what you're talking about sounds like overkill.
    – GHP
    Commented Nov 7, 2016 at 19:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.