Design patterns 图像编辑工具的高级设计模式

Design patterns 图像编辑工具的高级设计模式,design-patterns,oop,interface-design,Design Patterns,Oop,Interface Design,我最近开始创建一个图像编辑工具,它将满足一个非常特殊的需要。这是为那些将要使用它的人准备的,就像是为了我自己的娱乐一样。然而,我在早期遇到了一点架构上的障碍 与任何图像编辑器一样,用户将使用“工具”绘制和操作图像。我的第一次尝试包括一个简单的界面: public interface IDrawingTool { void DrawEffect( Graphics g ); // other stuff } 这(我认为)将是好的和干净的,并将允许易于维护和扩展。只需在中添加接口对

我最近开始创建一个图像编辑工具,它将满足一个非常特殊的需要。这是为那些将要使用它的人准备的,就像是为了我自己的娱乐一样。然而,我在早期遇到了一点架构上的障碍

与任何图像编辑器一样,用户将使用“工具”绘制和操作图像。我的第一次尝试包括一个简单的界面:

public interface IDrawingTool
{
    void DrawEffect( Graphics g );
    // other stuff
}
这(我认为)将是好的和干净的,并将允许易于维护和扩展。只需在中添加接口对象,并在运行时调用所选对象的DropeEffect方法

这种方法的问题是,不同的绘图工具不能清晰地附着到单个界面。例如,笔工具只需知道要绘制的点即可工作。但是,矩形需要单击第一个点以及当前位置。多边形工具需要跟踪多次鼠标单击


我很难想出一个好的方法来实现这一点。我现在能想到的最好的方法是使用switch语句和每个工具的case,这意味着绘图逻辑将在Canvas类中,而不是由工具类型的对象封装。因为这是实践,我想用正确的方法来做。感谢您事先提供的帮助。

好的,经验法则:如果您在代码草图中看到
switch
语句,则表示您需要使用多态性。因此,在本例中,您希望能够进行各种操作,并且您发现自己需要一个
开关
,因此您应该考虑“如何使用多态性实现这一点?”

现在,看看命令模式,其中的对象是动词而不是名词。每个命令都实现一个
doThis()
方法;构造对象时,需要确定命令将执行的操作

public interface Command {
   public void doThis(Graphics g);  // I don't promise returning 
                                    // void is the best choice
   // Would it be better to return a Graphics object?
}

public class DrawRectangle implements Command {
   public DrawRectagle( Point topLeft, Point btmRight) { // ...
   }
   public void doThis(Graphics g){ // ...
   }
}

现在,考虑如果你想实现撤销,你会做什么?

更新 好的,让我们把这个扩展一点。使用此模式的目的是确保客户不需要知道那么多,除非您正在进行原始构造。对于这个例子,让我们考虑画一个矩形。选择矩形工具时,按钮单击事件处理程序上会有一些代码(顺便说一句,这都是伪代码)

现在,当您选择了矩形后,您将向命令列表结构添加一个DrawRectangle对象。稍后,您将浏览列表

for cmd in cmdlist:
   cmd.doThis(Graphics g)

这些事情都完成了。现在很明显,您将通过向命令中添加一个“undoThis”方法来实现undo。创建命令时,必须生成代码,以便对象知道如何撤消自身。然后“撤消”意味着从列表中删除最后一个命令对象,并使用此方法执行其撤消操作。

我在尝试重新设计my以支持GDI+和Cairo图形库时遇到了类似的问题。我通过将绘图界面简化为一些常见的操作/原语解决了这个问题,请参见下面的代码

在此之后,要绘制的“效果”是命令(如Charlie所说)。他们使用
ipainer
界面进行绘制。这种方法的好处是效果与具体的绘图引擎(如GDI+)完全解耦。这对我来说很方便,因为我可以通过切换到Cairo引擎将图形导出到SVG

当然,如果需要一些额外的图形操作,则必须使用它扩展
iPainer
接口,但基本原理保持不变。请参见此处的更多信息:


你把界面设计得更复杂一点怎么样?让我们从一些代码开始,然后我将解释它应该如何工作

public class AbstractDrawingTool {

    private Graphics g;

    void AbstractDrawingTool( Graphics g ) {
        this.g = g;
    }

    void keyDown(KeyEvent e);
    void keyUp(KeyEvent e);
    void mouseMove(MouseEvent e);
    void mouseClick(MouseEvent e);
    void drop();
    // other stuff
}
想法是一旦用户开始使用特定的实现,就将用户输入传递给工具。这样,您就可以使用相同的界面创建许多不同的绘图工具。例如,一个简单的PointDrawingTool只会实现mouseClick事件来在画布上放置点。PolygondDrawingTool还将实现keyUp事件,以便在按下特定键(即escape键)时停止绘制线条

一种特殊情况是drop方法。它将被调用,以“删除”当前选定的工具。如果从工具栏或类似工具中选择了另一个实现,则会发生这种情况


您还可以将此定义与命令模式相结合。在这种情况下,AbstractDrawingTool的一个实现将负责创建命令接口的实例,并可能在操作完成后将它们放在堆栈上(即在画布上放置一个点)。

是的,当我看到switch语句写入自身时,我停止了:)。然而,我认为这并不能解决我的问题。我希望通过相同的接口使用这些工具,但是如何让它们获得完成工作所需的(不同的)信息?…例如,命令接口没有公开DrawRectangle方法,因此客户端仍然需要知道它们正在处理DrawRectangle对象,而不仅仅是命令对象。为什么?只要实现命令的对象知道该做什么,客户机为什么会关心呢?我会补充更多,这将不适合在评论。
public interface IPainter : IDisposable
{
    void BeginPainting ();
    void Clear ();
    void DrawLines (int[] coords);
    void DrawPoint (int x, int y);
    void EndPainting ();
    void PaintCurve (PaintOperation operation, int[] coords);
    void PaintPolygon (PaintOperation operation, int[] coords);
    void PaintRectangle (PaintOperation operation, int x, int y, int width, int height);
    void SetHighQualityLevel (bool highQuality);
    void SetStyle (PaintingStyle style);
}

public class PaintingStyle
{
    public PaintingStyle()
    {
    }

    public PaintingStyle(int penColor)
    {
        this.penColor = penColor;
    }

    public int PenColor
    {
        get { return penColor; }
        set { penColor = value; }
    }

    public float PenWidth
    {
        get { return penWidth; }
        set { penWidth = value; }
    }

    private int penColor;
    private float penWidth;
}

public enum PaintOperation
{
    Outline,
    Fill,
    FillAndOutline,
}
public class AbstractDrawingTool {

    private Graphics g;

    void AbstractDrawingTool( Graphics g ) {
        this.g = g;
    }

    void keyDown(KeyEvent e);
    void keyUp(KeyEvent e);
    void mouseMove(MouseEvent e);
    void mouseClick(MouseEvent e);
    void drop();
    // other stuff
}