在绘图应用程序中分离模型和视图/控制器

时间:2022-11-21 10:32:06

I am working on a vector drawing application (in java) and I am struggling with the the separation between my model classes and the view/controller classes.

我正在使用矢量绘图应用程序(在java中),我正在努力解决我的模型类和视图/控制器类之间的分离。

Some background:

You can draw different shapes:
rectangles, lines and pie segments

您可以绘制不同的形状:矩形,线条和饼图段

There are 4 tools to manipulate the shapes on the canvas:
scale-tool, move-tool, rotate-tool and morph-tool

有4种工具可以在画布上操纵形状:缩放工具,移动工具,旋转工具和变形工具

For this question the morph tool is the most interesting one: It lets you change a shape by dragging one of it's points and adjusting the other properties as shown in this graphic:

对于这个问题,变形工具是最有趣的一个:它允许您通过拖动其中一个点并调整其他属性来更改形状,如下图所示:

在绘图应用程序中分离模型和视图/控制器

These Transformation rules are different for each shape and I think they are part of the model's business logic but in a way they need to be exposed to the view/controller (the tool classes) so they can apply the correct one.

这些转换规则对于每个形状都是不同的,我认为它们是模型业务逻辑的一部分,但在某种程度上它们需要暴露给视图/控制器(工具类),以便它们可以应用正确的形状。

Additionally the shapes are internally represented via different values: - The rectangle is stored as center, width, height, rotation - The line is stored as start and end point - The pie segment is stored as center, radius, angle1, angle2

此外,形状通过不同的值在内部表示: - 矩形存储为中心,宽度,高度,旋转 - 该行存储为起点和终点 - 饼图段存储为中心,半径,角度1,角度2

I plan to add more shapes in the future, like stars, speech bubbles or arrows, each with their own control points.

我计划在未来添加更多形状,如星星,气泡或箭头,每个都有自己的控制点。

I also plan to add more tools in the future, like rotating or scaling groups of shapes.

我还计划在未来添加更多工具,例如旋转或缩放形状组。

The control points for each tool are different. Eg when using the scale tool, you can not grab the center point, but each scaling control points needs to be associated with one pivot point (or multiple to let the user choose from).

每个工具的控制点都不同。例如,使用缩放工具时,您无法抓取中心点,但每个缩放控制点需要与一个轴点(或多个让用户选择)相关联。

For the simple shapes like rectangle, line and pie the control points are the same for each instance of the class but futures shapes like a bezier path or a star (with configurable spike count) would have a different amount of control points per instance.

对于像矩形,直线和饼图这样的简单形状,控制点对于类的每个实例都是相同的,但是期货形状如贝塞尔曲线路径或星形(具有可配置的尖峰数)将为每个实例提供不同数量的控制点。

So the question is whats a good way to model and implement these control points?

那么问题是什么是建模和实施这些控制点的好方法?

As they are slightly different for each tool and carry some tool/controller specific data they belong to the tool/controller in some way. But as they are also specific for each type of shape and carry very important domain logic they also belong to the model.

由于它们对于每个工具略有不同并且携带一些工具/控制器特定数据,因此它们以某种方式属于工具/控制器。但由于它们也特定于每种类型的形状并且具有非常重要的域逻辑,因此它们也属于该模型。

I would like to avoid the combinatoric explosion of adding a special type of control point for each combination of tool/shape whenever one tool or shape is added.

我想避免在添加一个工具或形状时为每个工具/形状组合添加特殊类型的控制点的组合爆炸。


Update: To give another example: In the future it may occur that I have a idea for a new shape I want to support: the arc. It is similar to the pie segment but looks a bit different and behaves completely different when dragging the control points.

更新:再举一个例子:将来我可能会想到一个我想要支持的新形状:弧形。它类似于饼段,但看起来有点不同,拖动控制点时表现完全不同。

To implement this I would like to be able to just create a ArcShape class implementing my Shape interface and be done.

为了实现这一点,我希望能够创建一个实现我的Shape接口的ArcShape类并完成。

在绘图应用程序中分离模型和视图/控制器

4 个解决方案

#1


Basic Considerations

First of all let us make some definitions for simplicity.

