Re: Question on LSP



Responding to Kazakov...

I don't care about "is-a", I can access the target object, that's all. I
don't care about how this happens. That is an implementation detail.

But this is comp.object where subclassing and subtyping are used (and only used!) to express is-a relationships.


But is-a in the problem space is not is-a in the programming language. It
is very dangerous to mix them. I can model problem space's is-a by many
ways, and many of them will not be is-a. In my view, subtyping expresses
nothing. It is a relation which can be evaluated to true or false. Same as
an integer number, which also expresses nothing. We can use either to model
something that in our opinion has a similar structure / behavior / shape /
properties. I stop, where you begin.

I don't how to avoid "mixing" them. OOA/D abstracts from various problem spaces. The role of OOPLs is to provide an implementation for the OOA/D design that is closer to the hardware computational models. To preserve correctness, I think that implementation must preserve the OOA/D semantics.

It seems to me that failing to preserve is-a semantics in the type systems would be much worse than choosing OOPL names like X.Y() for OOA/D abstractions like Account.withdrawal().

This just underscores the nature of our disagreement. I don't care about linguistics and 3GL grammar design. So to me one cannot talk about subclassing or subtyping or LSP without an is-a context. That is exactly why I cannot see ClassY* being related to ClassY through any sort of subtyping (outside the language design meta model).


Yes, you are trying to bring a problem space, while I am trying to abstract
all them away.

Right. So I don't see us getting anywhere.

But I can't help pointing out that nobody had much interest in type theory in general and LSP in particular until OOA/D became prominent. When the only abstractions one needed to play with were procedures, one could make do with less sophisticated graph and set theory for 3GL language design. B-) So I find it hard to believe that the type mavens weren't strongly influenced by OOA/D abstraction semantics like is-a.

Calling methods through pointers is subtyping?!? Sorry, that notion is so alien to me that I don't even know where to push back. B-)

It looks like substitution, it works like substitution... (:-))

A pointer always gives one exactly the same thing every time it is invoked so there is no substitution.

You mean pointer-specific operations here. But I mean that a substitution
happens when you pass pointer where an object of the target type is
expected.

I see this as a quite different issue that has nothing to do with pointers per se. There are three aspects of OO relationships that one has to deal with during OOA/D/P: implementation, instantiation, and navigation.

Pointers are <one of several> implementation mechanisms for relationships at the OOP level. That implementation is fixed regardless of the dynamics of collaboration. Passing the pointer as a method argument is <one of several> mechanisms for navigating OOA/D relationships at OOP time.

Your substitutability, OTOH, is an issue for instantiation. If the pointer, whether passed or as an embedded attribute, is never changed after the initial instantiation, then there is no substitution; the receiver always sees exactly the same object with exactly the same behaviors. If the pointer is instantiated differently each time it is passed (or an embedded pointer attribute is reset) before each navigation, then one has your substitutability because the receiver now interacts with a different object with different behaviors during each collaboration.


You consider here individual objects. But types and their relations aren't
about that. "Pointer does not change" is misspelled. "Pointer is
changeable" were correct. Types are talked of in conjunctive mood, so to
speak. There is no objects so far, only values.

Mutability of value is not polymorphic substitution. LSP is only an issue for polymorphic dispatch. In fact, of all the forms of polymorphism available to OO development, LSP is only relevant to inclusion polymorphism. And inclusion polymorphism does not exist without and is-a semantics.

That's true even if the 'thing' is an object that is accessed through the root superclass or supertype. The polymorphic dispatch only comes into play through which specific object is assigned to the pointer. But once it is assigned I see no substitution when navigating the pointer.

[Implicit] navigation *is* substitution. You "navigate" from derived type
to the base in the same way, but call it substitution. There is no semantic
difference as long as implementation is abstracted. Navigation /
substitution may include pointer dereferencing, view conversion, creation
of temporal objects, marshaling them over the network. I don't care about
this. It is hidden.

