The corresponding Canvas visitor would now look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
unit uCanvasVisitorIntf; interface uses uShapeBase, Graphics, Types; type TShapeVisitorCanvas = class(TShapeVisitor, IShapeRectangleVisitor, IShapeCircleVisitor) private FCanvas: TCanvas; protected function TransformPoint(X, Y: Double): TPoint; virtual; public constructor Create(ACanvas: TCanvas); procedure VisitShapeCircle(Instance: TShapeCircle); procedure VisitShapeRectangle(Instance: TShapeRectangle); property Canvas: TCanvas read FCanvas; end; implementation constructor TShapeVisitorCanvas.Create(ACanvas: TCanvas); begin inherited Create; FCanvas := ACanvas; end; function TShapeVisitorCanvas.TransformPoint(X, Y: Double): TPoint; begin Result := Point(Round(X), Round(Y)); end; procedure TShapeVisitorCanvas.VisitShapeCircle(Instance: TShapeCircle); var P1: TPoint; P2: TPoint; begin P1 := TransformPoint(Instance.CenterX - Instance.Radius, Instance.CenterY - Instance.Radius); P2 := TransformPoint(Instance.CenterX + Instance.Radius, Instance.CenterY + Instance.Radius); Canvas.Ellipse(P1.X, P1.Y, P2.X, P2.Y); end; procedure TShapeVisitorCanvas.VisitShapeRectangle(Instance: TShapeRectangle); var P1: TPoint; P2: TPoint; begin P1 := TransformPoint(Instance.X0, Instance.Y0); P2 := TransformPoint(Instance.X1, Instance.Y1); Canvas.Rectangle(P1.X, P1.Y, P2.X, P2.Y); end; end. |
The only differences to the former canvas visitor are the declaration of the interfaces the visitor implements and the missing override in the method declaration.
Still clean and elegant as the standard visitor pattern, but now we are able to split the shape definitions into separate units. When we introduce new shapes in other units we declare a corresponding visitor interface and implement that in the different visitor descendants we have. Can this be made better? For sure it can!
More in Part 3…
I’d say the real reason the Visitor pattern doesn’t work in the real world is that the concrete examples where it makes sense (and the head spinning induced by trying to disentangle who actually ends up doing what and how) are simply too few and far between.
This rendering example, for instance.
The chances of needing to render to a canvas but not have to have any other interaction with the canvas are remote in the real world. e.g. in response to a mouse click determine which shape, if any, the user has clicked on.
The information needed to make that determination is almost certainly involved in rendering the shape (the hit region will correspond to the rendered area). You could of course have a “HitTestVisitor”, which will have to duplicate a lot of the code already contained in the “RenderVisitor”, not to mention any number of other visitors.
Also, any visitor can usually just work directly with the objects in question. No need to complicate things with invocation ping-pong with A visiting B just so that B can invoke A’s visit method. Just have A work directly against B and B doesn’t have to know anything about A at all.
Or more succinctly: With the Visitor Pattern you can only visit things which actually accept visits. It’s by invitation only.
It strikes me that you could implement a shape canvas renderer without even that level of coupling in a way that is much easier to understand and follow.
Maybe this was just a sub-optimal example.
I suggest to follow the remaining parts of this four-part series…