首先,让我们为简单起见做一些定义。

Entity is a Domain Model object, which defines all the structure and behaviour, i.e. logic. EntityUI is the graphical control that represents the Entity in the UI.

实体是一个域模型对象,它定义了所有结构和行为,即逻辑。 EntityUI是表示UI中实体的图形控件。

So basically, for Shape classes I think ShapeUI must be pretty much aware of the structure of the Shape. The structure is mainly composed of the control points I guess. In other words, having all the information about the control points (maybe vectors in future), the ShapeUI will be able to draw itself on the UI.

所以基本上,对于Shape类,我认为ShapeUI必须非常了解Shape的结构。结构主要由我猜测的控制点组成。换句话说,拥有关于控制点的所有信息(可能是将来的矢量),ShapeUI将能够在UI上绘制自己。

Initial Suggestions

What I would suggest for the Shape classes, is that the Shape class defines all the behaviour. The ShapeUI class will be aware of Shape class and keep a reference to one it is representing, by which it will have access to the control points, as well as be able to manipulate them, e.g. set their locations. The Observer pattern simply asks to be used in this context. Particularly, the Shape class may implement the Observable and the ShapeUI will implement Observer and subscribe to the corresponding Shape object.

我建议Shape类,是Shape类定义所有行为。 ShapeUI类将知道Shape类并保持对它所代表的类的引用,通过它可以访问控制点,并且能够操纵它们,例如,设置他们的位置。观察者模式只是要求在此上下文中使用。特别是,Shape类可以实现Observable,而ShapeUI将实现Observer并订阅相应的Shape对象。

So basically what will happen in this case, the ShapeUI object will handle all UI operations, and will be responsible for updating the Shape parameters, e.g. control point locations. Afterwards, as soon as a location update occurs, the Shape object executes its logic upon the state change and then blindly (without being aware of ShapeUI) notifies the ShapeUI about the updated state. So correspondingly the ShapeUI will draw the new state. Here you will gain low-coupled model and view.

所以基本上在这种情况下会发生什么,ShapeUI对象将处理所有UI操作,并将负责更新Shape参数,例如,控制点位置。之后,一旦发生位置更新,Shape对象在状态改变时执行其逻辑,然后盲目地(不知道ShapeUI)通知ShapeUI有关更新的状态。因此相应地,ShapeUI将绘制新状态。在这里,您将获得低耦合模型和视图。

As for the Tools, my own opinion is that each Tool must know how to manipulate each type of Shape, i.e. the per shape manipulation logic must be implemented inside the Tool class. For decoupling the view and the model, it is pretty much the same as for the Shape. The ToolUI class handles where the cursor is clicked, what ShapeUI was it clicked on, what control point was it clicked on, etc. By obtaining this information, ToolUI will pass it to the appropriate Tool object, which will then apply the logic based on the received parameters.

至于工具,我自己的观点是每个工具必须知道如何操纵每种类型的Shape,即每个形状操作逻辑必须在Tool类中实现。对于视图和模型的分离,它几乎与Shape相同。 ToolUI类处理点击光标的位置,点击的ShapeUI,点击的控制点等等。通过获取此信息,ToolUI会将其传递给相应的Tool对象,然后根据该对象应用逻辑。收到的参数。

Handling Different Shape Types

处理不同的形状类型

Now when it comes to the Tool treating different Shapes in their own ways, I think the Abstract Factory pattern steps in. Each tool will implement an Abstract Factory where we will provide manipulation implementations for each type of Shape.

现在,当涉及工具以自己的方式处理不同的形状时,我认为抽象工厂模式介入。每个工具将实现一个抽象工厂,我们将为每种类型的Shape提供操作实现。

Summary

Based on what I suggested, here is the draft Domain Model:

根据我的建议,这里是领域模型草案:

在绘图应用程序中分离模型和视图/控制器

To get the whole idea out of my suggestions, I am also posting the Sequence Diagram for a specific Use Case:

为了从我的建议中得到完整的想法,我还发布了特定用例的序列图:

Using ToolUI the user clicks on ShapeUI's ControlPointUI

使用ToolUI,用户单击ShapeUI的ControlPointUI

在绘图应用程序中分离模型和视图/控制器

