Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is frequent downcasting in class hierarchy always evil?

Tags:

oop

From what I`ve learned, it is no good if you frequently use downcasting in class hierarchies. I agree with that, but what are exceptions from this rule if any?
This is where my design of graphical editor shows thin: I have two hierarchies, where geometric figures hierarchy decoupled from graphic primitives one. Like this:

public class GeometricPrimitive {...}
public class RectangeGeometric: Geometric Primitive {...} 
public class GraphicPrimitive {...}
public class Rectangle: GraphicPrimitive {
 private RectangleGeometric figure;
 ...
}

So, every concrete graphic figure class encapsulates instance of concrete geometry class.
Is that approach is the right one, or should I prefer more generical one? - unfortunately, downcasting would be used in this case:

public class GraphicPrimitive {
   protected GeometryPrimitive figure; 
   ....
}

public class Rectangle: GraohicPrimitive {

        public Rectangle(Color c, TwoDPoint leftHighPoint, TwoDPoint rightLowPoint): 
            base(new RectangleGeometric(leftHighPoint.Point2D, rightLowPoint.Point2D), c) {   }

        #region Geometric Properties

        public TwoDPoint LeftTopCorner {
            get { return new TwoDPoint(base.color, (base.figure as RectangleGeometric).LeftTopCorner); }
        }

        public TwoDPoint RightBottomCorner {
            get { return new TwoDPoint(base.color, (base.figure as RectangleGeometric).RightBottomCorner); }
        }
like image 834
chester89 Avatar asked Sep 28 '09 08:09

chester89


1 Answers

While your question lacks some of the larger context about your application that would help with giving a specific answer, I'll try by giving you some ideas of how I would implement this using your code for inspiration.

I would start by inverting the relationship GeometryPrimitive and GraphicPrimitive. I see the the GeometryPrimitive hierarchy as the domain objects that make up your abstract scene graph and the GraphicPrimitive hierarchy as low level view components that translate a GeometryPrimitive into a set of pixels appropriate for drawing onto some kind of graphics context. The GeometryPrimitive subclasses hold all the state information necessary to describe themselves but no logic for translating that description into pixels. The GraphicPrimitive subclasses have all the pixel pushing logic, but no internal state. In effect, the GraphicPrimitive hierarchy represents a hierarchy of Command Objects.

In the GeometryPrimitive base class, include an abstract method called GetGraphicPrimitive(). In the GraphicPrimitive base class include an abstract method called Draw(Graphics g).

Within each GeometryPrimitive, include the appropriate GraphicPrimitive for drawing the object and an accessor method for accessing it. To draw the entire scene, walk your structure of GeometryPrimitive objects, asking each one for its GraphicPrimitive and then invoking the Draw() method.

abstract class GeometryPrimitive
{
    public abstract GraphicsPrimitive GetGraphicsPrimitive();
}

abstract class GraphicsPrimitive
{
    public abstract void Draw(Graphics g);
}

class RectangleGeometryPrimitive : GeometryPrimitive
{
    public Point TopLeft {get; set;}
    public Point BottomRight {get; set;}

    private RectangleGraphicPrimitive gp;

    public RectanglePrimitive(Point topLeft, Point bottomRight);
    {
        this.TopLeft = topLeft;
        this.BottomRight = bottomRight;
        this.gp = new RectangleGraphicsPrimitive(this);
    }

    public GraphicsPrimitive GetGraphicsPrimitive()
    {
        return gp;
    }
}

class RectangleGraphicsPrimitive : GraphicsPrimitive
{
    private RectangleGeometryPrimitive p;

    public RectangleGraphicsPrimitive(RectangleGeometryPrimitive p)
    {
        this.p = p;
    }

    public void Draw(Graphics g)
    {
        g.DrawRectangle(p.TopLeft, p.BottomRight);
    }
}

class CircleGeometryPrimitive : GeometryPrimitive
{
    public Point Center {get; set;}
    public int Radius {get; set;}

    private CircleGraphicPrimitive gp;

    public RectanglePrimitive(Point center, int radius);
    {
        this.Center = center;
        this.Radius = radius;
        this.gp = new CircleGraphicsPrimitive(this);
    }

    public GraphicsPrimitive GetGraphicsPrimitive()
    {
        return gp;
    }
}

class CircleGraphicsPrimitive : GraphicsPrimitive
{
    private CircleGeometryPrimitive p;

    public CircleGraphicsPrimitive(CircleGeometryPrimitive p)
    {
        this.p = p;
    }

    public void Draw(Graphics g)
    {
        g.DrawCircle(p.Center, p.Radius);
    }
}

As you can see above, no downcasting is required to draw GeometryPrimitives to the screen. With proper use of inheritance, you can also share GraphicsPrimitive objects between different GeometryPrimitives. For example, SquareGeometryPrimitive and RectangleGeometryPrimitive can both use RectangleGraphicsPrimitive if SquareGeometryPrimitive derives from RectangleGeometryPrimitive.

like image 171
Ryan Michela Avatar answered Oct 01 '22 01:10

Ryan Michela