编辑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:
AbstractPoint
和ScreenPoint
不同类的原因是语义上的。 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#)。
但是,在特殊情况下,当您确切地知道自己在做什么时,特别是如果您要实现的目的足够简单时,就可以打破一般规则。
您的情况似乎很简单。将AbstractPoint
和ScreenPoint
作为不同的类是正确的。但实际上,它们是“一起工作的”:所有AbstractPoint
都应该能够转换为ScreenPoint
(这也许是AbstractPoint
合同中最重要的功能?)。由于一个不能没有另一个而存在,因此AbstractPoint
知道ScreenPoint
并没有错。
更新资料
在不同的设计中:创建一个名为CanonicalPoint
的接口。AbstractPoint
具有一个称为ToCanonicalPoint
的方法,该方法返回CanonicalPoint
。AbstractPoint
的所有派生类都必须实现它并返回CanonicalPoint
。ScreenPoint
是AbstractPoint
的派生类,它实现了CanonicalPoint
接口。
您甚至可以有多个实现CanonicalPoint
的派生类。
注意:如果AbstractPoint
和CanonicalPoint
具有相同的方法,则两者都可以实现另一个方法
称为Pointable
的接口,它声明所有这些方法。