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

时间:2009-04-05 04:29:02

标签: design-patterns oop interface-design

我最近开始创建一个图像编辑工具,以满足非常具体的需求。这对于那些打算使用它的人来说同样适合我自己的娱乐。然而,我在早期遇到了一些建筑障碍。

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

public interface IDrawingTool
{
    void DrawEffect( Graphics g );
    // other stuff
}

这(我认为)会很干净,并且易于维护和扩展。只需添加接口对象并在运行时调用所选接口对象的DrawEffect方法即可。

这种方法的问题在于不同的绘图工具不能干净地粘附到单个界面上。例如,钢笔工具只需知道要绘制的点即可工作。然而,矩形需要点击第一个点,以及当前位置。多边形工具需要跟踪多次鼠标点击。

我无法想出一个很好的方法来实现它。我现在能想到的最好的方法是涉及每个工具的switch语句和case,这意味着绘图逻辑将在Canvas类中,而不是由Tool类型对象封装。因为这是练习,我想以正确的方式做到这一点。感谢您提前提供任何帮助。

3 个答案:

答案 0 :(得分:1)

好的,经验法则:如果您在代码草图中看到switch语句,则表示您需要使用多态。所以,在这种情况下,你希望能够进行各种操作,并且你发现自己想要一个switch,所以你应该想“如何使用多态来制作这个东西?”

现在,看看Command模式,你的对象是动词而不是名词。每个Command实现一个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){ // ...
   }
}

现在,考虑一下如果要实现撤消

,您会怎么做

更新

好的,让我们再扩展一下。使用此模式的目的是确保客户端需要知道所有这些,除非您正在进行原始构造。因此,对于此示例,让我们考虑绘制一个矩形。当您选择矩形工具时,您将在按钮单击事件处理程序上有一些代码(这是所有伪代码顺便说一句)

 cmdlist = [] // empty list
 bool firstClick = true
 Point tl = br = new Point(0,0)
 onClick:
   if firstClick:
     get mouse position into tl
     firstClick = false
   else:
     get mouse position into br
     cmdlist.append(new DrawRectangle(tl, br))
     firstClick = true

现在,当您选择矩形时,将DrawRectangle对象添加到命令列表结构中。稍后,您将浏览列表

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

这些事情就完成了。现在应该很明显,您可以通过向Command添加“undoThis”方法来实现撤消。创建命令时,必须使用buuild代码,以便对象知道如何撤消自身。然后撤消意味着只需从列表中取出最后一个Command对象并执行undoThis方法。

答案 1 :(得分:1)

你的界面设计有点复杂吗?让我们从一些代码开始,然后我将解释它应该如何工作。

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事件以在画布上放置一个点。 PolygonDrawingTool还会实现keyUp事件,以便在按下特定键(即转义键)时停止绘制线条。

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

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

答案 2 :(得分:0)

在尝试重新设计我的mapping SW以支持GDI +和Cairo图形库时,我遇到了类似的问题。我通过将绘图界面减少到一些常见的操作/原语来解决它,请参阅下面的代码。

在此之后,你想要绘制的“效果”是命令(就像查理所说)。他们使用IPainter界面绘制。这种方法的好处在于效果与GDI +之类的具体绘图引擎完全分离。这对我来说很方便,因为我可以通过切换到Cairo引擎将我的绘图导出到SVG。

当然,如果您需要一些额外的图形操作,则必须使用它扩展IPainter接口,但基本原理保持不变。点击此处了解详情:http://igorbrejc.net/development/c/welcome-to-cairo

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,
}