#2


If I correctly understand, here what we have :

如果我正确理解,这里有我们所拥有的:

  • different figures which all have control points
  • 不同的数字都有控制点

  • the UI allows to draw figures and drag the control points
  • UI允许绘制图形并拖动控制点

My advice here is to say that what characterizes a figure goes in Model layer, and that the UI part go in the View/Controller one.

我在这里的建议是说,一个图形的特征​​在于模型层,并且UI部分在视图/控制器中。

One step further for the model :

该模型更进一步:

  • figures should implement an interface :

    数字应该实现一个接口:

    public interface Figure {
        List<Segment> segments();
        List<ControlPoint> controlPoints();
        void drag(ControlPoint point, Pos newPos);
        void rotate(ControlPoint point, Pos newPos, Pos center); // or rotate(Pos center, double angle);
    }
    
  • Segment is an abstraction that can represent a line segment, an arc or a Bezier curve

    段是一种抽象,可以表示线段,弧或贝塞尔曲线

  • a ControlPoint has a sense for a Figure implementation and has a current Pos

    ControlPoint具有图实现的意义并且具有当前Pos

    public interface ControlPoint{
        Figure parent();
        void drag(Pos newPos); // unsure if it must exist in both interfaces
        Pos position();
        ToolHint toolHint();
    }
    
  • the ToolHint should be a indication for which tool can use the control point and for which usage - per your requirement, the rotate tool should considere the center as special.

    ToolHint应该指示哪个工具可以使用控制点以及哪个用途 - 根据您的要求,旋转工具应该将中心视为特殊。

  • a Pos represents x,y coordinates
  • Pos表示x,y坐标

That way the UI does not have to know anything about what the figures actually are.

这样,UI就不必了解数字实际上是什么。

To draw a Figure, the UI gets the list of Segment and simply draw independely each Segment, and add a mark at each control points. When a control point is dragged, the UI gives new position to the Figure and redraws it. It should be able to erase a Figure before redrawing it in its new position, or alternatively (simpler but slower) it could redraw all at each operation

为了绘制图形,UI获取Segment列表并简单地单独绘制每个Segment,并在每个控制点添加标记。拖动控制点时,UI会给图形赋予新位置并重新绘制它。它应该能够在将其重新绘制到新位置之前擦除图形,或者(更简单但更慢)它可以在每次操作时重绘所有图形

With the drag method, we only can drag a simple control point on a single shape. It is easily extensible, but extensions will be have to be added for each tool. For example, I have allready added the rotate method that allows to rotate a shape by moving one control point with a define center. You could also add a scale method.

使用拖动方法,我们只能在单个形状上拖动一个简单的控制点。它易于扩展,但必须为每个工具添加扩展。例如,我已经添加了旋转方法,该方法允许通过使用定义中心移动一个控制点来旋转形状。您还可以添加缩放方法。

Multiple shapes

If you want to apply a transformation to a set of shapes, you could use a subclass of the rectangle. You build a rectangle with sides parallel to coordinate axes that contain all shapes. I recommend to add a method in Figure that returns an (reasonnably small) enclosing rectangle with sides parralel to coordinate axes to ease the creation of the multi shapes rectangle. Then, when you apply a transformation to the englobing rectangle, it simply reports the transformation to all of its elements. But we come here to transformations that cannot be done by dragging control points, because the point that is dragged does not belong to the internal shape.

如果要将变换应​​用于一组形状,可以使用矩形的子类。您构建一个矩形,其边平行于包含所有形状的坐标轴。我建议在图中添加一个方法,该方法返回一个(相当小的)带有侧面parralel的封闭矩形来协调轴,以便于创建多形状矩形。然后,当您将变换应用于englobing矩形时,它只会报告对其所有元素的变换。但是我们来到这里是通过拖动控制点无法完成的转​​换,因为拖动的点不属于内部形状。

Internal transformations

Until now, I have only dealt with the interface between the UI and the model. But with the multi shapes, we saw that we need to apply arbitrary affine transformations (translation of a point of an englobing rectangle or scaling of the englobing rectangle) or rotation. If we choose to implement rotation as rotate(center, angle) the rotation of an included shape is already done. So we simply have to implement the affine transformation

