Search code examples
c#oopdesign-patternslanguage-agnosticfactory-pattern

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)

Solution

  • 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.