Reputation: 3941
Please read the code below, the questions are at the end.
using System;
using System.Collections.Generic;
namespace Graphics
{
public interface IGraphicsFactory
{
ICanvas CreateCanvas();
Square CreateSquare();
ComposedShape CreateComposedShape();
}
public class SimpleGraphicsFactory : IGraphicsFactory
{
public Square CreateSquare()
{
return new SimpleImpl.SimpleSquare();
}
public ComposedShape CreateComposedShape()
{
return new SimpleImpl.SimpleComposedShape();
}
public ICanvas CreateCanvas()
{
return new SimpleImpl.SimpleCanvas();
}
}
public interface ICanvas
{
void AddShape(ShapeBase shape);
void Render();
}
public abstract class ShapeBase
{
public abstract void Paint(ICanvas canvas);
}
public abstract class Square : ShapeBase
{
public int size;
}
public abstract class ComposedShape : ShapeBase
{
public int size;
public ShapeBase InternalShape1 { get; set; }
public ShapeBase InternalShape2 { get; set; }
}
}
namespace Graphics.SimpleImpl
{
internal class SimpleSquare : Graphics.Square
{
public void Init()
{
// do something really important
}
public override void Paint(ICanvas canvas)
{
Init();
//?? how to avoid the type cast? (and I want to keep the DrawLine out of the ICanvas interface)
SimpleCanvas scanvas = (canvas as SimpleCanvas);
scanvas.DrawLine();
scanvas.DrawLine();
scanvas.DrawLine();
scanvas.DrawLine();
}
}
internal class SimpleComposedShape : Graphics.ComposedShape
{
public void Init()
{
//?? how can I call `InternalShape1.Init', preferably without type casts? (and I want to keep `Init` out of the `ShapeBase` class)
// this.InternalShape1.Init();
// this.InternalShape2.Init();
}
public override void Paint(ICanvas canvas)
{
Init();
// TODO: draw the thing
}
}
internal class SimpleCanvas : Graphics.ICanvas
{
List<ShapeBase> shapes = new List<ShapeBase>();
public void AddShape(ShapeBase shape)
{
shapes.Add(shape);
}
public void Render()
{
foreach (ShapeBase s in shapes)
{
s.Paint(this);
}
}
public void DrawLine()
{
}
}
}
namespace Test
{
using Graphics;
class TestSimpleGraphics
{
static void Test1()
{
IGraphicsFactory fact = new SimpleGraphicsFactory();
ICanvas canvas = fact.CreateCanvas();
Square sq1 = fact.CreateSquare();
Square sq2 = fact.CreateSquare();
ComposedShape cs = fact.CreateComposedShape();
cs.InternalShape1 = sq1;
cs.InternalShape2 = sq2;
canvas.AddShape(cs);
canvas.Paint();
}
}
}
SimpleSquare.Paint
: it is possible to avoid the type cast? (and I want to keep the DrawLine
out of the ICanvas
interface)SimpleComposedShape.Init
: how can I call InternalShape.Init
, preferably without type casts? (and I want to keep Init
out of the ShapeBase
class)Upvotes: 1
Views: 506
Reputation: 51274
If I got your intentions right, you are trying to mimic the functionality of the System.Drawing.Graphics
class (or HTML <canvas>
, for example).
If that's correct, I would give following suggestions:
Rename IGraphicsFactory
to ICanvasFactory
and let it create only concrete ICanvas
implementations. Remove the CreateSquare
and other methods, because you don't have to create shapes through a factory (it's only important that your Shapes are passed a concrete ICanvas
implementation).
The ICanvas
interface represents a canvas which can draw primitive shapes (lines, circles, filled areas, etc.). This means that you should expose public methods which will allow callers to create these primitives. Graphics
also provides various transformation capabilities, but this may be an overkill right now:
interface ICanvas
{
void Clear();
void DrawLine(Point from, Point to, Pen pen);
void DrawCircle(Point center, Double radius, Pen pen);
void Paint();
/* and stuff like that */
}
A Canvas
should not contain a list of Shapes
, but rather a list of primitives. E.g., when you call the ICanvas.DrawLine(...)
method, it should create an instance of a Line
primitive and store it in the internal list.
The functionality of these primitive classes will depend on the actual implementation of your ICanvas
(does it paint to a Bitmap, or to a printer, etc.). You will also have some hidden, implementation-dependent data (I will use a byte[]
, pretending it stores an 8-bit bitmap of some sort):
class BitmapCanvas : ICanvas
{
private readonly byte[] _bitmapData;
private readonly List<IBitmapShape> _primitives;
public BitmapCanvas(int width, int height)
{
_bitmapData = new byte[width * height];
_primitives = new List<IPrimitiveShape>();
}
public void DrawLine(...)
{
// different implementations will handle this part differently.
_primitives.Add(new BitmapLine(_bitmapData, from, to, pen));
}
public void Paint()
{
Clear(_bitmapData);
foreach (var shape in _primitives)
shape.Draw();
}
}
Concrete primitive classes would then handle this internal logic:
class BitmapLine : IBitmapShape
{
public void Draw()
{
// write to the underlying byte array
}
}
Since ICanvas
implementation will not draw actual shapes, you don't need the ShapeBase
class. You will, however, need an analogue class for drawing graphic primitives (called IBitmapShape
above).
Upvotes: 0
Reputation: 10855
1 - I think your SimpleGraphicsFactory
is indeed a good example of an Abstract Factory.
2 - It is completely appropriate that SimpleSquare
casts to SimpleCanvas
because they are both part of the same "family", created by the same concrete factory. Recall the definition of Abstract Factory (emphasis is mine):
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
The implication of this design pattern is that the classes created by it can assume / require they are being used with classes from the same family.
To use another example from the .NET world, the System.Data
namespaces acts in a similar way. Objects from the System.Data.Sql
namespaces will not work with objects from System.Data.Oracle. You cannot pass a SqlParameter
where an OracleParameter
is expected. You select the family and stay within the family.
3 - I cannot tell what you are trying to do, you;ll need to comment with details and I'll revies my answer to address. I would expect a ComposedShape
to have a method Add(Shape s)
that lets the caller add multiple shapes to the composite (container). But perhaps I misunderstand.
Upvotes: 1