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

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

一些背景:

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

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

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

The different options of manipulating a shape

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

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

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

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

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

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

那么问题是什么来建模和实现这些控制点呢?

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

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


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

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

The behaviour of the arc shape


答案 1

基本注意事项

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

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

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

初步建议

我对类的建议是,类定义了所有的行为。该类将知道类并保留对它所表示的类的引用,通过该类,它将可以访问控制点,并能够操纵它们,例如设置它们的位置。该模式只是要求在此上下文中使用。具体地,该类可以实现和 will 实现并订阅相应的对象。ShapeShapeShapeUIShapeObserverShapeObservableShapeUIObserverShape

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

至于,我自己的观点是,每个人都必须知道如何操纵每种类型的,即每形状操纵逻辑必须在类内部实现。对于解耦视图和模型,它与 .该类处理光标单击的位置、单击的内容、单击的控制点等。通过获取此信息,会将其传递给相应的对象,然后该对象将基于接收的参数应用逻辑。ToolsToolShapeToolShapeToolUIShapeUIToolUITool

处理不同的形状类型

现在,当谈到以自己的方式对待不同的人时,我认为这种模式会介入。每个工具都将实现一个 where,我们将为每种类型的 .ToolShapeAbstract FactoryAbstract FactoryShape

总结

根据我的建议,以下是领域模型草案:

Domain Model

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

使用 ToolUI,用户点击 ShapeUIControlPointUI

enter image description here


答案 2

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

  • 不同的数字,都有控制点
  • UI允许绘制图形并拖动控制点

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

该模型更进一步:

  • 数字应该实现一个接口:

    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是一个抽象,可以表示线段、弧或贝塞尔曲线

  • a 对实现有意义,并具有当前ControlPointFigurePos

    public interface ControlPoint{
        Figure parent();
        void drag(Pos newPos); // unsure if it must exist in both interfaces
        Pos position();
        ToolHint toolHint();
    }
    
  • 应该指示哪个工具可以使用控制点以及用于哪种用途 - 根据您的要求,旋转工具应将中心视为特殊。ToolHint

  • a 表示 x,y 坐标Pos

这样,UI就不必知道数字的实际情况。

对于 ,UI 获取列表,并简单地独立绘制每个段,并在每个控制点添加一个标记。拖动控制点时,UI 会为 新位置并重新绘制它。它应该能够在将其重新绘制到新位置之前擦除,或者(更简单但更慢)它可以在每次操作时全部重绘drawFigureSegmentFigureFigure

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

多种形状

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

内部转型

到目前为止,我只处理过UI和模型之间的接口。但是对于多形状,我们看到我们需要应用任意仿射变换(平移凸起矩形的点或凸起矩形的缩放)或旋转。如果我们选择实现旋转,因为包含的形状的旋转已经完成。因此,我们只需要实现仿射变换rotate(center, angle)

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

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

现在的数字是:

    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);
    }

总结:

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