But subclassing relationships are not navigable. In an is-a relationship, like Highlanders, There Can Be Only One. That's why the dynamic_cast in C++ represents a language deficiency. In an is-a relationship, there is only one object in hand at a time that incorporates the entire tree so there is nothing to navigate.


It does not, it has a potential to become. Is-a is abstracted as "is
substitutable for" and the latter is as "inherits this method from."

Another fundamental disagreement. "is-a" does not imply anything at all about substitutability. Is-a is abstracted as "is a member of that set". Nothing more.

Substitutability only comes into play through polymorphic dispatch when a member of a set is accessed during collaboration and then only if the access is through a superclass. And inheritance is orthogonal to subclassing; inheritance merely provides a suite of rules for resolving the properties of a particular member of a root superclass. IOW, inclusion polymorphism and inheritance are enabled by subclassing, but they are quite different things than the is-a semantics.

However, an OOPL supporting the OO paradigm /must/ support the is-a semantics in its type system. That semantics includes the notion that an instance resolves the entire type tree and one cannot separately instantiate multiple instances from different levels of the tree simultaneously. Note that the use of types in Data Modeling is quite different and one can instantiate both super- and subtype instances at the same time. Thus an is-a relationship in an OO context is not navigable but a Data Modeling parent/child relationship is navigable.

I see that as the issue with pointers. One always gets the same object regardless of whether the referenced object is a member of a subclass or not. So there is no substitution unless one can instantiate the pointer differently for each collaboration at run time through some sort of dynamic dispatch mechanism. And that has nothing to do with the nature of pointers themselves because the instantiation is at a different level of abstraction. Thus in the GoF pattern:

* R1 1
[Client] --------------- [Strategy]
A
| R2
+------+------+
| | |
... ... ...

The substitutability lies in instantiating R1 at run time.


OK, if you mean a possibility to instantiate values of S as ones of T. Why
T* cannot be instantiated as T?

I mean that the [Strategy] is-a exists with or without the R1 relationship and is unaffected by whether R1 exists. But LSP is not relevant unless the R1 relationship exists. Nor does it matter how one implements the relationship (T vs T*). The if-a-tree-falls-in-the-woods-is-there-a-sound? argument about the possibility of substitution doesn't matter. It is the existence of the /relationship/ that makes LSP substitutability concrete, not its implementation via pointers or any other mechanism. In contrast,

[Strategy]
A
| R2
+------+--------+
| |
[A] ...
| 1
|
| R1
|
| *
[Client]

Here LSP is not relevant at all, even though R1 may be implemented with a pointer.

Apropos of the point above, I think this whole pointer discussion is a red herring because it is confusing <pointer> value mutability with polymorphic dispatch in object collaborations. In an OO context there is no LSP issue unless one has an is-a relationship and a object collaboration with a superclass in that relationship.

But R1 can be implemented in a variety of ways besides pointers or argument passing, so those implementation details aren't crucial. Similarly in any given collaboration between Client and Strategy there is no navigation of R2. One gets exactly the object that was used to instantiate R1.

IOW, LSP and substitutability are related to the dynamic dispatch mechanism for instantiation, not the pointers, argument passing, or other 3GL relationship implementation details.


But you can dispatch on pointers. Then dispatch happens only upon argument
passing etc. If you agree that pointer is an implementation detail, why
then cannot you accept that this detail could be one of some implementation
of substitutability?

I don't disagree that pointers can be used to implement substitutability. In fact, my argument is that they are implementations of semantics, not the semantics itself. What I disagree with is that pointer dispatch is the same as polymorphic dispatch. The pointer dispatch is not /intrinsically/ polymorphic; any substitution is purely in the value of the pointer. The pointer dispatch semantics are completely and unambiguously describable without any mention of polymorphic dispatch. But one cannot describe LSP substitutability without talking about polymorphic dispatch and, in an OO context, is-a relationships.

