K
kwikius
Uh, actually you shouldn't do that. Most re-use should move out to
delegates, not up to base classes.
Please Do enlighten us all with an example ...
regards
Andy Little
Uh, actually you shouldn't do that. Most re-use should move out to
delegates, not up to base classes.
[email protected] said:Jerry said:struct UML_object {
int x_, y_;
virtual void draw(surface_t &) = 0;
};
class UML_interface : public UML_object {
static color_t color;
public:
static void highlight(bool state=true) {
static color_t colors[] = {
RGB(0,0,0),
RGB(255, 0,0)
};
color = colors[state];
// code to force re-draw goes here.
}
square(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "interface" on specified surface
}
};
class UML_class : public UML_object {
public:
UML_class(int x, int y) : x_(x), y_(y) {}
virtual void draw(surface_t &s) {
// draw "class" on specified surface
}
};
I don't really understand what you are doing there. It's not even
valid C++.
(And public member variables?)
Maybe one of us is missing the point here?
The problem is not how to implement a feature which is common to all
the objects. The problem is how to implement a feature which is not.
Of course if a feature is common to all the objects (such as your
highlighting above), you simply make that feature part of the base
class. The problem is that not all features are common to all objects
and cannot be logically placed in the base class.
[email protected] said:All I understood from his post was: If you have a feature which is
common to *all* the objects, put that feature in the base class.
Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
that every time I can. You don't have to tell me that.)
The issue is that some derived classes might have features which do
not belong to the base class at all. (And, in some cases, couldn't even
be added there.)
There's a definite advantage in that you state up front which
objects are transportable. In many cases, such objects may have
to fulfill additional requirements as well. Deriving from a
common base allows some additional compile time checking.
As for the performance hit, be serious. You're going through an
extra layer; that will cost something in terms of runtime. More
than any virtual function call, anyway.
Which can work too, but is less sure than if the programmer
explicitly states that he implements the TransportableObject
contract. And at least in C++, doing this way means using
templates, with the resulting increase in coupling, code bloat
and explosion of compile times. (This would be partially
mitigated if your compiler supports export, but the code bloat
and some additional coupling is inevitable.)
Inhertance remains the simplest and most efficient means of
specifying conformance to a contract. It does get in the way of
the programmer accidentally creating a class which conforms to
the superficial aspects (presence of such and such a function),
but being unaware of the actual contract, and not implementing
the desired semantics, yes.
Which is why I wouldn't generally put those Derived in a collection of
Base *, but in a collection of Derived *.
Daniel T. wrote:
All I understood from his post was: If you have a feature which is
common to *all* the objects, put that feature in the base class.
Well, duh. Hello? That isn't the issue here at all. (*Of course* I do
that every time I can. You don't have to tell me that.)
The issue is that some derived classes might have features which do
not belong to the base class at all. (And, in some cases, couldn't even
be added there.)
As I noted elsethread, I'd start from the fact that as we're postulating
the design, we're using a shared action among all objects of a given
type to simulate sharing state among all objects of a given type.
If we want objects to act like they're sharing state, I think we should
express that directly rather than simulating it by sharing an action
across those objects.
yes, good point
My earlier post contained an implementation that
answered the question that was asked, but wasn't (IMO) particularly
extensible.
I think we had some posts cross. I hadn't seen you post until today.
Realistically, we know that if you want to be able to highlight objects
of one type, you probably also want to be able to highlight objects of
other types as well -- in fact, you _probably_ want to be able to
highlight objects of _any_ type.
yes
The approach I used elsethread _could_ do that, but would require code
duplicated across all the descendants of 'shape' to do so (to create and
manipulate a static variable in each). Realistically, we also know that
there's more to the UI of an object than just its current color. That
implies that there would be quite a lot of code duplicated across the
hierarchy, something we'd generally much rather avoid.
To accomplish this, we probably want to start by creating a UI class
that describes the current UI state of an object. We create an instance
of this UI class for each class of object in our hierarchy. When we
create each object in the hierarchy, we include a pointer (or reference)
to the appropriate UI state object that's shared among all objects of
that type. When we want to (for example) highlight objects of a
specified type, we do NOT manipulate the individual objects of that type
-- rather, we manipulate the UI state object shared by all objects of
that type:
oh yes, much better
class UI_state {
enum { NORMAL, HIGHLIGHTED } highlight_state;
color colors[2];
int x_, y_;
/* other UI state here */
public:
highlight() { highlight_state = HIGHLIGHTED; }
normal() { highlight_state = NORMAL; }
color current_color() const { return colors[highlight_state]; }
};
enum UML_objects {
UML_CLASS,
UML_INTERFACE,
UML_COMPONENT,
/* ... */
UML_OBJECT_LAST
};
std::vector<UI_state> UI_states(UML_object_last);
class UML_object {
UI_state &ui_;
public:
virtual void draw(surface_t &surface) const = 0;
}
class UML_class : public UML_object {
public:
UML_class(int x, int y)
: x_(x), y_(y), ui_(UI_states[UML_CLASS])
{}
virtual void draw(surface_t &surface) const {
// draw self onto surface using ui
}
};
class UML_interface : public UML_object {
public:
UML_interface(int x, int y)
: x_(x), y_(y), ui(UI_states[UML_INTERFACE])
{}
virtual void draw(surface_t &surface) const {
// draw self onto surface using ui
}
};
Now, to highlight all interfaces, we do NOT look through our collection
of objects looking for interface objects, and then manipulate the color
of each. Instead, 'UI_states[UML_INTERFACE].highlight()' does the whole
job at once. Highlighting objects of a different type obviously requires
nothing more than choosing the appropriate constant. In any case, we're
manipulating a single shared state instead of searching for a set of
objects with duplicated state.
I should add that I doubt this design would really apply to a UML
editor. I can't remember ever having wanted to highlight all objects of
a given type.
I was having trouble in all the theory talk (hetrogenous collections)
and wanted something more concrete. Squares and Circles *still* seemed
a bit unreal. So I thought a noddy UML editor might be better.
The trouble with noddy examples is they remain noddy.
Rather, highlighting would typically be based on a
relationship, such as "everything that inherits from X", or "everything
that implements Y", or "everything that depends on Z". As such, you're
probably going to determine the highlight state based on the
relationships defined in the model, not simply the type of an object.
As such, I think the design is probably for something nobody has ever
(and probably will ever) want, at least as presented. OTOH, I can
imagine situations where such a thing really would be useful, and for
such a situation, I think this is a much cleaner implementation than
anything using dynamic_cast.
IMO, this design is much more adaptable to a real-world scenario. If I
was designing a UML editor, I don't think I'd use a static class
hierarchy to represent the UML elements. Rather, I'd put almost
everything related to each UML element into initalization files of some
sort, and simply load those up during program startup. For example, an
implementation on Window would load a series of metafiles, and when it
needed to draw an object, it would use PlayMetafile or PlayEnhMetafile
to do the job.
Using this design, all the UML objects might easily be of precisely the
same type. Sharing the UI state would remain easy, because when we
create an object we know what kind of object we're creating, and we only
need to use a suitable value to relate that object to the appropriate UI
state. In this situation, dynamic_cast wouldn't work at all -- it would
do nothing to distinguish between one UML element and another, since
from the viewpoint of C++ they would all just be objects of a single
type.
thanks very helpful
FWIW Using dynamic_cast is fine in my book.
dynamic polymorphism is a theory beloved of academics.
outside the ideal world of academia and textbooks, dynamic_cast is
required to discriminate.
Daniel said:I am a video game programmer, so most of my
work is in the simulation domain... often on systems that are much
like what embedded programmers have to deal with.
OO programming's roots are in simulation work so I sometimes think
that I'm more steeped in the paradigm than many others.
Matthias said:Hmm, that's interesting. Does modeling a highly dynamic environment
such as a game world work well with a rigid object system such as C++'?
Kindof like an agent world vs. a static
object tree.
Daniel said:It might be approprate to remember what started this thread in the
first place. Andy Champ found the dynamic_cast syntax to be ugly and
asked how often people use it. I said that I have never used it in 10
years of writing professional code.
Now here you are saying that it is required? All I can say is that our
experiences differ.
Phlip said:No. This is why some games ship with the upper 2/3rds of their code in Lua.
Right. "Alternate hard and soft layers".
[warning, libral snippage]
FWIW Using dynamic_cast is fine in my book.dynamic polymorphism is a theory beloved of academics.outside the ideal world of academia and textbooks, dynamic_cast is
required to discriminate.
It might be approprate to remember what started this thread in the
first place. Andy Champ found the dynamic_cast syntax to be ugly and
asked how often people use it. I said that I have never used it in 10
years of writing professional code.
Now here you are saying that it is required? All I can say is that our
experiences differ.
Juha Nieminen a écrit :
I guess it is one of those urban legend.
All I can say to that is that I have never had my hand forced. The
question with the above is, do you know statically (i.e., at compile
time) the types of the objects?
kwikius said:It is IIRC specifically stated in either The C++ programming language
book, or in the Design and Evolution of C++ book, (both by Bjarne
Stroustruup) I can't remember which, probably TCPPL .
Matthias Buelow said:Hmm, that's interesting. Does modelling a highly dynamic environment
such as a game world work well with a rigid object system such as C++'?
Juha Nieminen said:Daniel T. wrote:
You want me to add a virtual function named "changeSquareColor" to
the Shape base class if I want to be able to change the color of
squares?
That breaks object-oriented design quite horribly. For one, it
breaks the "is-a" relationship. More precisely, it breaks the
"behaves like" property of an "is-a" relationship: If you specify
that "Shape" supports the functionality "changeSquareColor", you
are effectively saying that *all* classes derived from Shape
support that functionality. However, only one does, the others
don't.
The base class would be cluttered with functions specific to some
derived classes.
And besides, in some cases you *can't* modify the base class (eg.
because it's in a library).
Juha Nieminen said:You may have several types derived from the base class, and they are
all stored in a common data container, and their *order* inside that
data container is crucial. Storing each type in their own type-specific
data container doesn't work because this ordering is lost.
Some operations need to be performed to the objects in the order in
which they are stored.
Daniel said:Absolutely. IMHO, the only real difference between them is how much of
the design is exposed code.
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.