4

You who have worked with a framework implementing the MVC architectural pattern most likely know how these frameworks are usually implemented.

They contain a base Controller class, which you extend, where some part of the name of the controller indicates the begining of a route and methods, methods which you as a developer add to your newly created controller, represent the rest.

I.e. the route http://hostname.com/users/filter/name/John Wick requires a UsersController class (extending a main Controller) with a filter method accepting one parameter, the name.

By introducing new public methods, which the extended Controller classes public interface has no idea about, the frameworks break the LSP, which says:

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. (See also 2, 17 for other work in this area.)

Link to the official document by Barbara Liskov.

Where the T is the base Controller class and P is the user defined UserController. MVC frameworks must be implemented in terms of the base Controller, because the frameworks do not know what the routes and implementations will be, and based on the routes they perform some kind of a magical lookup to find the controller and method which suits the needs, otherwise redirects to an error page.

Update in response to not only but including enderland's answer

To make my intentions more clear, I am going to provide a code example, explaining my thought process.

Imagine two classes. For demonstration purpose I am going to use the common duck/toy duck example.

This concrete piece of code will be in C#, but I believe it is so simple you should not have problems with understanding it as long as you have some OOP background.

class Duck
{
    public string Name { get; protected set; }

    public Duck(string name)
    {
        this.Name = name;
    }

    public void Quack()
    {
        Console.WriteLine("Duck quacked.");
    }
}

class ToyDuck : Duck
{
    protected bool _batteriesAreCharged;

    public ToyDuck(string name, bool batteriesAreCharged = false)
        : base(name)
    {
        this._batteriesAreCharged = batteriesAreCharged;
    }

    public void ChargeBatteries()
    {
        this._batteriesAreCharged = true;
    }

    public void Quack()
    {
        if (this._batteriesAreCharged == true) {
            Console.WriteLine("Toy duck quacked.");
        }

        this._batteriesAreCharged = false;
    }
}

Citing Barbara Liskov and also enderland's answer:

The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype) plus something extra.

Pretty much covers what I have just done. I took a supertype, extended it and added new functionality altering the public API by adding new public methods. The intuitive approach to code reuse. But is it the correct one?

Be aware, I have not changed the pre nor post condition of the Quack method itself, only changed the implementation, thus that rule is not broken.

Now imagine you would have a method, perhaps a facade, which uses the ToyDuck class:

void makeToyDuckQuack(ToyDuck duck)
{
    duck.ChargeBatteries();
    duck.Quack();
}

Fairly straightforward, you pass a ToyDuck object to this method and it makes sure the ToyDuck will always quack, by calling the ChargeBatteries method before quacking.

Which brings us back to the first paragraph I cited by Ms. Liskov.

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. (See also 2, 17 for other work in this area.)

Based on this, programs should be written in terms of the supertype T, here the base Duck class. But the makeToyDuckQuack method I presented uses the subtype P instead. So to fullfil Ms. Liskov's terms, let's swap the tighter bond detertmined by the ToyDuck class to a more generic approach.

void makeDuckQuack(Duck duck)
{
    duck.ChargeBatteries(); // <-- EEeeeeh? What is this method?
    duck.Quack();
}

And the code will not compile, because the Duck class does not know the ChargeBatteries method exists. I just broke the code.

The constraint of using the supertype still remains, so to fix the code, I am forced to remove the call to ChargeBatteries method.

void makeDuckQuack(Duck duck)
{
    duck.Quack();
}

But now there's a problem. The ToyDuck will never ever quack more than once. No matter how how hard I try, it simply will not. So after one call to the Quack method on a ToyDuck object, I can throw the object away and create it again to make it quack once more.

So by extending a class and extending the public API, you are in fact introducing methods, which will never be called (unless some kind of a magical runtime lookup is performed), and why would you even introduce methods, which are useless? YAGNI is all against that.

How is this connected to the controllers?

As I said before, the MVC frameworks cannot know how you will extend the base Controller class, so the core of such frameworks must be programmed in terms of the base class.