As an analogy consider that I can describe any complex behavior with interacting state machines using asynchronous event-based communications. Almost always the state machine actions will be executed at some lower level by making synchronous procedure calls. Does that make a synchronous procedure call inherently asynchronous? No. Like a pointer, it is just an implementation artifact. In this case that call is /inherently/ synchronous even though it implements an asynchronous solution. So the synchronous procedure call has no more to do with asynchronous behavior than a pointer has to do with polymorphic dispatch.

The only substitutability lies in the nature of /references/ because a reference's "value" can be defined to include a null set as well as a class reference set. But that isn't really substitutability because the reference isn't an object. It is just a set definition around the reference's data domain, which is quite different than the target object's responsibility domain.

After all you can always add Dereference_Error exception to the target type
contract and everything will be perfectly substitutable. (:-))

Not in a well-formed OOA/D. B-)) That would lead to a cohesion problem for the target abstraction. Whatever the problem space entity underlying the target abstraction has as intrinsic characteristics, they surely doesn't include exception processing.


Ah, that nGL again! (:-)) Exception processing is not necessary a model. It
could serve for "phase transition." You might be unable to have one model
working well in all cases, just because the solution space has a structure
of its own. You have to handle "bifurcations" by changing models. (Not
everything is a model. I am *not* a translationist, as you know! (:-))

Whether one explicitly models exception processing depends on the problem space. If there are rules and policies around exceptions that are unique to the problem in hand, one would have to model it explicitly in the solution. OTOH, if it is some artifact of the computing space with well defined and has standardized infrastructure, one would leave it to OOD or OOP.

What I am saying here is that IF one explicitly models it at the OOA level, then one would still have to abstract its responsibilities separately from those of the target. That is, the exception would be abstracted separately in some other object(s) than the target in your example.

However, I was thinking of knowledge attributes in general. On thinking about it, I have to retract this particular statement because knowledge attributes can be the root of LSP problems. I have seen Shape trees where the real problem was that completely different knowledge <private> attributes were defined for subclasses and superclass for the same semantics:

Rectangle:
majorSide.
minorSide.

Square:
side.

This is just asking for trouble with the behavior substitutability because the implementations that the behaviors assume are incompatible.


I don't see it as the problem. Both are just implementations, they could be
any. The problem is in what they model, in the problem space. Circle is not
substitutable for ellipse already there.

Really?

[Shape]
A
|
+------+-------+
| |
[Rectangle] [Square]
- majorSide - side
- minorSide

Try defining a constructor for [Shape] that any client can use. Or

[Rectangle]
+ majorSide
+ minorSide
+ area ()
A
|
+-------+--------+
| |
[Square] [Rhomboid]
- side

Presumably Square.area() uses Square.side for the computation. Where does the value of Square.side come from? Even for this simple case all the answers are ugly. For more complex situations it would get worse.

But the real problem is the redundancy. For all [Square] objects you will also have to put the value of Square.side into both majorSide and MinorSide attributes so that clients of [Rectangle] can get the right stuff when they ask about sides.

*************
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: Question on LSP
    ... ClassY* is related to ClassY via subclassing or subtyping. ... A pointer always gives one exactly the same thing every time it is invoked so there is no substitution. ... If the pointer is instantiated differently each time it is passed before each navigation, then one has your substitutability because the receiver now interacts with a different object with different behaviors during each collaboration. ...
    (comp.object)
  • Re: Question on LSP
    ... That is an implementation detail. ... It looks like substitution, it works like substitution... ... A pointer always gives one exactly the same thing every time it is ... Your substitutability, OTOH, is an issue for instantiation. ...
    (comp.object)
  • Re: question about object instantiation
    ... entire pool of memory is created on the heap usually, ... that is is the firt method called instantiation by? ... how about instantiation by pointer and instantiation by value? ... Connection is a derived class like MySqlConnection, OracleConnection, etc. ...
    (comp.lang.cpp)
  • Re: When to use pointers?
    ... If 'b' were a pointer ... upon instantiation of the 'A' object - you would have to ... What all this really boils down to, is that allocating memory ... But what efficiency are ...
    (alt.comp.lang.learn.c-cpp)