Andrei Bozantan
Andrei Bozantan

Reputation: 3941

Abstract Factory Pattern - dependency between concrete products

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();
        }
    }
}
  1. Is my abstract factory pattern implementation correct?
  2. Inside SimpleSquare.Paint: it is possible to avoid the type cast? (and I want to keep the DrawLine out of the ICanvas interface)
  3. Inside 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

Answers (2)

vgru
vgru

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:

  1. 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).

  2. 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 */
    }
    
  3. 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();
        }
    }
    
  4. Concrete primitive classes would then handle this internal logic:

    class BitmapLine : IBitmapShape
    {
        public void Draw()
        {
            // write to the underlying byte array
        }
    }
    
  5. 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

tcarvin
tcarvin

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

Related Questions