Re: Mixing OO and DB
Date: Fri, 22 Feb 2008 12:04:52 -0600
Message-ID: <200802221204527826-unclebob_at_objectmentorcom>
On 2008-02-21 20:43:52 -0600, David BL <davidbl_at_iinet.net.au> said:
> On Feb 22, 4:03 am, Robert Martin <uncle..._at_objectmentor.com> wrote:
>> On 2008-02-20 20:27:28 -0600, David BL <davi..._at_iinet.net.au> said:
>>> How does a program represent an ellipse? I would prefer it if you
>>> could be more careful with your terminology!
>>
>> I think you know the answer to that. The source code in
>> ellipse.{h,cpp} that defines the C++ class named ellipse *represents*
>> an ellipse at runtime; but is not an ellipse.
>
> This is your justification for not sub-typing circle from ellipse?
Yes! I think it's a pretty good justification too. References to subtypes are not themselves subtypes.
>> The point is that a C++ class named "Circle" is not a >> circle. It "refers" to a circle in some sense; but it is not a circle >> in and of itself.
>
> I think you mean a run time instance of the Circle class "refers" to a
> circle in some sense.
>> The fact that the slicing rules of C++ make it >> impossible to properly upcast a Circle value to an Ellipse value is >> simply evidence that the Circle value is not a subtype of an Ellipse >> value.
>
> Disagree. C++ is broken in that case.
Not in the slightest. The slicing rules of C++ are consistent with substitutability.
>> Nor should it be since a circle value is not a circle, and an >> Ellipse value is not an ellipse.
>
> No, by definition a circle value is a circle etc. Are you
> associating the word "value" with what C.Date calls an appearance (or
> encoding if you like) of a value?
I don't know Date's nomenclature. However the fact that I put two variables next to each other, and call one "radius", and the other "center", and then I wrap both in a container labled "circle", does not mean I have a circle. All I have is a model of a circle. I cannot roll that container the way I could role a circle. I cannot use a tape measure to empirically measure the circumference. I cannot arrange little tiny squares within it to approximate the area. I cannot draw the container with a compass, nor can I bisect it and measure the internal angle of the bisection at a right angle. The container, for all it's wonderful ability to *describe* a circle, is not a circle. And so that poor container is not a subtype of a similar container that happens to describe an ellipse.
>> The issue is isomorphic with S** not being a subtype of T** and >> vector<S> not being a subtype of vector<T>.
>
> The analogy depends on the idea that a value-type variable that holds
> an appearance of a value can be regarded as analogous to a "pointer"
> to a value. I find this analogy a little suspect - because I would
> reserve the terms "pointer" or "reference" to when there is some
> concept of an address space and the pointer or reference is to a
> location in the address space and that is distinct from what is stored
> at that location.
Consider three real numbers, x, y, and r. Taken together they represent a circle. (3,3,27) represents a unique circle with center at (3,3) and radius of 27. Let's say I have two triplets, both with values (3,3,27). They both represent the same circle, not two independent circles. Indeed, the triplets are references to the same circle. The triplets hold the "address" of the circle in cartesian space. The triplets are, for all intents and purposes, pointers to circles.
And pointers to subtypes are not themselves subtypes.
> Values are self-identifying. When a variable holds an appearance of a
> value there is an encoding involved, but that isn't the same as an
> indirection in the sense of a pointer dereference.
But the value is not a circle. The value *refers* to a circle.
>
>> References do not inherit the subtyping of their referents.
>
> Sure.
>> I once saw a geometry library where a circle was a subtype >> of arc. Same flaw.
>
> So do you agree it is a bad idea to treat value-types in the OO sense?
No, it is a bad idea for people to think that instances of C++ classes ARE the objects they represent. The author of that library made the erroneous assumption that instances of the circle class WERE circles.
>> OO classes are "references" to referents in the problem or solution >> domain. Those referents may have subtype relationships. That does not >> mean the classes do.
>
> I don't understand that at all. In what sense does a class (a compile
> time definition of a type plus a full or partial implementation)
> reference something in the problem or solution domain? Let's see you
> formalise that!
class Person {....};
Person p("Bob Martin");
The value of p refers to me. The value of p is not me.
>> >>>> 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; }; >>> struct Rectangle : Square { int height; } >> >>> void f(Square s); >> >>> Rectangle r; >>> f(r); // fine. Slices off the height. Works great. >> >> But here you have made the error that the class of Square should be a >> subtype of the class of Rectangle, just because a true square is a true >> rectangle. That's the flaw! References do not inherit the subtype >> relationship of their referents.
>
> You got it round the wrong way. The above code states (incorrectly)
> that a Rectangle is a subtype of Square.
Sorry, my mistake. I see it now.
Yes, slicing a rectangle to become a square is possible. Don't do that.
This is not a C++ problem any more than: class DesertTopping : public FloorWax
Programmers can do dumb things.
On the other hand slicing an AddressWithZip to an Address is not necessarily a dumb thing.
> You have defined a base class called Address. Presumably this is a
> value type, meaning that it is associated with an abstract set of all
> (possible) address values.
>
> From Address you define a subtype called AddressWithZip. As a subtype
> there is meant to be some sense in which it is a specialisation. I
> presume you would say informally that a AddressWithZip is-a Address.
> Agreed?
Yes.
> It would seem valid to assume that within the set of all address
> values, some have zip codes and some do not. As a type,
> AddressWithZip is relevant to the addresses that have a zip code. If
> follows that a zip code is regarded as part of an address value.
>
> Unfortunately C++ will happily (silently) allow the zip code to be
> sliced away. This means we end up with an Address value without a
> zip code. Are we to assume that the address doesn't have a zip
> code. Clearly we can't! Therefore given an instance of class
> Address we cannot assume it actually locates a particular value in our
> abstract set of all possible address values. This contradicts the
> definition of Address as a value-type for all possible address values.
I don't know about other parts of the world. However, in the US a zipcode is completely redundant information. It speeds up the postal sorters, but adds no new information. An Address uniquely specifies a postal delivery point. Indeed, the zipcode is derivable from the address.
Now, let's say I have two programs. One that was written in the 60s before there were zipcodes, and so uses Address. Another that manages modern mailing lists and so uses AddressWithZip.
void functionInOldProgram(Address a);
void functionInNewProgram(AddressWithZip az) {
functionInOldProgram(az);
}
This works, even though it slices. Indeed it *must* slice because the old program can't deal with even the existence of the zip code. And yet, the substitutability is consistent with the LSP definition of subtype.
>>>>> 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. >> >>> I said values not variables. By definition a value type is a set of >>> values plus operators on those values. Let S be a subtype of T. In C >>> ++ an S* can be upcast to a T*. Let v denote some value in S stored >>> in variable x (of type S). We can take the address of x, upcast it to >>> a T* and dereference (to read the value v) and assume it is of type T. >>> Therefore we have shown that upcasting pointers implies that the >>> values in a subtype must be a subset of the values in a supertype.
>
> Please read my description again, with the following code in mind
>
> class T { ... };
> class S : public T { ... };
>
> S x = v; // v is a value . (r-value in C++ terminology)
> S* p = &x;
> T* q = p;
> T v2 = *q;
>
> No slicing => values in type S are subset of values in type T
OK, I think I follow. You are saying that V2 should == v since they both represent the same value, and therefore the properties of S ought to be a subset of the properties of T.
BTW, it's nice to discuss these things without resorting to name calling. Thank you for that.
-- 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 Fri Feb 22 2008 - 19:04:52 CET