rick@tetrauk.UUCP (Rick Jones) (02/08/91)
In a previous article I raised the issue of mutable v. immutable objects; I'm glad I did, it has provoked some very interesting responses: In article <1991Feb3.225619.29491@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes: >I described this situation in a posting to comp.object a while back. I had an inkling I'd seen or heard something on these lines before - it must have been your article. Are you proposing an arbitrary number of programmer-defined sub-interfaces? Although this would be flexible, I can see that it could lead to greater complications and ultimately make the design process even more complex. >I'm concerned a bit about achieving the desired syntactic conciseness; >"read-only array" is a lot longer than "array". I agree - as above, I think there is a danger of what starts as an enhancement eventually leading to unreadable code. >Maybe the compiler could somehow infer the right subinterface. If you look at it carefully, this is really what Bertrand Meyer's global checking is intended to achieve. The *effective* sub-interface depends on the context in which the object reference is used. >Of course, subinterfaces aren't strictly required, since each >subinterface could just be a separate type/class. See below - you're on the same track as Paul Johnson >This sounds like you're proposing a built-in read-only subinterface >much like const in C++. This was my initial idea, but I'm not sure that it really addresses the problem properly. Continuing with the example of figures (I sometimes wonder to what extent these things reflect reality, but at least we all understand what is meant), then you can have an operation "scale" applicable to any figure, which will not change its shape or properties, only its size. You may well want a scaleable square, but you still can't allow it to have either its number of vertices changed, nor its aspect ratio. In article <821@puck.mrcu> paj@uk.co.gec-mrc (Paul Johnson) writes: > >Yes. I have been thinking along precisely these lines and have come >up with some ideas which I call "Fine Grain Inheritance". >I believe you should split your class up into lots and lots of little >superclasses, each implementing one elementary concept (such as "first >item" or "read/write current item") and usually having only one or two >attributes. I have also wondered whether this is a possible solution. My fear is that you might end up having to resolve awkward clashes in a complex class which inherits from very many smaller classes. I haven't been able to try it in practice, so that is just a gut reaction. >This scheme becomes completely flexible when a new type rule (which I >call the "Sibling-Supertype" rule) is added which (put briefly) allows >the following. > If the supertypes of A are a subset of the supertypes of B, then A > is a supertype of B. This I like. It appears a clean way of separating the type hierarchy from the class hierarchy, which I believe is ultimately at the bottom of this problem. >The only problem comes when coarse grain libraries must be reused as well. They would still be compatible, just not as reusable as the fine grain stuff. >Of course, tools like "flat" and "short" become absolutely vital in >fine grain libraries, as does multiple inheritance. Makes a convincing argument for MI :-) (but see below) >See you all at TOOLS '91. I look forward to meeting you - I'd like to discuss this further. In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes: >I have always felt uncomfortable with the emphasis on "Class" in >languages like Smalltalk, C++ etc. To me, the notion of an Object >seems much more basic and fundamental than a Class. > [...] >In the real world, objects do not, strictly speaking, belong to a >class. A classification system is imposed on real objects by our >world view; class membership is an answer to a question we phrase. >Different question, different answer. My current development project has lead to an interesting situation, which is related both to this topic, and to the question of granularity discussed above. Briefly, we are building OLTP server processes, and decided to use an OO approach so we treated the servers as "classes" when defining their interfaces. This is a clean way of giving a behavioural view of the online stored data. I envisaged writing these servers as Eiffel classes, with the features available as services callable by a client. Mainly for implementation reasons this proved impractical, and the final structure is that each "feature" of the server is in fact written as a separate language "class". The result is that the server "object-interface" is a composite construction, where each "feature" is a separate component. A new server can be assembled using several already exisiting "features", plus new special ones where relevant. In this "macro-object" (it is an object - information hiding, interface defined by behaviour, etc), reuse is by assembly, not inheritance. Of course the smaller components are being written in an object oriented language, but at a different level. It seems that maybe the idea of defining an object interface at the language level by assembling a shopping list of features (Bertrand Meyer talks about this in OOSC), *but where each feature is a separate module*, is a possible alternative to inheritance for reuse. This seems to be very close to Paul Johnson's fine grain inheritance, but is inheritance really the correct mechanism, or does it just look right because it's there? I'm a bit too much up to my ears in the project at the moment to think properly about the wider implications, but it does seem to address the issues of finer granularity, and also the notion of type could be defined in terms of the set, or subset, of features assembled for any particular object. I am coming more and more to believe that finer granularity is an important issue when it comes to effective reuse. The larger a traditional class, the more complex it is, and the less likely it is to be reusable. But reuse by inheritance by definition adds to the parent class, thus creating ever larger classes. It is almost self-defeating. I'm not saying inheritance isn't valuable, but perhaps we need something else as well. Food for thought? -- Rick Jones Tetra Ltd. Maidenhead, Your .signature has expired, Berks, UK please apply to your analyst for a replacement rick@tetrauk.uucp
kers@hplb.hpl.hp.com (Chris Dollin) (02/11/91)
In all this debate about RECTANGLE, POLYGON, and add_vertex, I have wondered one thing; what is the add_vertex feature *for*? There are two possibilities that come to (my) mind. One is that it is required so that a POLYGON object can be created and then its vertices added (because they cannot be designated at the moment of creation); in this case, once the vertex initialisation is complete, one would not expect further calls to add_vertex. In this case it seems to me that add_vertex is really only an encoding of create-me-this-polygon-with-these-vertices, and that a way of saying this without lots of separate feature calls might be advisable. (In Lisp or Pop, one might create the object passing in a list/vector/other-collection of verticies.) The other possibility is that during the adult life of a polygon (as opposed to its infancy) its number of vertices changes for whatever reason (didn't somebody suggest a graphics editor where the user can add and remove verticies?). I'd like to know which of these two cases is the norm in which situations, and if there are any other reasonable patterns of add_vertex use. Incidentally, I'm in the camp that says "if POLYGON has add_vertex, then RECTANGLE is not a subtype of / does not inherit from POLYGON". -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
paj@mrcu (Paul Johnson) (02/11/91)
In article <1087@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes: >In article <821@puck.mrcu> paj@uk.co.gec-mrc (Paul Johnson) writes: >> [...] some ideas which I call "Fine Grain Inheritance" [FGI]. >> >>I believe you should split your class up into lots and lots of little >>superclasses, each implementing one elementary concept (such as "first >>item" or "read/write current item") and usually having only one or two >>attributes. > >I have also wondered whether this is a possible solution. My fear is that you >might end up having to resolve awkward clashes in a complex class which >inherits from very many smaller classes. I haven't been able to try it in >practice, so that is just a gut reaction. This worried me when I first started thinking about the idea. In practice it does not seem to be much of a problem. Name clashes are no more common than in traditional coarse grain libraries because the number of features is not increased, only the number of classes. >>This scheme becomes completely flexible when a new type rule (which I >>call the "Sibling-Supertype" rule) is added which (put briefly) allows >>the following. > >> If the supertypes of A are a subset of the supertypes of B, then A >> is a supertype of B. > >This I like. It appears a clean way of separating the type hierarchy from the >class hierarchy, which I believe is ultimately at the bottom of this problem. Nice of you to say so. Personally I would have preferred to keep the type and class hierarchies together but I could not think of a clean way to do it. Separating the two makes the subtype graph implicit. Its no longer a heirarchy since two classes A and B can be mutual subtypes via identical parents. Since the subtype graph is the important one for reuse (see below) I would have preferred to make it explicit. On the other hand tools to describe the subtype graph can be built fairly easily. On reuse: when people say "reuse" they usually mean reusing existing code by expanding the classes. In fact this is not particularly powerful. What is needed (and what FGI provides) is a way of avoiding "case-and-paste" reuse where code is copied and then a global search-and-replace is made to some type name. This leads to code bloat and some horrible problems in maintainance. To take our existing example with rectangles and polygons, suppose we need a centre-of-gravity (COG) function for our shapes and for some reason it does not form part of the classes themselves. We have one for polygons "cog( shape: POLYGON );". If in a coarse-grain library we make RECTANGLE a subtype of POLYGON then we can get the cog of a rectangle with no further effort, but we have to trust cog not to change our shapes. For cog that is trivial, but other functions might not respect that and there is no way of statically type checking it. Using FGI, we already have POLYGON_READ, so we make RECTANGLE a subclass of that (and other things, including RECTANGLE_READ and STRETCHABLE). Our cog function we define as "cog(shape:POLYGON_READ)" which guarantees that cog will only read the vertices and not change them. Hence we can reuse cog in a type-safe manner. A problem arises when we have POLYGON but do not have POLYGON_READ. We can construct it out of existing base classes but then POLYGON_READ will not be an ancestor of POLYGON (unless we change POLYGON). The sibling-supertype rule gets around this because the ancestors of POLYGON are a superset of the ancestors of POLYGON_READ. Hence POLYGON is a subtype of POLYGON_READ. Paul. -- Paul Johnson UUCP: <world>!mcvax!ukc!gec-mrc!paj --------------------------------!-------------------------|------------------- GEC-Marconi Research is not | Telex: 995016 GECRES G | Tel: +44 245 73331 responsible for my opinions. | Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244