图像编辑工具的高级设计模式
我最近开始创建一个图像编辑工具,它将满足非常具体的需求。 这既是为了我自己的娱乐,也是为了将要使用它的人。 然而,我很早就遇到了一些架构上的障碍。
与任何图像编辑器一样,用户将使用“工具”来绘制和操作图像。 我的第一次尝试包括一个简单的界面:(
public interface IDrawingTool
{
void DrawEffect( Graphics g );
// other stuff
}
我认为)这将是漂亮和干净的,并且可以轻松维护和扩展。 只需添加界面对象并在运行时调用所选界面对象的 DrawEffect 方法即可。
这种方法的问题在于不同的绘图工具不能完全遵守单一界面。 例如,钢笔工具只需知道要绘制的点即可工作。 然而,矩形需要单击的第一个点以及当前位置。 多边形工具需要跟踪多次鼠标单击。
我很难想出一个好的方法来实现这个。 我现在能想到的最好的方法是涉及一个 switch 语句和每个工具的 case,这意味着绘图逻辑将位于 Canvas 类中,而不是由 Tool 类型对象封装。 因为这是练习,我想以正确的方式做到这一点。 感谢您提前提供的任何帮助。
I have recently begin creating an image editing tool which will cater to a very specific need. This is as much for the people who are going to use it as it is for my own entertainment. however, I have hit a bit of an architectural snag early on.
Like any image editor, the user will use 'tools' to draw on and manipulate an image. My first attempt at this consisted of a simple interface:
public interface IDrawingTool
{
void DrawEffect( Graphics g );
// other stuff
}
This (I thought) would be nice and clean and would allow for easy maintenance and extension. Just add the interface objects in and call the DrawEffect method of the selected one at runtime.
The problem with this approach is that different drawing tools do not cleanly adhere to a single interface. For example, a pen tool need only know the point to draw at in order to work. The rectangle however needs the first point clicked, as well as the current position. The polygon tool needs to keep track of multiple mouse clicks.
I am having trouble thinking of a nice way to implement this. The best method that I can think of now would involve a switch statement and a case for each tool, which would mean that the drawing logic would be in the Canvas class, not encapsulated by Tool type objects. because this is practice, I would like to do this the right way. Thanks for any help in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
好的,经验法则:如果您在代码草图中看到
switch
语句,则表明您需要使用多态性。 因此,在这种情况下,您希望能够进行各种操作,并且您发现自己需要一个switch
,因此您应该思考“如何使用多态性来实现这一点?”现在,看一下命令模式,其中您的对象是动词而不是名词。 每个命令都实现一个
doThis()
方法; 当您构造对象时,您就确定了命令将执行的操作。现在,考虑一下如果您想实现撤消,您会做什么?
更新
好吧,让我们再扩展一下。 使用此模式的目的是确保客户端不需要了解那么多,除非您正在进行原始构造。 因此,对于这个例子,让我们考虑绘制一个矩形。 当您选择“矩形”工具时,您将在按钮单击事件处理程序上有一些代码(顺便说一句,这都是伪代码)
因此,现在当您选择了矩形时,您将一个 DrawRectangle 对象添加到命令列表结构中。 一段时间后,你浏览一下清单
,这些事情就完成了。 现在很明显,您可以通过向 Command 添加“undoThis”方法来实现撤消。 当您创建命令时,您必须构建代码,以便对象知道如何撤消自身。 那么撤消意味着只是将最后一个 Command 对象从列表中取出并执行其 undoThis 方法。
Okay, rule of thumb: if you see a
switch
statement in your code sketch, it's a sign you need to use polymorphism instead. So, in this case, you want to be able to have various operations, and you're finding yourself wanting aswitch
, so you should think "how can I make this something using polymorphism?"Now,have a look at the Command pattern, where your objects are verbs instead of nouns. Each Command implements a
doThis()
method; when you construct the object, you establish what the command wil do.Now, consider what you would do if you wanted to implement undo?
Update
Okay, let's extend this a bit more. The point of using this pattern is to make sure the client doesn't need to know all that much, except when you're doing the original construction. So for this example, let's think about drawing a rectangle. When you picka Rectangle tool, you're going to have some code on the button-click event handler (this is all pseudocode btw)
So now when you've picked out the rectangle, you add a DrawRectangle object to the command list structure. Sometime later, you run through the list
and these things get done. It should be obvious now that you would implement undo by adding an "undoThis" method to Command. When you create a command, you have to buuild code so that the object will know how to undo itself. Then undo means just taking the last Command object off the list and doing its undoThis method.
您将界面设计得复杂一点怎么样? 让我们从一些代码开始,然后我将解释它应该如何工作。
这个想法是,一旦用户开始使用特定的实现,就将用户输入传递给工具。 这样,您可以使用相同的界面创建许多不同的绘图工具。 例如,一个简单的 PointDrawingTool 将仅实现 mouseClick 事件以在画布上放置一个点。 PolygonDrawingTool 还将实现 keyUp 事件,以便在按下特定键(即退出键)时可以停止绘制线条。
一个特殊情况是 drop 方法。 它将被称为“删除”当前选定的工具。 如果从工具栏或类似工具中选择另一个实现,就会发生这种情况。
您还可以将此定义与命令模式结合起来。 在这种情况下,AbstractDrawingTool 的实现将负责创建 Command 接口的实例,并且可能在操作完成后将它们放置在堆栈上(即在画布上放置一个点)。
How about you design your interface a bit complexer? Lets start off with some code and afterwards I'll explain how it's supposed to work.
The idea is to hand down the user input to the tool, once the user starts working with a specific implementation. This way, you could create lots of different drawing tools all using the same interface. A simple PointDrawingTool for example would only implement the mouseClick event to place a point on the canvas. A PolygonDrawingTool would also implement the keyUp event so that it could stop the drawing the lines when a specific key (i.e. the escape key) was pressed.
A special case is the drop method. It would be called, to "drop" the currently selected tool. This would happen, if another implementation was selected from a toolbar or similar.
You could also combine this definition with the command pattern. In this case, an implementation of the AbstractDrawingTool would be responsible for creating instances of the Command interface and perhaps place them on the stack once an operation is finished (i.e. placing a point on the canvas).
在尝试重新设计映射软件以支持 GDI+ 和 Cairo 图形库时,我遇到了类似的问题。 我通过将绘图界面减少为一些常见操作/基元来解决这个问题,请参阅下面的代码。
之后,您想要绘制的“效果”就是命令(就像查理所说的那样)。 他们使用
IPainter
接口来绘图。 这种方法的好处是效果与 GDI+ 等具体绘图引擎完全解耦。 这对我来说很方便,因为我可以通过切换到 Cairo 引擎将我的绘图导出到 SVG。当然,如果您需要一些额外的图形操作,则必须使用它扩展
IPainter
接口,但基本原理保持不变。 在这里查看更多相关信息:http://igorbrejc.net/development/c/welcome-飞往开罗I faced a similar problem when trying to redesign my mapping SW to support both GDI+ and Cairo graphics libraries. I solved it by reducing the drawing interface to some common operations/primitives, see the code below.
After this, the "effects" you want to draw are Commands (like Charlie says). They use the
IPainter
interface to draw. The nice thing about this approach is that the effects are completely decoupled from a concrete drawing engine like GDI+. This comes handy to me, since I can then get my drawing exported to SVG by switching to Cairo engine.Of course, if you need some additional graphics operations, you would have to extend the
IPainter
interface with it, but the basic philosophy stays the same. See more about this here: http://igorbrejc.net/development/c/welcome-to-cairo您的具体问题已通过 Smalltalk-80 HotDraw 应用程序中引入的
editor
模式进行了描述和解决,该模式在 Kent Beck 和 Ralph Johnson 的论文中进行了描述“模式生成架构”。 原始源代码可在此处获取,进一步的改进是这里。该应用程序后来被移植到 Java,名为 JHotDraw,并由 Dirk Riehle 在章节中进行了描述他的论文“框架设计一种角色建模方法”的第 8 部分。
Objective-J 中还有一个名为 cupDraw 的实现此处。
Your exact problem is described and solved with the
editor
pattern introduced in the Smalltalk-80 HotDraw application, described in the paper by Kent Beck and Ralph Johnson “Patterns Generate Architectures”. The original source code is available here, further improvements are here.The app was later ported to Java as JHotDraw and described by Dirk Riehle in chapter 8 of his dissertation "Framework Design A Role Modeling Approach".
There's also an implementation in Objective-J called cupDraw here.