抽象工厂模式 - 具体产品之间的依赖性

时间:2013-08-01 20:46:16

标签: c# oop design-patterns language-agnostic factory-pattern

请阅读下面的代码,问题在最后。

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. 我的抽象工厂模式实现是否正确?
  2. SimpleSquare.Paint内:可以避免类型转换吗? (我希望将DrawLine保留在ICanvas界面)
  3. 之外
  4. SimpleComposedShape.Init内:如何调用InternalShape.Init,最好不要使用类型转换? (我希望将Init保留在ShapeBase类)
  5. 之外

2 个答案:

答案 0 :(得分:1)

1 - 我认为你的SimpleGraphicsFactory确实是抽象工厂的一个很好的例子。

2 - SimpleSquare投射到SimpleCanvas是完全合适的,因为它们都是同一个家庭&#34;的一部分,由同一个具体工厂创建。回想一下Abstract Factory的定义(重点是我的):

  

提供用于创建相关或依赖系列的界面   没有指定具体类的对象。

这种设计模式的含义是它创建的类可以假定/要求它们与同一族的类一起使用。

要使用.NET世界中的另一个示例,System.Data命名空间的行为方式类似。来自System.Data.Sql命名空间的对象不适用于System.Data.Oracle中的对象。您无法通过SqlParameter预计OracleParameter的位置。您选择家庭并留在家庭中。

3 - 我不知道你要做什么,你;我需要对细节发表评论,并且我会回复我的答案。我希望ComposedShape有一个方法Add(Shape s),让调用者可以为复合(容器)添加多个形状。但也许我误解了。

答案 1 :(得分:0)

如果我的意图是正确的,那么您正试图模仿System.Drawing.Graphics类(或HTML <canvas>)的功能。

如果这是正确的,我会提出以下建议:

  1. IGraphicsFactory重命名为ICanvasFactory,并让它只创建具体的ICanvas实现。删除CreateSquare和其他方法,因为您不必通过工厂创建形状(唯一重要的是您的形状通过具体的ICanvas实现)。

  2. ICanvas界面表示可以绘制原始形状(线条,圆圈,填充区域等)的画布。这意味着您应该公开允许调用者创建这些基元的公共方法。 Graphics还提供了各种转换功能,但现在这可能是一种过度杀伤力:

    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. Canvas不应包含Shapes的列表,而应包含基元列表。例如,当您调用ICanvas.DrawLine(...)方法时,它应该创建一个Line原语的实例并将其存储在内部列表中。

    这些原始类的功能取决于ICanvas的实际实现(它是绘制到位图还是打印机等)。您还将拥有一些隐藏的,与实现相关的数据(我将使用byte[],假装它存储某种类型的8位位图):

    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. 然后,具体的基本类将处理这个内部逻辑:

    class BitmapLine : IBitmapShape
    {
        public void Draw()
        {
            // write to the underlying byte array
        }
    }
    
  5. 由于ICanvas实现不会绘制实际形状,因此您不需要ShapeBase类。但是,您需要一个模拟类来绘制图形基元(上面称为IBitmapShape)。