Re: Mixing OO and DB
Date: Wed, 20 Feb 2008 18:27:28 -0800 (PST)
Message-ID: <e4dbbf54-7d74-44d4-8000-30b967088805_at_h25g2000hsf.googlegroups.com>
On Feb 20, 11:32 pm, Robert Martin <uncle..._at_objectmentor.com> wrote:
> On 2008-02-19 22:14:36 -0600, David BL <davi..._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.
The "class of S"? What is that?
No, S is a given subtype of T, S is *not* the set of all subtypes of T.
> 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.
I thought you were saying before that S is a subtype of T if substitutability holds for all P. Now you are saying the subtype relationship is a function of P. Which is it?
> 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.
Yes, well the last case was only put in for contrast. My point was that C++ allows for two types of conversions. One happily performs value conversions by slicing away state, the other happily allows you to upcast pointers to variables. Taken together this is a little confused.
> > 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.
Sure they do. I was being facetious. I am a C++ programmer.
> 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.
I generally like the way C++ doesn't tend to force a particular methodology on you. However, its approach to subtyping only caters for object-types.
> >> 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.
This is the sense in which Date says that Real can inherit all operations from Complex. Basically, for a given algorithm we only allow conversions of in-parameters that are passed by value.
Another example: The integers can inherit real-valued multiplicative inverses from the reals.
> >> , 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...
If you want to treat value types as object types, you will *always* find that subtyping is inappropriate amongst value types. I strongly suspect many opportunities for code reuse are lost. The poor C++ programmer has to compensate by using templates all over the place, because the program doesn't properly express a physical-logical separation between an (abstract) value type and its possible encodings.
> > 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.
I find that extremely ugly. It only takes another small step for the
following
struct Square { int width; };
void f(Square s);
Rectangle r;
struct Rectangle : Square { int height; }
f(r); // fine. Slices off the height. 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.
Now of course this proof assumed that v wasn't sliced, so I guess you will say the proof is flawed. However I find the idea that the compiler will silently slice away part of a value and allow you to reinterpret it as something else extremely suspicious. It could only be justified for value types that are merely tuples - ie that cannot hide their components and are referred to as non-scalar types. However the idea that a tuple subtypes one of its components seems plain stupid to me. Received on Thu Feb 21 2008 - 03:27:28 CET