Dispatching on type vs polymorphism in C#. The business entities and non-business logic

149 Views Asked by At

I want to perform some operations (db persistence, json serialization, etc.) on an object, based on its type. Polymorphism is the usual way to do this, but I don't want to add a lot of non-business logic to my models.

Here is a simple type hierarchy (these are not my original types, but a simplification to show the problem):

public abstract class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
}

public class PrintBook : Book
{
    public decimal Weight { get; set; }
    public CoverType CoverType { get; set; }
}

public class Ebook : Book
{
    public EbookType Type { get; set; }
    public bool AutoUpgrade { get; set;}
}

These book are created on the client-side as JS objects and then, they are send to the server as a JSON (I cannot change its structure), to be parsed into a list of books:

public List<Book> ParseJson(string json)
{
    /* JSON sample (in JS we have duck typing - the type is determined by its members)
    "books": [
        { "id": 1, "title": "Sample print book", "weight": 50, "coverType": "soft" },
        { "id": 2, "title": "Sample ebook", "type": "pdf", "autoUpgrade": true }
    ]
    */
}

Then, I want to save this list to the database:

public void SaveBooksToDatabase(List<Book> books)
{
    foreach (var book in books)
    {
        if (book is Ebook)
        {
            Save((Ebook)book);
        }
        else if (book is PrintBook) 
        {
            Save((PrintBook)book);
        }
    }
}

public void Save(Ebook ebook) { /* */ }

public void Save(PrintBook printBook) { /* */ }

I'm not satisfied with the dispatching on type in the SaveBooksToDatabase method: it violates the Open-Closed Principle and is not very safe when we are adding new types.

But I do not want to add an abstract Save() method to the books hierarchy, because there are more type-dependent operations besides saving (e.g. json serialization) and this would violate the Single Responsibility Principle. I know that the Visitor pattern may be considered an alternative solution, but IMO it would introduce more messy, boilerplate code.

What is the best and the most elegant way to handle such cases?

1

There are 1 best solutions below

0
On BEST ANSWER

I would keep a separation between the objects sent from the client and the business objects which know how to save themselves.

I would have one class that can be deserialized from the JSON.

I would then have multiple business classes like Book, PrintBook, EBook, with the inheritance hierarchy. These would have polymorphic Save methods, among others.

I would use a tool like AutoMapper to map between the Data Transfer Objects and the business objects.