到目前为止,我只处理了UI和模型之间的接口。但是对于多个形状,我们看到我们需要应用任意仿射变换(平移矩形的点的平移或者旋转矩形的缩放)或旋转。如果我们选择将旋转实现为旋转(中心,角度),则已完成包含形状的旋转。所以我们只需要实现仿射变换

class AffineTransform {
    private double a, b, c, d;
    /* creators, getters, setters omitted, but we probably need to implement
       one creator by use case */

    Pos transform(Pos pos) {
         Pos newpos;
         newpos.x = a * pos.x + b;
         newpos.y = c * pos.y + d;
         return newpos;
    }
}

That way, to apply an affine transformation to a Figure, we just have to implement transform(AffineTransform txform) in a way that simply apply the all the points defining the structure.

这样,为了将仿射变换应用于图,我们只需要以简单地应用定义结构的所有点的方式实现变换(AffineTransform txform)。

Figure is now :

图现在是:

    public interface Figure {
        List<Segment> segments();
        List<ControlPoint> controlPoints();
        void drag(ControlPoint point, Pos newPos);
        void rotate(Pos center, double angle);
        // void rotate(ControlPoint point, double angle); if ControlPoint does not implement Pos
        Figure getEnclosingRectangle();
        void transform(AffineTransform txform);
    }

Summary :

It is just the general ideas, but it should be the basics to allows tools to act on arbitrary shapes, with a low coupling

它只是一般的想法,但它应该是允许工具作用于任意形状,低耦合的基础

#3


I'd not expect a good design to emerge without getting down on coding and hitting actual problems. But if you don't know where to start here is my proposal.

我不希望在没有编写代码并遇到实际问题的情况下出现好的设计。但如果你不知道从哪里开始是我的建议。

inteface Shape {
   List<Point> getPoints(ToolsEnum strategy); // you could use factory here
}

interface Point {
    Shape rotate(int degrees); // or double radians if you like
    Shape translate(int x, int y);
    void setStrategy(TranslationStrategy strategy);
}

interface Origin extends Point {}

interface SidePoint extends Point {}

interface CornerPoint extends Point {}

Then implement Point interface extensions as inner classes in each concrete shape.

然后将Point接口扩展实现为每个具体形状的内部类。

I assume next user flow:

我假设下一个用户流程:

  1. Tool selected - currentTool inside controller set to appropriate value from enum.
  2. 选择工具 - 控制器内的currentTool从枚举设置为适当的值。

  3. User selects/ hower a shape - getPoints called, depending on tool some type of points may be filtered out. E.g. only corner points returned for morph operations. Inject appropriate strategies for exposed points.
  4. 用户选择/增加一个形状 - 调用getPoints,取决于工具,某些类型的点可能被过滤掉。例如。仅为变形操作返回角点。为暴露点注入适当的策略。

  5. As user drags the point - translate called and you have new shape transformed with a given tool.
  6. 当用户拖动点 - 被调用的转换,并且您使用给定工具转换了新形状。

#4


In principle, it is a good idea to make the model match the drawing interface. So, for example, in Java Swing, rectangles may be drawn with the drawRect method which takes as arguments the x,y of the upper left corner, the width, and the height. So, usually you would want to model a rectangle as { x-UL, y-UL, width, height }.

原则上,使模型与绘图界面匹配是个好主意。因此,例如,在Java Swing中,可以使用drawRect方法绘制矩形,该方法将左上角的x,y,宽度和高度作为参数。因此,通常您希望将矩形建模为{x-UL,y-UL,width,height}。

For arbitrary paths, including arcs, Swing provides the GeneralPath object with has methods for working with a sequence of points connected either by lines or Quadratic/Bezier curves. To model a GeneralPath you can provide a list of points, a winding rule, and the necessary parameters of either a Quadratic curve or a Bezier curve.

对于任意路径(包括弧),Swing为GeneralPath对象提供了一些方法,用于处理由线或Quadratic / Bezier曲线连接的点序列。要为GeneralPath建模,您可以提供点列表,绕组规则以及二次曲线或贝塞尔曲线的必要参数。

#1


Basic Considerations

