rmy@beach.cis.ufl.edu (Rasthiyaadu Yakaa) (10/13/90)
There is an issue in oo programming and software engieering that has been bothering us for a few weeks, and i would like to hear from knowledgeable/experienced persons on the net who may have encountered a similar problem .. On a database project, the work was divided up based on functionality, e.g. data dictionary, query processor, object management, among various individuals. Of course, a top down design was done (blah, blah ..) Now, each component is to be implemented in an oo manner. Following, guidelines for object based design (Grady Booch etc) the necessary system objects and classes were identified and implemented. Each component obviously was broken down into various smaller components and so on. Following reasonable software engineering practices. HOWEVER, we keep finding that when looking at a module or component from a point of view of its functionality, and in its entirety, we desire an interface for the entire component or sub component as a whole. Consider a software module or component which comprises of several objects and classes. Clearly, each class has an interface, which may be used by other classes in the same module or by external components. When taken collectively, all classes in a module have a tremendous amount of interfaces (messages) but only a small sub set of these need to be visible outside the module to other modules (sort of like an IC where one only sees the external interfaces and the internal interfaces between internal components are hidden - hope this is a reasonable analogy). This makes it easier to conform to specs. It is also easier to understand, convenient, and allows changing internal interfaces. This it would seem is an issue common to fairly large software projects .. The question is: would it be wrong to define a class that simply represents an interface to a layer or module. It would represent collectively a set of interfaces corresponding to a software module. e.g. a dictionary-handler class which represents the outside interface to a DBMS dictionary or catalog (which are implemented as classes and objects). If this is OK, what is the connection between such an interface class and system (dictionary) objects and classes. If this is not OK, what are we doing wrong ? Thanks in advance for all responses e-mail is appreciated since i may miss articles in this newsgroup .. yaseen (rmy@beach.cis.ufl.edu OR rmy@trout.cis.ufl.edu)
eberard@bse.com (Edward V. Berard) (10/14/90)
In article <24872@uflorida.cis.ufl.EDU>, rmy@beach.cis.ufl.edu (Rasthiyaadu Yakaa) writes: > > On a database project, the work was divided up based on > functionality, e.g. data dictionary, query processor, object > management, among various individuals. Of course, a top down > design was done (blah, blah ..) I have quite a number of comments based on real life experience: 1. While a top-down approach is compatible with an object-oriented approach, dividing the system up "based on functionality" is asking for trouble. This is due to the different types of localization used for functional decomposition approaches and object-oriented approaches. Simply put, functional decomposition approaches localize information around objects, and object-oriented approaches localize information around objects. Objects are not functions and functions are not objects. What happens is this: Very often some information about an object is placed in one functional "chunk" (piece of the system), and other information is located in other functional chunks. Each functional piece of the system is then usually handed off to separate teams. Each team then attempts to identify objects in their specific chunk. As they identify objects, they will attempt to characterize the object they have based on the information at hand, i.e., their specific chunk. Unfortunately, it is highly unlikely that their chunk of the system contains a complete description of the object. Further, it is also very likely that if the "same" object shows up in other chunks, it will appear to have different characteristics -- and, thus, will be designed differently. All will appear to be going well until integration time. If objects must be transmitted among several parts of the system, those dealing with objects coming from other chunks of the system will notice that others did not characterize the "same" objects in the same way. If you are working with a strongly typed language, some intended interactions may not be allowed. Even if you are dealing with a comparatively weakly typed language, some operations will not be possible due to such things as "missing" operations in object interfaces. As you begin to modify the system objects (and possibly even the system design) to compensate for these problems you will uncover other interesting phenomena. For example, you will find that many objects which should be common to several different parts of the system, will have been implemented many different times in many different ways (so much for reusability). What will be especially irritating to management is the fact that the effort seemed to be going along at an acceptable pace, until integration time -- then a lot of re-work will slow progress to a crawl. The way to avoid all this nonsense is to do two things. First, identify objects at the beginning of the effort. Specifically, survey the entirety of the requirements information to identify candidate objects, and survey the entirety of the requirements information again to gain as complete an understanding of the objects as possible. (This information will be in two general categories: information about each object in isolation, and information about how the objects will interact to affect a solution to the original problem.) The second thing you need to do is an object-oriented decomposition (i.e., _not_ a functional decomposition). Identify the large objects in the system, then identify the objects which make up these objects. Continue the process until you have small, easily-definable objects. This is not unlike designing an automobile. The automobile is a large object composed of several smaller objects, e.g., an engine, a transmission, and an electrical system. These will, in turn, be composed of still smaller objects, e.g., nuts and bolts. 2. The connotations of what an object is in terms of an "object management system" often conflict with what an object is in the classic object-oriented sense. > The question is: would it be wrong to define a class that simply > represents an interface to a layer or module. It would represent > collectively a set of interfaces corresponding to a software > module. e.g. a dictionary-handler class which represents the > outside interface to a DBMS dictionary or catalog (which are > implemented as classes and objects). If this is OK, > what is the connection between such an interface class and > system (dictionary) objects and classes. If this is not OK, > what are we doing wrong ? Here, you are touching on object-oriented development in the large (OODIL). The problem you have encountered is not unlike dealing with a very large object, e.g., an automobile. When I drive a car, I am oblivious to the smaller components which make up the car, e.g., I do not see the engine. Even if I raise the hood (bonnet), and look at the engine, I cannot see all the objects which are internal to the engine. There are several key points: 1. By viewing a system as simply a large object, and asking what should be in the interface to this object, with no regard to how the object is implemented internally, I avoid the problem of attempting to put a coat of "object-oriented paint" on something which is clearly not an object, i.e., a functionally-designed component. 2. While classes, metaclasses, and instances of each are sufficient for small, textbook problems, they are not entirely satisfactory for larger systems. For example, consider the engine of an automobile. It presents one interface to the transmission of the automobile, another (completely different) interface to the fuel system, and yet another different interface to the cooling system. In effect, the engine is a very large object with what appears to be several completely disjoint interfaces. (For a better picture, ask yourself if your university or company is an object. Of course it is. Now ask yourself if there is only one interface to this very large object. No, there are many.) There will be other large objects which seem to represent capabilities rather than stand-alone applications. For example, consider a windowing system, e.g., X Windows. rather than being a very physically cohesive system of objects, it appears to be more like a library of objects whose purpose is to support a very large object-oriented concept, i.e., windows. To construct a dialog window, for example, you will need quite a number of objects, e.g., radio buttons, check boxes, static and editable text, icons, pictures, scroll bars, etc. The first large object I mentioned (i.e., the one with a high degree of physical cohesion and the possibility of multiple disjoint interfaces) is what I refer to as a "system of interacting objects." You might say that each object-oriented application you create is just such a beast. The second large object (i.e., the one which has a high degree of logical cohesion (the objects are all there to support a larger, object-oriented concept) and is granular (you can select individual components)) is what I and others refer to as a "subsystem." 3. In designing any system, but especially a large system, it is crucial to have well-defined interfaces among the components. These interfaces should be defined early, and should remain fixed. Otherwise, it is difficult to "hand off" pieces of the system to different teams and expect them to smoothly integrate the pieces. -- Ed ---------------------------------------------------------------------------- Edward V. Berard | Phone: (301) 353-9652 Berard Software Engineering, Inc. | FAX: (301) 353-9272 18620 Mateney Road | E-Mail: eberard@bse.com Germantown, Maryland 20874 | ----------------------------------------------------------------------------
rfg@NCD.COM (Ron Guilmette) (11/03/90)
In article <24872@uflorida.cis.ufl.EDU> rmy@beach.cis.ufl.edu () writes: > >The question is: would it be wrong to define a class that simply >represents an interface to a layer or module... A related question was discussed awhile back, i.e. the question of why C++ can't be a bit more like Ada. Specifically, I think that the kind of thing that you are perhaps looking for might be a header file that looks kinda like this: class car; car::car (int cost); car::~car (); heading car::turn (direction d); next_date car::change_oil (station s); In other words, a sort of *abbreviated* description of the interface to a class which is provided to some user of the class. Allowing abbreviated descriptions like this (which leave out information about data members) could potentially cut down dramatically on the amount of recompilations we have to do over the lifetime of a class. I hope that the ANSI committee will consider this possible solution for an important (and as yet unresolved) problem. I (for one) can't see any problems with the solution proposed here. It certainly doesn't break any existing syntax rules, and if (for instance) `car::turn' was never really defined for the class car, then you most certainly would find out about *that* problem at link time. One minor problem with this proposed `feature' is that `car::change_oil' might be private to the class car (in which case the user of the header file should not be allowed to use `car::change_oil'. That problem could easily be solved by providing three different name manglings for member functions. One (and only one) of these would be selected and used depending upon the access category (i.e. public, private, or protected) of the member function where its *true* declarations (within the class body) occurs. -- // Ron Guilmette - C++ Entomologist // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // Motto: If it sticks, force it. If it breaks, it needed replacing anyway.
vaughan@mcc.com (Paul Vaughan) (11/04/90)
From: rfg@NCD.COM (Ron Guilmette)
A related question was discussed awhile back, i.e. the question of
why C++ can't be a bit more like Ada.
Specifically, I think that the kind of thing that you are perhaps
looking for might be a header file that looks kinda like this:
class car;
car::car (int cost);
car::~car ();
heading car::turn (direction d);
next_date car::change_oil (station s);
In other words, a sort of *abbreviated* description of the interface to
a class which is provided to some user of the class.
Allowing abbreviated descriptions like this (which leave out information
about data members) could potentially cut down dramatically on the
amount of recompilations we have to do over the lifetime of a class.
I hope that the ANSI committee will consider this possible solution
for an important (and as yet unresolved) problem.
. . .
I've suggested this sort of thing for different reasons before, but it
hasn't sparked any enthusiasm. I proposed that this sort of
declaration would help to solve severe modularity problems. One thing
that became clear to me was that a very complete declaration is
required. For instance if the real class definition said
class car : public virtual vehicle {
public:
virtual Heading heading() const;
// . . .
};
You're going to have to declare
class car : public virtual vehicle;
public virtual Heading car::heading() const;
Anyway, I'd lend some support if anyone wants to start a ball rolling
in the ANSI committee.
Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639
Box 200195, Austin, TX 78720 | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan
mjy@charis.UUCP (Michael J. Young) (11/04/90)
In article <2390@lupine.NCD.COM> rfg@NCD.COM (Ron Guilmette) writes: >Specifically, I think that the kind of thing that you are perhaps >looking for might be a header file that looks kinda like this: > > class car; > > car::car (int cost); > car::~car (); > heading car::turn (direction d); > next_date car::change_oil (station s); > >In other words, a sort of *abbreviated* description of the interface to >a class which is provided to some user of the class. That might be fine, as long as you don't ever try to use class car. But what happens when you try to create an instance of car, or use it as a member of another class? The compiler has no way of determining sizeof(car). -- Mike Young UUCP: lectroid.sw.stratus.com!charis!mjy Tel: +1 508 568 1572
pcg@cs.aber.ac.uk (Piercarlo Grandi) (11/05/90)
On 3 Nov 90 04:15:50 GMT, rfg@NCD.COM (Ron Guilmette) said: rfg> Specifically, I think that the kind of thing that you are perhaps rfg> looking for might be a header file that looks kinda like this: rfg> class car; rfg> /* void */ car::car (int cost); rfg> /* void */ car::~car (); rfg> heading car::turn (direction d); rfg> next_date car::change_oil (station s); You can well do this currently with a little sleight of hand using inheritance. The key to understanding thsi is that your proposal only works if you manipulate only pointers or references to car objects, and not for all operations (all that imply dereferencing are out). Otherwise you have to pull in the full declaration. rfg> In other words, a sort of *abbreviated* description of the interface to rfg> a class which is provided to some user of the class. Also, if my modest proposal for an alternative to member functions (including destructors and constructors) were allowed, you would write: class car; void operator { (car &self,int cost); // constructor void operator } (car &self); // destructor heading turn (car &self,direction d); next_date change_oil (car &self,station s); Then you could write (references are implemented with pointers): class car; typedef struct char char; car *c1 = new car(8999); car *c1 = (car *) malloc(sizeof (car)); __op_lbrace__car__int(c1,8999); c1->turn(90); __turn__car__direction(c1,90); change_oil(*c1,esso); __change_oil__car__station(c1,esso); delete c1; __op_rbrace__car(c1); free((void *) c1); But please note the problem in the first line; we do not know the value of sizeof (car). The description is too abbreviated for that: rfg> Allowing abbreviated descriptions like this (which leave out rfg> information about data members) could potentially cut down rfg> dramatically on the amount of recompilations we have to do over the rfg> lifetime of a class. Precisely -- but this is only because you are going to use pointers for access. Just like opaque types in Modula 2. And note that without knowing the size of a car object you cannot even allocate them. I know of some languages in which to get around this problem and minimize recompilation and favour binary compatibility across struct declaration revisions you can say something like class[100] car; // car is not more than 100 bytes long class[100] { char *make; long unsigned serialno; ... } car; This has the effect of wasting some space, but avoiding recompilation of code if one only adds news fields, as long as the total size does not grow beyond 100 bytes. Naturally you can go the Ada way and assume that definitions are precompiled, and by saying just 'import car;' the definition of car is imported and you don't need to repeat it. But this is not C++, which assumes conventional compiler technology. -- Piercarlo "Peter" Grandi | ARPA: pcg%uk.ac.aber.cs@nsfnet-relay.ac.uk Dept of CS, UCW Aberystwyth | UUCP: ...!mcsun!ukc!aber-cs!pcg Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk
scp@acl.lanl.gov (Stephen C. Pope) (11/05/90)
on 3 Nov 90 20:01:59 GMT, vaughan@mcc.com (Paul Vaughan) said: Paul> From: rfg@NCD.COM (Ron Guilmette) Ron> A related question was discussed awhile back, i.e. the question of Ron> why C++ can't be a bit more like Ada. [...] Paul> I've suggested this sort of thing for different reasons before, but it Paul> hasn't sparked any enthusiasm. I proposed that this sort of Paul> declaration would help to solve severe modularity problems. One thing Paul> that became clear to me was that a very complete declaration is Paul> required. There are other problems. Like inlined methods, which can't be used in this way since the compiler must know about the implementation (data members) of the class to generate inline code. stephen pope advanced computing lab, lanl scp@acl.lanl.gov
bp@porpoise.cis.ufl.edu (Brian Pane) (11/05/90)
In article <12823@cadillac.CAD.MCC.COM>, vaughan@mcc.com (Paul Vaughan) writes: | | From: rfg@NCD.COM (Ron Guilmette) | | A related question was discussed awhile back, i.e. the question of | why C++ can't be a bit more like Ada. ^^^^^^^^^^^ aaargh! ^^^^^^^^^^^^^^^^ | | Specifically, I think that the kind of thing that you are perhaps | looking for might be a header file that looks kinda like this: | | class car; | | car::car (int cost); | car::~car (); | heading car::turn (direction d); | next_date car::change_oil (station s); | | In other words, a sort of *abbreviated* description of the interface to | a class which is provided to some user of the class. | | Allowing abbreviated descriptions like this (which leave out information | about data members) could potentially cut down dramatically on the | amount of recompilations we have to do over the lifetime of a class. | | I hope that the ANSI committee will consider this possible solution | for an important (and as yet unresolved) problem. | . . . | |I've suggested this sort of thing for different reasons before, but it |hasn't sparked any enthusiasm. I proposed that this sort of |declaration would help to solve severe modularity problems. That sort of abbreviated class description seems like a very good approach for leaf classes. However, you still need the full declaration in order to derive child classes. IMHO, such a syntax would only make it harder to get any work done; maintaining consistency among the full class declaration, the abbreviated declaration, and the member implementations would require extra software engineering work. Actually, I suppose you could achieve the same result by writing a filter that read the class declaration and wrote out a version containing only the function prototypes, not the data members. This filtered file could be included by files which needed only to call the class's member functions, and the original could be included by files implementing derived functions. You could maintain declaration coherency by writing a makefile rule which regenerated the filtered file when the original header file was modified, perhaps. How does this approach sound? | Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639 | Box 200195, Austin, TX 78720 | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan ------------------------------------------------------------------------- Brian_Pane::~Brian_Pane() { "bp@swamp.cis.ufl.edu"; } // University of Florida Department of Computer Science Class of 1991 -------------------------------------------------------------------------
vaughan@mcc.com (Paul Vaughan) (11/06/90)
From: scp@acl.lanl.gov (Stephen C. Pope) vaughan@mcc.com (Paul Vaughan) said: Paul> From: rfg@NCD.COM (Ron Guilmette) Ron> A related question was discussed awhile back, i.e. the question of Ron> why C++ can't be a bit more like Ada. [...] Paul> I've suggested this sort of thing for different reasons before, but it Paul> hasn't sparked any enthusiasm. I proposed that this sort of Paul> declaration would help to solve severe modularity problems. One thing Paul> that became clear to me was that a very complete declaration is Paul> required. There are other problems. Like inlined methods, which can't be used in this way since the compiler must know about the implementation (data members) of the class to generate inline code. stephen pope advanced computing lab, lanl scp@acl.lanl.gov I may be missing something here, but I don't really see inline methods as being a problem, especially when using this as a technique to enhance modularity, rather than data hiding. Inline methods can only be inlined in modules where the inline method definition is present. In my proposal, the class definition must be available in order to define any method of that class. So if you've got the class definition, you can define inline methods and they can be inlined. If you haven't got the class definition, you can't have any inline method definitions, and obviously, they can't be inlined. This really isn't any different from the current situation. So, it comes down to a tradeoff between modularity and speed. The more stuff you dump into a module, the more potential for inlining and speed you get. Having the ability to declare methods (and data members, BTW) in this way gives the originator or user of a class the flexibility to make this tradeoff. The biggest problem that I see is that it transfers even more duties to the linking stage. I believe it can be handled there, but the error messages (for instance, about using a method declared independently as public but listed as private in the class definition) aren't going to be as precise as we've come to expect. Also, I don't know much about how well it can be supported by a compiler that first produces C code. Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639 Box 200195, Austin, TX 78720 | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan