Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Let me point out some of the shortcomings of your approach to this problem before I suggest different solutions, each with its own advantages and disadvantages.</p> <h2>Shortcomings of the multiple inheritance approach</h2> <ul> <li>Inheriting from the <code>Stackable</code> class/interface builds knowledge of an aggregate data structure into a class that represents data (<code>Rect</code>). This might prove limiting in situations where <code>Rect</code>s need to go into more than one data structure (A tree for lookups, for instance) or when a data structure needs to have <code>Circle</code>s in it too. This solution also makes it difficult to swap out the data structure later.</li> <li>Having lots of different class combinations can result in an unmanageable amount of classes that must be maintained over time.</li> <li>Should someone else need to work on your code, but fail to notice that you've defined the type <code>StackableStyleableDrawableRect</code>. He or she then goes on to define their own <code>DrawableStackableStyleableRect</code>, that provides the same functionality but isn't the same as your class. In the best case scenario, you now have redundant code in your project. Worse off, you'll run into problems and confusion when you need to mix use of both classes because portions of the codebase already exist that use either.</li> <li>Once another concern is introduced into the program, such as resizing <code>Rect</code>s, do we change all of our existing classes or create further new ones? Do we change <code>StackableStyleableDrawableRect</code> into <code>StackableStyleableDrawableResizeableRect</code>, prompting changes to the existing codebase, or do we create it as an entirely new class?</li> <li>Of course, with multiple inheritance, you run the risk of introducing the <a href="http://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem" rel="nofollow">diamond problem</a> if you're not careful, or if one day you decide that a <code>Rect</code> is both a <code>GDIDrawable</code> and a <code>DirectXDrawable</code> and needs to call something like <code>Drawable::logDrawingOperation()</code>.</li> </ul> <p>So while it may seem trivial that a <code>Rect</code> is-a Drawable, Stackable, etc, this approach is cumbersome and has many drawbacks. I believe that in this case, <code>Rect</code> has no business being anything other than a plain rectangle and <em>shouldn't</em> know about any other subsystem of the project.</p> <h2>Possible alternate solutions</h2> <p>Two alternate solutions exist that I can think of, but each of those makes the usual tradeoffs of readability vs. flexibility and compile-time complexity vs. run-time complexity.</p> <h3>Mix-ins</h3> <p>As illustrated <a href="http://www.thinkbottomup.com.au/site/blog/C%20%20_Mixins_-_Reuse_through_inheritance_is_good" rel="nofollow">here</a>, using mix-ins through template cleverness can avoid some of the problems of the MI approach, though not all. On top of that, it creates a strange inheritance hierarchy and adds compile-time complexity. It also <em>breaks down</em> once we add more classes to the <code>Shape</code> hierarchy, since <code>Drawable</code> only knows how to draw a <code>Rect</code>.</p> <h3>Visitor pattern</h3> <p>The <a href="http://en.wikipedia.org/wiki/Visitor_pattern" rel="nofollow">visitor pattern</a> allows us to separate walking the object hierarchy from the algorithms that operate on it. Since each object knows its own type, it can dispatch the proper algorithm even without knowing what that algorithm is. To illustrate using <code>Shape</code>, <code>Circle</code> and <code>Rect</code>:</p> <pre><code>class Shape { public: virtual void accept(class Visitor &amp;v) = 0; }; class Rect : public Shape { public: float width; float height; void accept(class Visitor &amp;v) { v.visit(this); } }; class Circle : public Shape { public: float radius; void accept(class Visitor &amp;v) { v.visit(this); } }; class Visitor { public: virtual void visit(Rect *e) = 0; virtual void visit(Circle *e) = 0; }; class ShapePainter : public Visitor { // Provide graphics-related implementations for the two methods. }; class ShapeSerializer : public Visitor { // Provide methods to serialize each shape. }; </code></pre> <p>By doing this we have sacrificed some run-time complexity, but decoupled our various <em>concerns</em> from our <em>data</em>. Adding a new concern into the program is now easy. All we need to do is add another <code>Visitor</code> class that does what we want and use <code>Shape::accept()</code> in conjunction with an object of this new class, like so:</p> <pre><code>class ShapeResizer : public Visitor { // Resize Rect. // Resize Circle. }; Shape *shapey = new Circle(); ShapeResizer sr; shapey-&gt;accept(sr); </code></pre> <p>This design pattern also has the advantage that if you forget to implement some data/algorithm combination but use it in the program, the compiler will complain. We may want to override <code>Shape::accept()</code> later to define, say, aggregate shape types like <code>ShapeStack</code>. This way we can traverse and draw the whole stack.</p> <p>I think that if performance isn't critically important in your project, the Visitor solution is superior. It might also be worth considering if you need to meet real-time constraints but it doesn't slow down the program enough to jeopardize meeting the deadlines.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload