编辑2:TL; DR:是否有一种方法不打破OO最佳实践,同时仍然满足必须将一堆相同类型的事物转换为该类型规范事物的约束?

另外,请记住,我的问题是关于一般情况,而不是具体示例。这不是作业问题。



假设您具有以下条件:


实现通用功能的抽象基类;
一个具体的派生类,作为规范的表示形式。


现在,假设您希望基类的任何继承者都可以转换为规范表示形式。一种实现方法是在基类中有一个抽象方法,该方法将返回继承者的转换作为规范派生类的实例。

但是,似乎普遍认为基类不应该知道它们的任何派生类,在一般情况下,我同意。但是,在这种情况下,这似乎是最好的解决方案,因为它可以使任何数量的派生类(通过它们我们不需要了解的实现)可以通过转换为每个派生的规范表示来互操作。类必须实施。

您会做不同的事情吗?为什么以及如何?

几何点的示例:

// an abstract point has no coordinate system information, so the values
// of X and Y are meaningless
public abstract class AbstractPoint {
    public int X;
    public int Y;

    public abstract ScreenPoint ToScreenPoint();
}

// a point in the predefined screen coordinate system; the meaning of X
// and Y is known
public class ScreenPoint : AbstractPoint {
    public ScreenPoint(int x, int y) {
        X = x;
        Y = y;
    }

    public override ScreenPoint ToScreenPoint()
        => new ScreenPoint(X, Y);
}

// there can be any number of classes like this; we don't know anything
// about their coordinate systems and we don't care as long as we can
// convert them to `ScreenPoint`s
public class ArbitraryPoint : AbstractPoint {
    private int arbitraryTransformation;

    public ArbitraryPoint(int x, int y) {
        X = x;
        Y = y;
    }

    public override ScreenPoint ToScreenPoint()
        => new ScreenPoint(X * arbitraryTransformation, Y * arbitraryTransformation);

    // (other code)
}




编辑1:AbstractPointScreenPoint不同类的原因是语义上的。 AbstractPoint没有定义的坐标系,因此AbstractPoint实例中的X和Y的值没有意义。 ScreenPoint确实具有定义的坐标系,因此ScreenPoint实例中的X和Y值具有明确定义的含义。

如果ScreenPoint是基类,则ArbitraryPoint将是ScreenPoint,事实并非如此。可以将ArbitraryPoint转换为ScreenPoint,但这并不表示它是is-a ScreenPoint

如果仍然不满意,请考虑将任意坐标系ACS1定义为相对于屏幕坐标系SCS具有动态偏移量。这意味着两个坐标系之间的映射会随时间变化,即点ACS1 (1, 1)可以在某一时刻映射到SCS (10, 10),而在另一时刻可以映射到SCS (42, 877)

最佳答案

这种设计通常是代码异味。基类不应该知道它们的派生类,因为它创建了循环依赖。循环依赖关系通常导致复杂的设计,而在这些设计上很难推理出应该做什么类。在Java中,了解它们的派生类的基类在某些罕见情况下甚至可能导致死锁(我不了解C#)。

但是,在特殊情况下,当您确切地知道自己在做什么时,特别是如果您要实现的目的足够简单时,就可以打破一般规则。

您的情况似乎很简单。将AbstractPointScreenPoint作为不同的类是正确的。但实际上,它们是“一起工作的”:所有AbstractPoint都应该能够转换为ScreenPoint(这也许是AbstractPoint合同中最重要的功能?)。由于一个不能没有另一个而存在,因此AbstractPoint知道ScreenPoint并没有错。

更新资料

在不同的设计中:创建一个名为CanonicalPoint的接口。
AbstractPoint具有一个称为ToCanonicalPoint的方法,该方法返回CanonicalPoint
AbstractPoint的所有派生类都必须实现它并返回CanonicalPoint
ScreenPointAbstractPoint的派生类,它实现了CanonicalPoint接口。
您甚至可以有多个实现CanonicalPoint的派生类。
注意:如果AbstractPointCanonicalPoint具有相同的方法,则两者都可以实现另一个方法
称为Pointable的接口,它声明所有这些方法。

10-05 22:39