Re: Mixing OO and DB
Date: Sat, 23 Feb 2008 01:04:18 GMT
Message-ID: <miKvj.12903$0w.8758_at_newssvr27.news.prodigy.net>
"Robert Martin" <unclebob_at_objectmentor.com> wrote in message
news: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.
>
> Of course. Class circle is a model of circle-ness. Instances of class
> circle are models of individual circles. Those instances are not
> themselves circles; they are references to (models of) circles. And
> references to subtypes are not themselves subtypes.
>
>>> 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.
>
I don't get it. A circle is an abstract object. Something that is abstract is by definition not possibly located in spacetime. Since a circle cannot be located in spacetime, how can you possibly touch, measure or do any of the things you've mentioned? For that matter, if you put two variables next to each other and wrap them in a container labled "circle," all you have is two variables in a container. Now, assuming that the combination of a radius and a center constitutes a definite description of a circle, the contents of those variables at various points in time may define many different circles.
>>> 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.
>
> Of course this is not the way the language works. *q does indeed get
> sliced before it is assigned to v2. It's also not the way that subtyping
> works. Subtyping is a guarantee of substatutability, not of value
> preservation.
>
> 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 Sat Feb 23 2008 - 02:04:18 CET