Re: LSP and subtype



Responding to Johansson...

Assume we have a class named Base with a method called foo.

We have a subklass to this Base called Sub also with a method called foo.

I assume that Base.foo() is a concrete implementation that Sub.foo() overrides...



Now to my question:
I'm I right if I say that if the client can use this foo from Sub insted of foo from Base for example by using polymorfism and the client doesn't see any difference then Sub is a subtype of Base.

That is correct. There is only one instance of the root superclass in hand and inheritance resolves the properties for that instance. If the object in hand is a member of Sub, then inheritance provides the override for foo() in all access contexts.



What requirement is on the client program when I say client doesn't see any difference.
Does this actually mean that the client program must be identical when calling foo in Base and when calling foo in Sub.

It doesn't matter to the client if Base.foo() or Sub.foo() is invoked; the client always gets the Sub.foo() because of the override.


However, there are some potential pitfalls here. For example, some poorly formed OOPLs like C++ allow one to instantiate a superclass without specifying a subclass. This creates an inherently ambiguous situation for resolving the properties and the language will have to provide additional rules, such as one always gets Base.foo().

That can create problems for the client who might be more particular about what actual implementation is provided. For example:

                  [Base]
                  + foo()
                    A
                    |
          +---------+--------+
          |                  |
        [Sub1]            [Sub2]
        + foo()           + foo()

where [Sub1] and [Sub2] provide different overrides. Now suppose the Client is indifferent to [Sub1] vs. [Sub2] but does not want to invoke foo() if the instance in hand is just a Base (i.e., no subclass specified). There is no way to express that LSP constraint. Perhaps more important, the diagram provides no clue that such a LSP violation can even happen, which is an invitation to maintenace disasters.

That segues to the notion that the client has to understand _the whole tree_ to access it properly. For example, suppose we can't instantiate Base on a standalone basis and we have:

                  [Base]
                  + foo()
                    A
                    |
          +---------+--------+
          |                  |
        [Sub1]            [Sub2]
        + foo()

where [Sub2] gets the Base.foo() implementation. Suppose a Client invoking Base.foo() is happy with either implementation so all is well.

Now suppose somebody comes along doing maintenance and decides we need a third implementation:

                  [Base]
                  + foo()
                    A
                    |
          +---------+--------+--------------+
          |                  |              |
        [Sub1]            [Sub2]          [Sub3]
        + foo()                           + foo()

This may break the original Client if the Sub3.Foo() implementation is unacceptable (i.e., an LSP violation from the Client perspective) even though the original Client and its context was not touched. To fix this problem we would need to do further surgery on the tree:

                  [Base]
                  + foo()
                    A
                    |
          +---------+--------+
          |                  |
        [SubA]            [Sub3]
          A               + foo()
          |
    +-----+------+
    |            |
  [Sub1]       [Sub2]
  + foo()      + foo()

Not only do we have to perform more surgery on the tree, we have to change the calling context in Client to invoke SubA.foo() rather than Base.foo(). Thus the Client has to understand the tree to invoke the correct property implementations

Bottom line: overriding implementations in subclassing was just one of those things that seemed like a good idea at the time but turned out to be a real Bad Idea.


************* There is nothing wrong with me that could not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions  -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
(888)OOA-PATH



.



Relevant Pages

  • Re: About kontract pre and post condition
    ... either in 'foo' or in the client. ... behavior responsibility and that is none of the client's business. ...
    (comp.object)
  • Re: Synchronized objects
    ... > proxies to remote objects and the appropriate one to use depends on how ... > plan to use the object in your clients and on your server. ... > 1) Does every client that connects need to access the same Foo instance ...
    (microsoft.public.dotnet.framework.remoting)
  • LSP and subtype
    ... We have a subklass to this Base called Sub also with a method called foo. ... foo from Base for example by using polymorfism and the client doesn't see ... calling foo in Base and when calling foo in Sub. ...
    (comp.object)
  • How does tcpclient.getstream know when a serialized objects ends?
    ... The code in the end of this mail is a snippet from a application im ... A tcp client serializes 10 FOO objects and sends them to the server ... FOO obj = formatter.Deserialize; ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: A roguelike programming contest
    ... a client to directly include headers needed only indirectly ... Bar _bar; ... Your proscription would force a module using Foo to write: ... Forward declarations /where possible/ help reduce compile ...
    (rec.games.roguelike.development)