如果我理解正确,这里是我们所拥有的:
- 不同的人物都有控制点
- 用户界面允许绘制图形并拖动控制点
我的建议是,图形的特征放在模型层,而 UI 部分放在视图/控制器层。
模型更进一步:
-
figures 应该实现一个接口:
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是一种抽象,可以表示线段、圆弧或贝塞尔曲线
-
ControlPoint 对Figure 实现具有意义,并且具有当前的Pos
public interface ControlPoint{
Figure parent();
void drag(Pos newPos); // unsure if it must exist in both interfaces
Pos position();
ToolHint toolHint();
}
ToolHint 应该指示哪个工具可以使用控制点以及用于哪种用途 - 根据您的要求,rotate 工具应该将中心视为特殊的。
- a
Pos 代表 x,y 坐标
这样,用户界面就不必知道这些数字实际上是什么。
对于draw a Figure,UI 获取Segment 的列表并简单地独立绘制每个Segment,并在每个控制点添加一个标记。拖动控制点时,UI 会为 Figure 提供新位置并重新绘制它。它应该能够在将Figure 重绘到新位置之前将其擦除,或者(更简单但更慢)它可以在每次操作时全部重绘
使用drag 方法,我们只能在单个形状上拖动一个简单的控制点。它很容易扩展,但必须为每个工具添加扩展。例如,我已经添加了rotate 方法,该方法允许通过移动一个具有定义中心的控制点来旋转形状。您还可以添加 scale 方法。
多种形状
如果您想对一组形状应用变换,您可以使用矩形的子类。您构建一个边平行于包含所有形状的坐标轴的矩形。我建议在Figure 中添加一个方法,该方法返回一个(相当小的)封闭矩形,其边平行于坐标轴,以简化多形状矩形的创建。然后,当您将变换应用到 englobing 矩形时,它只是将变换报告给它的所有元素。但是我们来这里是通过拖动控制点无法完成的变换,因为被拖动的点不属于内部形状。
内部转换
到目前为止,我只处理了 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;
}
}
这样,要将仿射变换应用于Figure,我们只需以简单应用定义结构的所有点的方式实现transform(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);
}
总结:
这只是一般的想法,但它应该是允许工具以低耦合作用于任意形状的基础