First of all let us make some definitions for simplicity.

首先,让我们为简单起见做一些定义。

Entity is a Domain Model object, which defines all the structure and behaviour, i.e. logic. EntityUI is the graphical control that represents the Entity in the UI.

实体是一个域模型对象,它定义了所有结构和行为,即逻辑。 EntityUI是表示UI中实体的图形控件。

So basically, for Shape classes I think ShapeUI must be pretty much aware of the structure of the Shape. The structure is mainly composed of the control points I guess. In other words, having all the information about the control points (maybe vectors in future), the ShapeUI will be able to draw itself on the UI.

所以基本上,对于Shape类,我认为ShapeUI必须非常了解Shape的结构。结构主要由我猜测的控制点组成。换句话说,拥有关于控制点的所有信息(可能是将来的矢量),ShapeUI将能够在UI上绘制自己。

Initial Suggestions

What I would suggest for the Shape classes, is that the Shape class defines all the behaviour. The ShapeUI class will be aware of Shape class and keep a reference to one it is representing, by which it will have access to the control points, as well as be able to manipulate them, e.g. set their locations. The Observer pattern simply asks to be used in this context. Particularly, the Shape class may implement the Observable and the ShapeUI will implement Observer and subscribe to the corresponding Shape object.

我建议Shape类,是Shape类定义所有行为。 ShapeUI类将知道Shape类并保持对它所代表的类的引用,通过它可以访问控制点,并且能够操纵它们,例如,设置他们的位置。观察者模式只是要求在此上下文中使用。特别是,Shape类可以实现Observable,而ShapeUI将实现Observer并订阅相应的Shape对象。

So basically what will happen in this case, the ShapeUI object will handle all UI operations, and will be responsible for updating the Shape parameters, e.g. control point locations. Afterwards, as soon as a location update occurs, the Shape object executes its logic upon the state change and then blindly (without being aware of ShapeUI) notifies the ShapeUI about the updated state. So correspondingly the ShapeUI will draw the new state. Here you will gain low-coupled model and view.

所以基本上在这种情况下会发生什么,ShapeUI对象将处理所有UI操作,并将负责更新Shape参数,例如,控制点位置。之后,一旦发生位置更新,Shape对象在状态改变时执行其逻辑,然后盲目地(不知道ShapeUI)通知ShapeUI有关更新的状态。因此相应地,ShapeUI将绘制新状态。在这里,您将获得低耦合模型和视图。

As for the Tools, my own opinion is that each Tool must know how to manipulate each type of Shape, i.e. the per shape manipulation logic must be implemented inside the Tool class. For decoupling the view and the model, it is pretty much the same as for the Shape. The ToolUI class handles where the cursor is clicked, what ShapeUI was it clicked on, what control point was it clicked on, etc. By obtaining this information, ToolUI will pass it to the appropriate Tool object, which will then apply the logic based on the received parameters.

至于工具,我自己的观点是每个工具必须知道如何操纵每种类型的Shape,即每个形状操作逻辑必须在Tool类中实现。对于视图和模型的分离,它几乎与Shape相同。 ToolUI类处理点击光标的位置,点击的ShapeUI,点击的控制点等等。通过获取此信息,ToolUI会将其传递给相应的Tool对象,然后根据该对象应用逻辑。收到的参数。

Handling Different Shape Types

处理不同的形状类型

Now when it comes to the Tool treating different Shapes in their own ways, I think the Abstract Factory pattern steps in. Each tool will implement an Abstract Factory where we will provide manipulation implementations for each type of Shape.

现在,当涉及工具以自己的方式处理不同的形状时,我认为抽象工厂模式介入。每个工具将实现一个抽象工厂,我们将为每种类型的Shape提供操作实现。

Summary

Based on what I suggested, here is the draft Domain Model:

根据我的建议,这里是领域模型草案:

在绘图应用程序中分离模型和视图/控制器

To get the whole idea out of my suggestions, I am also posting the Sequence Diagram for a specific Use Case:

为了从我的建议中得到完整的想法,我还发布了特定用例的序列图:

Using ToolUI the user clicks on ShapeUI's ControlPointUI

使用ToolUI,用户单击ShapeUI的ControlPointUI

