Re: Mixing OO and DB

From: Robert Martin <unclebob_at_objectmentor.com>
Date: Wed, 20 Feb 2008 08:32:38 -0600
Message-ID: <200802200832387987-unclebob_at_objectmentorcom>


On 2008-02-19 22:14:36 -0600, David BL <davidbl_at_iinet.net.au> said:

> On Feb 19, 11:18 pm, Robert Martin <uncle..._at_objectmentor.com> wrote:

>> On 2008-02-18 21:09:48 -0600, David BL <davi..._at_iinet.net.au> said:
>> 
>>> I have a more specific understanding of LSP.   Let an object mean a
>>> variable that has identity, state and behavior, but isn't assumed to
>>> hold a value.  If S,T are object types where S is a subtype of T then
>>> LSP states that if q(x) is a property provable about objects x of type
>>> T. Then q(y) should be true for objects y of type S.
>> 
>>> In my mind LSP is inappropriate for value-types because it is
>>> concerned with variables rather than values.
>> 
>> Sorry to butt in but Liskov didn't make a distinction about
>> value-types.  The substitution principle simply says that every program
>> P that works with T should also work with S.

>
> I think you are oversimplifying what substitution means.
>
> What exactly do you mean by "program"?

I was using Liskov's words.

> It sounds like you refer to
> the entire source code for an application, but that would include the
> code that defines T and S.

Let's restrict P to the set of code that directly uses, but does not define, T.

>
> Moving on, you say that *every* program P that works with T should
> also work with S. Suppose S' is another subtype of T. Let program P
> be the following
>
> // declaration
> void foo(T* x);
>
> // call
> S' s;
> foo(&s);
>
> We cannot replace T with S in this code, because the cast from S'* to
> S* isn't valid. So presumably you mean something else by "works
> with". What exactly? Can you formalise that?

S is any and every subtype of T. S' is in the class of S. Remember, Liskov's rule was an attempt to define what S was. In essense she said that subtypes are those things that are substitutable.

What this means, of course, is that some S may be a subtype of T for one particulr P but not for another. Liskov's rule implies that being a subtype is not intrinsic, it is an attribute evaluated by the user. It is P that shows that S is a true subtype of T. S and T cannot prove their relationship without P.

> Substitution can mean lots of different things. Eg it can relate to
> implicit compile time genericity in the source code, substitution of
> in-parameters, out-parameters, or type coercions of pointers. It
> follows that there are alternative notions of sub-typing (and
> inheritance as well).

Of course.

>
> C++ only makes a feeble attempt at supporting value types. For
> example when one writes
>
> class B {};
> class D : public B {};
>
> the C++ compiler allows conversions as follows
>
> D x;
> B y = x; // ok : "value conversion"
>
> D* px;
> B* py = px; // ok : "pointer conversion"
>
> D** ppx;
> B** ppy = ppx; // Compiler error
>
>
> It is worth asking why the first two are considered valid and the
> third is not.

Lots and lots was written about why that particular conversion is not allowed. It is similar to the arguments for why a vector<S> is not a subtype of a vector<T>. In the end the argument always boils down to LSP. Some user of ppx might cast it to a B** and load it with a D'**. Poor ppx, a D**, now holds a D'**. Bad. A substitution violation.

> Also, how does this relate to an LSP notion of
> subtyping? In the interests of being precise I like to associate LSP
> specifically with the pointer conversion.
>
> This pointer conversion is unreasonable for value-types. It is not
> at all surprising that C++ programmers don't think a circle is an
> ellipse.

C++ programmers know that a circle is an ellipse. They also know that a program that represents a circle is not necessarily a program that represents an ellipse -- for the same reason that vector<circle> is not a subtype of vector<ellipse>.

> Eg suppose Circle subtypes Ellipse then
>
> Ellipse e;
> Circle c;
> Circle* px = &c;
> Ellipse* py = px; // pointer conversion
> *py = e; // oops

As I said. Exactly the same reasoning. A reference to S is not a subtype of a referenct to T.

>> It is easy to envison a T and S that are pure value types.  Imagine,
>> for example, an Address type, and a subtype that supports 9 digit
>> zipcodes.  Any program written for Addess can safely use the subtype.
>> 
>> BTW, while it is true that real numbers are a subtype of complex
>> numbers (i.e. any algorithm that works when the I part is non-zero
>> should work when the I part is zero)

>
> What does that mean?

It means that any valid algorithm using complex number remains valid when the imaginary part of all the complex numbers is zero. i.e. it remains valid for real number.

>
> For example consider the following function to find roots of az^2 + bz
> + c = 0, where it is assumed 'a' is non zero.
>
> pair<Complex,Complex> FindRoots(Complex a, Complex b, Complex c);
>
> Equations with real valued coefficients can easily have complex valued
> roots. I think you need to elaborate on what you said above.

Valid point. I should have been specific that the complex numbers were used as inputs to the algorithm.
>
>

>> , it is not true that Real inherits
>> from Complex.

>
> C.Date discusses a sense in which Real can subtype Complex, and Real
> can inherit all operations from Complex.

We can define inheritance to mean anything we want. However, in the OO sense, a complex number composed of two real numbers cannot be the base class of the real numbers. If it were, then every real number would have two real numbers inside it...

>
> I don't consider LSP to be applicable to value-types. LSP is relevant
> to OO, and objects are variables not values. IMO the LSP notion of
> subtyping implies that a reference to a variable (ie object) can be
> passed to a function that expects a reference to a variable of any
> supertype. For value-types, this implies that subtype and supertype
> must represent the exact same set of values.

struct Address {
  string street, city, state;
};

struct AddressWithZip : Address {
  string zip;
}

void f(Address a);

AddressWithZip az;
f(az); // fine. Slices off the zip. Works great.

> Here is a "proof": One expects variables of the supertype to support
> assignment to any values of the supertype, hence we deduce that a
> subtype is only allowed to contain a superset of all the values in a
> supertype.

Fine so far. subtype values must have every variable that the supertype has.

> Also the function can read the value of the variable as a
> value in the supertype, which shows that the values of the subtype
> must be a subset of the values in the supertype.

I didn't follow that one. Subtypes cannot contain subsets of variables of the supertype.

-- 
Robert C. Martin (Uncle Bob)  | email: unclebob_at_objectmentor.com
Object Mentor Inc.            | blog:  www.butunclebob.com
The Agile Transition Experts  | web:   www.objectmentor.com
800-338-6716                  |
Received on Wed Feb 20 2008 - 15:32:38 CET

Original text of this message