There might be methods like:

void processRoute(Controller controller)
{
   // ...
}

But by using the base class, you would never have access to the new public methods of its children, by definition. Now, I know MVC frameworks do not care about this, because the methods are looked up during runtime based on the request, that's how MVC architecture works.


Because you are unable to know, what the routes are, because they are not defined up front, would this be considered a violation of the LSP?

I personally think it might be, because I believe an extending class should never introduce new public methods which the base class does not know about.

If it is a violation, there's another question. Is there a MVC framework which does not break it, perhaps by using composition over inheritance? I tried looking for some, but without luck.

To clarify:

I know violating the LSP is not completely wrong and LSP is merely a mean to make writing code easier when using IDE to help you with code completition and not having to downcast variables to access child methods.

The covers of MVC framework probably do not care about this at all, considering the route lookup is dynamic, as pointed out before.

  • 14
    I believe an extending class should never introduce methods which the base class does not know about. -- Isn't that kind of the whole purpose of extending a class... to introduce added functionality? – Robert Harvey Jan 28 '16 at 15:37
  • 7
    I recognize absolutely no way in which what you describe violates LSP... I think you should perhaps read around some more to get a better handle on LSP. Have a read here perhaps to understand what LSP aims to protect you from better. Controllers do not have any issues like this. – Jimmy Hoffa Jan 28 '16 at 15:40
  • 5
    @DavidPacker new methods have absolutely nothing to do with LSP. LSP is all about the designed behaviour of the base types methods - if you change those to misbehave in derived types then you lose the ability to transparently utilize different derived types as though they were the base type. Adding new methods to a derived type means nothing to the pre-conditions or post-conditions of the derived methods – Jimmy Hoffa Jan 28 '16 at 15:44
  • 4
    If you think there is some kind of bad design in the MVC pattern, describe exactly what you think it is and how / why it's bad in detail in your Q so we can all understand what you mean. Remove everything you said about LSP because that's an absolute red herring; LSP has nothing to do with what you seem to be describing. I think you're sensing a code smell which may or may not be there but it's not clear what it is you refer to. I'm certain however it's not LSP. – Jimmy Hoffa Jan 28 '16 at 15:48
  • 2
    @bug-a-lot: That's not true either. – Robert Harvey Jan 28 '16 at 15:50
11

Ignoring the philosophical question of "to violate or not" (that sounds bad...), you omitted the sentence immediately prior to your quote:

The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype) plus something extra.

This gives context to why the axiom you quoted was written.

Reading further, one of the next examples explains how this works:

The second example is abstract devices, which unify a number of different kinds of input and output devices. Particular devices might provide extra operations. In this case, abstract device operations would be those that all devices support, e.g., the end-of-file test, while subtype operations would be device specific.

If you imagine certain aspects of Controller being consistent throughout all the UserController objects, similar to the operations listed above, the Controller example fits perfectly with the examples given in her paper.

The section is concluded with:

An inheritance mechanism can be used to implement a subtype hierarchy. There would be a class to implement the supertype and another class to implement each subtype. The class implementing a subtype would declare the supertype's class as its superclass.

Which again is clearly articulating how inheritance works. It again is nearly exactly the example you are listing.


I should add, this is all assuming you don't actually violate LSP doing other things. There are plenty of ways you could customize your UserControllers to violate LSP.

  • I added code example, enderland, explaining my concerns in more detail and perhaps providing a more direct approach to the question itself. – Andy Jan 29 '16 at 8:22
2

By introducing new public methods, which the extended Controller classes public interface has no idea about, the frameworks break the LSP:

I don't think that new methods break the Liskov substitution principle. If you replace the parent with the child a new method won't change the behabiour if treated as a parent class. It just be a ignored method that cannot be called without a cast.

BaseController should be abstract. You cannot call the constructor of a baseController so you won't be able to substitute it for another controller. See that you may instantiate a UsersController and replace it with ImprovedUsersController that has a new method. In this case everything works great. You need to be able to replace a workable class with another version but BaseController is no workable on its own.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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