2

I have a large class that used to hold different information for two different sets of the same class. For example, say a receipt that can be a customer receipt or an internal receipt. All this information is in one giant class because that's how the database is structured, but I want to separate the classes so that I would have a receipt class that holds all the shared information, a customer receipt class, and an internal receipt class. They could share an interface, but the implementations would be different, and that is what is confusing me.

Should there be two separate interfaces that get implemented? So I would have an ICustomerReceipt and IInternalReceipt? I thought there should be one interface with say a Get() and Save() method, and based on the implementation if it's a customer or internal receipt, I get the information specific to the class. I'm a bit lost here.

public class Receipt { 
    public int ID { get; set; } 
    public int ReceiptNumber { get; set; }
    public List<string> Items { get; set; }
}

public class CustomerReceipt : Receipt { 
    public string CustomerNumber { get; set; } 
    public string CustomerEmail { get; set; }
    public string CustomerOption { get; set; }
}

public class InternalReceipt : Receipt {
    public string InternalNumber { get; set; }
    public string InternalEmail { get; set; }
    public string InternalOption { get; set; }
}

public interface IReceiptRepository { 
    public Receipt Get(int id);
    public Receipt Add(Receipt receipt);
}

public CustomerReceiptRepository : IReceiptRepository {
    public CustomerReceipt Get(int id) {
        // get information about customer receipts here
    }
}

public InternalReceiptRepository: IReceiptRepository {
    public InternalReceipt Get(int id) {
        // get information about internal receipts here
    }
}

Basically I just want to return the correct Receipt to a view model in my controller that just has the generic 'ReceiptNumber' or 'ReceiptEmail'. I know it's not the best example, but it's the only one I could come up with.

2
  • What does the class do? Are all operations same except the data type? All filters applicable tot he base class? generics may help you there, either on interface or on the methods public interface IReceiptRepository<TReceipt> where TReceipt : Receipt, where your methods are like public TReceipt Get(int id) using explicit interface implementations. You would have to cast it though to get the correct interface. Alternatively, have one interface with generic methods: public TReceipt Get<TReceipt>(int id) : where TReceipt : Receipt. Commented Mar 29, 2019 at 12:50
  • If the logic is fundamentally different, use two repositories Commented Mar 29, 2019 at 12:51

3 Answers 3

1

Don't get tripped up on trying to force two similar things to share a single abstraction (base class or interface). So, I'd recommend what you suggested: create two separate interfaces.

Remember, the point of polymorphism is so you don't have to know what specific (derived/implemented) type an instance is if you're only looking for an instance of the base type/interface. That's it. If you don't need that, then jumping through hoops to force two similar classes to share a base is not worth it.

Sign up to request clarification or add additional context in comments.

2 Comments

since the methods are the same for each, would you suggest just having three interfaces? basically a base interface that has all the classes, and then two interfaces that just extend the base interface?
Yes. You should use interface inheritance as it makes sense. The idea is one of contexts. There is a context in which you just want to generically work with a "receipt" and you don't care what kind it ultimately is. So, you need an interface that covers just the base concept of a receipt. Then, you may want to specifically work with a customer receipt or an internal receipt. So those are two additional contexts.
0
public interface IReceiptRepository { 
    public Receipt Get(int id);
    public Receipt Add(Receipt receipt);
}

public CustomerReceiptRepository : IReceiptRepository {
    public Receipt Get(int id) {
        // get information about customer receipts here
        return new CustomerReceipt();
    }
}

public InternalReceiptRepository: IReceiptRepository {
    public Receipt Get(int id) {
        // get information about internal receipts here
        return new InternalReceipt();
    }
}

Comments

0

Inheritance can be represented on the database in different ways and there are some strategies depending on the ORM you are using. At the end of the day, using one of the strategies, you can base your repository on the base class and let the ORM act as a proxy to resolve the instance you need, or try to recreate yourself, at the level of the repository, based on a discriminator field, the instances you need

Receipt
    ID 
    ReceiptNumber 
    CustomerNumber 
    CustomerEmail 
    CustomerOption 
    InternalNumber 
    InternalEmail 
    InternalOption 
    DISCRIMINATOR_FIELD

(most of the ORM do this translation for you), but for you to get the idea, you can keep only one repository to treat all the classes as Receipt and keep your hierarchy as you have it.

public interface IReceiptRepository { public Receipt Get(int id); public Receipt Add(Receipt receipt); }

public CustomerReceiptRepository : IReceiptRepository {
    public Receipt Get(int id) {
            var rec = DbContext.Table.Receipt.FirstOrDefault(r => r.id = id);
            if(rec.DiscriminatorField == 1) //CustomerReceipt
            {
                return new CustomerReceipt
                {
                     ID = ...
                     ReceiptNumber = ...
                     CustomerNumber = ...
                     CustomerEmail = ...
                     CustomerOption = ...
                 }
              }
              //all other cases are InternalReceipts
              return new InternalReceipt
              {
                   ID = ...
                   ReceiptNumber = ...
                   InternalNumber  = ...
                   InternalEmail = ... 
                   InternalOption = ...
              }
    }
}

The same thing for the Add method, just fill only the fields you need for that object. This composition is basing everything on a discriminator field. I am not suggesting you implement your solution in that way, but with that, you still get on your ViewModel the generic receipt. My suggestion is that you read more about the ORM you are using an how you can represent inheritance there(maybe you are using database first instead of code first and you will need to handle the things manually, because the database was not designed on that way and you need to take a similar approach of what I suggested. But if you have the chance to create your POCO classes and create the database, definitely it deserves to take a look at how they implement the inheritance.

Here I am attaching a link of how this problem is addressed on EntityFramework 6

Inheritance Strategy in Entity Framework 6

Hope this helps

1 Comment

This is basically how it's done now except with an Enum. Sadly, the database schema was built around 20 years ago and lacks things like keys and indexers so the only reasonable way for me to query the data is through good old SqlClient. But doing it this way means that there are multiple places where there's a distinguish between which to use, and if somewhere down the road another type gets added it just makes it a mess to update.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.