oopdesign-principlesliskov-substitution-principle

How does the Liskov Substitution Principle works with type specialization in concrete implementations of abstract classes?


Suppose we want to model different kinds of food; pizzas and pies. Both pizzas and pies do have a sort of topping, but their toppings are different.

We implement an abstract class Baked, and two concrete classes Pizza and Pie. We also implement an abstract class Topping, and two concrete classes PizzaTopping and PieTopping.

By definition, any concrete implementation of Baked must have a Topping, so Baked should have an attribute of type Topping. Also, this specific topping attribute should be of type PizzaTopping in Pizza, and PieTopping in Pie.

If I understand this previous answer correctly, the LSP is violated in this situation, because if we set aside the fact that Baked is abstract, it could be possible to instantiate a Baked holding a PieTopping, but that is not possible if we substitute Baked with Pizza.

My question is; is there a way to implement this model without violating the LSP?


Solution

  • I'll address the immediate concern first, but subsequently, I'll have something to say about the overall assumptions implied by the question.

    Using C#-like pseudocode, you could define Pizza like this:

    public class Pizza : Baked
    {
        private readonly PizzaTopping topping;
    
        public Pizza(PizzaTopping topping) : this(topping)
        {
            // Consider saving `topping` to a private or protected class field
            // so that you don't have to downcast any `topping` field from the
            // base class:
            this.topping = topping;
        }
    }
    

    Then do the same for the Pie class.

    This may now prompt the natural question: Why even have an abstract Topping class?

    Indeed, based on the information in the OP, we don't have enough information to answer such a question. Does Topping define some polymorphic behaviour that derived classes can implement? If so, what is it? And does any of this even relate to the Liskov Substitution Principle (LSP)?

    It's unfortunate that universities, bootcamps, and online courses alike keep insisting on teaching object-oriented design from the notion that it's about modelling concrete objects in the real world. That may be how Simula started out, but rarely turns out to be the best way to structure code in languages like Java or C#.

    The LSP isn't about structure, but about behaviour. The modern understanding of the LSP is actually captured quite well by the Wikipedia entry, which discusses that

    All this (pre-, postconditions, and invariants) are, in the sense of Bertrand Meyer's view of OOD, part of a type's contract. Only the designer of a type can say what the contract is, and since the OP doesn't state that, we can't really answer whether or not a given design violates the LSP.