12 Comments

  1. meze
    December 16, 2012 @ 6:23 am

    If you read about LSP it says ” objects of type S may be substituted for objects of type T”. Constuctors aren’t a part of an object’s API. They are “factories” for objects.
    For example, if you have a method public function setApi(TwitterApi $api), you can substitute $api with Cache API that extends the original class. setApi method doesn’t care at all (unless you do something really silly like $api->__construct()) what constructor signature is, so it doesn’t violate LSP and your agument becomes invalid…

    Reply

    • Jacob Mather
      December 17, 2012 @ 7:41 am

      You are correct with this specific instance — I have added another example where the reasoning is a little more straight forward.

      However even with the poor prior example, if you do this via inheritance instead of via composition, the cache then technically becomes a fundamental piece of that object hierarchy, which it actually has zero relation to, and I feel an implementation built on inheritance would still violate the general separation of concerns you want to enforce to keep code simple and easy to maintain further down the line.

      Thank you so much for the feedback!

      Reply

  2. cordoval
    December 16, 2012 @ 3:59 pm

    would be good to know a real case of this problem

    Reply

    • Jacob Mather
      December 17, 2012 @ 7:41 am

      I hope my new example helps expand on a real-world scenario.

      Reply

  3. Dan Trenz
    December 17, 2012 @ 8:13 am

    Great post.

    I must admit I’m not completely clear on why “implements” > “extends”.

    How is your example – “MyEmailingLoggerProxy implements Logger” – better than simply extending the base class (i.e. “MyEmailingLoggerProxy extends MyBasicLogger”)?

    Forgive me, I’m not well-versed in OOP design patterns, so I’m trying to understand the concrete benefits of using interfaces.

    Reply

    • Jacob Mather
      December 17, 2012 @ 9:46 am

      Dan,

      I totally get where you’re coming from. Let me try and re-explain it within the example I gave.

      The question you’re really asking is called composition over inheritance. The basic idea is when we extend, we want to keep within a subset of the main object.

      As email is about notification, and not logging, it doesn’t make sense to extend our emailing facility from one of our base logging objects.

      The other artifact, is that if we do extend MyEmailingBasicLogger from MyBasicLogger, we would then also have to create MyEmailingMonologLogger which has the exact duplicate functionality.

      Extracting the emailing logic away form the My*Logger object hierarchy reduces complexity in the over-all system design, and increases flexibility.

      Say tomorrow you decide you not only want to email on critical failure, but automatically open a bug in your issue tracker, you could then make MyIssueTrackerLoggerProxy in the same vein as MyEmailingLoggerProxy. Then you would do something like:

      $logger = new MyIssueTrackerLoggerProxy(new MyEmailingLoggerProxy(MyBasicLogger()));

      Any system you have that relies on a Logger interfaced object can continue to use $logger the same way it always has, and you can dynamically add new functionality on top of it without changing any expectations.

      Is that a little clearer? Or did I just muddy the water more?

      Reply

  4. designbymobius
    December 17, 2012 @ 8:26 am

    Good read. Basically a way to keep your system structure decoupled.
    Need to try a new logger? Simple!

    1. Implement original logger in a new class
    2. New class uses its process and method and wraps them in logger’s original endpoints
    3. Profit!

    I know this is a PHP blog but it would be awesome to see something like this for javascript …

    Reply

    • Jacob Mather
      December 17, 2012 @ 9:51 am

      Right.

      This isn’t necessarily a PHP blog, it’s just a blog on stuff I work on (and things that interest me), which does also include JavaScript.

      You can do all of this in JavaScript, though there are no actual contractual constraints on it. I believe this is part of the reason some of the JavaScript community is pushing CoffeeScript, as it adds a compilation step where such constraints can be tested against, giving JavaScript a little more rigidity.

      Reply

      • designbymobius
        December 17, 2012 @ 12:22 pm

        I managed something like this in a way I’m not proud of but gets the job done. I enclosed separate systems into objects and communicated between them with predefined endpoints. Something like this:

        var playerEngine = {};

        (function(x){

        // change currently playing track
        x.changeTracks = function(trackMetadata) {
        // action here
        renderEngine.renderNewTrack(trackMetadata);
        }

        }(x))(playerEngine);

        var renderEngine = {};

        (function(x){

        // update UI with current playing track
        x.renderNewTrack = function(trackMetadata) {
        // action here
        }

        }(x))(renderEngine);

        What this allowed me to do was separate the UI from the actual internals, so when I needed to change UI in a different version of the app, all I did was change renderEngine. Same endpoint to the main app. Solution has so many problems with it from an architectural standpoint, but it currently gets the job done so it’s what I still use.

        Searching around for better ways of doing it though. Thanks for the reply, JM

        Reply

        • Jacob Mather
          December 17, 2012 @ 12:30 pm

          Sometimes working trumps elegant. Especially with JavaScript. 🙂

          Reply

    • Jon
      December 17, 2012 @ 10:29 pm

      I think that explanation is finally resonating with me. I’m also struggling with the need for interfaces but I agree with both the points you make here.

      Reply

      • Jacob Mather
        December 17, 2012 @ 11:07 pm

        Heh, what comes to mind with that is a great quote from Canopy (the parent company of SCO — who went suing people willy-nilly ~10 years ago over linux stuff?):

        “Contracts are what you use against people you have relationships with.”

        Interfaces are contracts you make with your code. They let you guarantee a specific set of functionality.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *