[ ... ]
this seems to be specifically waht Juha was objecting to!!
The Shape class ends up with a very application specific
turn_square_yellow()
method. I can understand his horror! Even if we try a more
real world UML editor.
hilight_interface()
that still clutters the base class with UI issues. And sub-class
specific calls. I'm a beginner I looking for design advice!
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. My earlier post contained an implementation that
answered the question that was asked, but wasn't (IMO) particularly
extensible.
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.
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:
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. 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.