在绘图应用程序中分离模型和视图/控制器

#2


If I correctly understand, here what we have :

如果我正确理解,这里有我们所拥有的:

  • different figures which all have control points
  • 不同的数字都有控制点

  • the UI allows to draw figures and drag the control points
  • UI允许绘制图形并拖动控制点

My advice here is to say that what characterizes a figure goes in Model layer, and that the UI part go in the View/Controller one.

我在这里的建议是说,一个图形的特征​​在于模型层,并且UI部分在视图/控制器中。

One step further for the model :

该模型更进一步:

  • figures should implement an interface :

    数字应该实现一个接口:

    public interface Figure {
        List<Segment> segments();
        List<ControlPoint> controlPoints();
        void drag(ControlPoint point, Pos newPos);
        void rotate(ControlPoint point, Pos newPos, Pos center); // or rotate(Pos center, double angle);
    }
    
  • Segment is an abstraction that can represent a line segment, an arc or a Bezier curve

    段是一种抽象,可以表示线段,弧或贝塞尔曲线

  • a ControlPoint has a sense for a Figure implementation and has a current Pos

    ControlPoint具有图实现的意义并且具有当前Pos

    public interface ControlPoint{
        Figure parent();
        void drag(Pos newPos); // unsure if it must exist in both interfaces
        Pos position();
        ToolHint toolHint();
    }
    
  • the ToolHint should be a indication for which tool can use the control point and for which usage - per your requirement, the rotate tool should considere the center as special.

    ToolHint应该指示哪个工具可以使用控制点以及哪个用途 - 根据您的要求,旋转工具应该将中心视为特殊。

  • a Pos represents x,y coordinates
  • Pos表示x,y坐标

That way the UI does not have to know anything about what the figures actually are.

这样,UI就不必了解数字实际上是什么。

To draw a Figure, the UI gets the list of Segment and simply draw independely each Segment, and add a mark at each control points. When a control point is dragged, the UI gives new position to the Figure and redraws it. It should be able to erase a Figure before redrawing it in its new position, or alternatively (simpler but slower) it could redraw all at each operation

为了绘制图形,UI获取Segment列表并简单地单独绘制每个Segment,并在每个控制点添加标记。拖动控制点时,UI会给图形赋予新位置并重新绘制它。它应该能够在将其重新绘制到新位置之前擦除图形,或者(更简单但更慢)它可以在每次操作时重绘所有图形

With the drag method, we only can drag a simple control point on a single shape. It is easily extensible, but extensions will be have to be added for each tool. For example, I have allready added the rotate method that allows to rotate a shape by moving one control point with a define center. You could also add a scale method.

使用拖动方法,我们只能在单个形状上拖动一个简单的控制点。它易于扩展,但必须为每个工具添加扩展。例如,我已经添加了旋转方法,该方法允许通过使用定义中心移动一个控制点来旋转形状。您还可以添加缩放方法。

Multiple shapes

If you want to apply a transformation to a set of shapes, you could use a subclass of the rectangle. You build a rectangle with sides parallel to coordinate axes that contain all shapes. I recommend to add a method in Figure that returns an (reasonnably small) enclosing rectangle with sides parralel to coordinate axes to ease the creation of the multi shapes rectangle. Then, when you apply a transformation to the englobing rectangle, it simply reports the transformation to all of its elements. But we come here to transformations that cannot be done by dragging control points, because the point that is dragged does not belong to the internal shape.

如果要将变换应​​用于一组形状,可以使用矩形的子类。您构建一个矩形,其边平行于包含所有形状的坐标轴。我建议在图中添加一个方法,该方法返回一个(相当小的)带有侧面parralel的封闭矩形来协调轴,以便于创建多形状矩形。然后,当您将变换应用于englobing矩形时,它只会报告对其所有元素的变换。但是我们来到这里是通过拖动控制点无法完成的转​​换,因为拖动的点不属于内部形状。

Internal transformations

Until now, I have only dealt with the interface between the UI and the model. But with the multi shapes, we saw that we need to apply arbitrary affine transformations (translation of a point of an englobing rectangle or scaling of the englobing rectangle) or rotation. If we choose to implement rotation as rotate(center, angle) the rotation of an included shape is already done. So we simply have to implement the affine transformation

到目前为止,我只处理了UI和模型之间的接口。但是对于多个形状,我们看到我们需要应用任意仿射变换(平移矩形的点的平移或者旋转矩形的缩放)或旋转。如果我们选择将旋转实现为旋转(中心,角度),则已完成包含形状的旋转。所以我们只需要实现仿射变换

class AffineTransform {
    private double a, b, c, d;
    /* creators, getters, setters omitted, but we probably need to implement
       one creator by use case */

    Pos transform(Pos pos) {
         Pos newpos;
         newpos.x = a * pos.x + b;
         newpos.y = c * pos.y + d;
         return newpos;
    }
}

That way, to apply an affine transformation to a Figure, we just have to implement transform(AffineTransform txform) in a way that simply apply the all the points defining the structure.

这样,为了将仿射变换应用于图,我们只需要以简单地应用定义结构的所有点的方式实现变换(AffineTransform txform)。

Figure is now :

图现在是:

    public interface Figure {
        List<Segment> segments();
        List<ControlPoint> controlPoints();
        void drag(ControlPoint point, Pos newPos);
        void rotate(Pos center, double angle);
        // void rotate(ControlPoint point, double angle); if ControlPoint does not implement Pos
        Figure getEnclosingRectangle();
        void transform(AffineTransform txform);
    }

Summary :

It is just the general ideas, but it should be the basics to allows tools to act on arbitrary shapes, with a low coupling

它只是一般的想法,但它应该是允许工具作用于任意形状,低耦合的基础

#3


I'd not expect a good design to emerge without getting down on coding and hitting actual problems. But if you don't know where to start here is my proposal.

我不希望在没有编写代码并遇到实际问题的情况下出现好的设计。但如果你不知道从哪里开始是我的建议。

inteface Shape {
   List<Point> getPoints(ToolsEnum strategy); // you could use factory here
}

interface Point {
    Shape rotate(int degrees); // or double radians if you like
    Shape translate(int x, int y);
    void setStrategy(TranslationStrategy strategy);
}

interface Origin extends Point {}

interface SidePoint extends Point {}

interface CornerPoint extends Point {}

Then implement Point interface extensions as inner classes in each concrete shape.

然后将Point接口扩展实现为每个具体形状的内部类。

I assume next user flow:

我假设下一个用户流程:

  1. Tool selected - currentTool inside controller set to appropriate value from enum.
  2. 选择工具 - 控制器内的currentTool从枚举设置为适当的值。

  3. User selects/ hower a shape - getPoints called, depending on tool some type of points may be filtered out. E.g. only corner points returned for morph operations. Inject appropriate strategies for exposed points.
  4. 用户选择/增加一个形状 - 调用getPoints,取决于工具,某些类型的点可能被过滤掉。例如。仅为变形操作返回角点。为暴露点注入适当的策略。

  5. As user drags the point - translate called and you have new shape transformed with a given tool.
  6. 当用户拖动点 - 被调用的转换,并且您使用给定工具转换了新形状。

#4


In principle, it is a good idea to make the model match the drawing interface. So, for example, in Java Swing, rectangles may be drawn with the drawRect method which takes as arguments the x,y of the upper left corner, the width, and the height. So, usually you would want to model a rectangle as { x-UL, y-UL, width, height }.

原则上,使模型与绘图界面匹配是个好主意。因此,例如,在Java Swing中,可以使用drawRect方法绘制矩形,该方法将左上角的x,y,宽度和高度作为参数。因此,通常您希望将矩形建模为{x-UL,y-UL,width,height}。

For arbitrary paths, including arcs, Swing provides the GeneralPath object with has methods for working with a sequence of points connected either by lines or Quadratic/Bezier curves. To model a GeneralPath you can provide a list of points, a winding rule, and the necessary parameters of either a Quadratic curve or a Bezier curve.

对于任意路径(包括弧),Swing为GeneralPath对象提供了一些方法,用于处理由线或Quadratic / Bezier曲线连接的点序列。要为GeneralPath建模,您可以提供点列表,绕组规则以及二次曲线或贝塞尔曲线的必要参数。