面向对象设计问题:关于继承和运算符重载

7
针对一个数学包,我试图创建不同类型矩阵的类,例如典型的矩形矩阵、三角形矩阵、对角线矩阵等。自然而然的原因是为了节省特殊矩阵的高效存储和高效算法实现。但我仍希望具有重载运算符的灵活性,其中C = A + B将A和B作为任何类型的矩阵,并返回相应的结果(如果操作数之一是矩形,则结果可以降级为典型的矩形矩阵)。
我想到了两个可能的想法,但都很混乱:
(1)IMatrix接口,列出每种类型矩阵需要实现的所有方法,例如转置、逆等。这些方法的高效实现对于每种类型的矩阵都不同。这里有两个问题: (a) 运算符重载是静态方法,因此无法在接口中列出,甚至不能在实现接口的基类中列出。必须在每个类中单独编写运算符重载,否则我无法实现C=A+B类型的操作(如上所述),而且客户端代码中会出现混乱的类型检查和强制转换,这是我真正想避免的。(b) 当我定义运算符重载时,我不能将两个操作数都定义为接口: 例如,在DiagonalMatrix类中无法执行以下操作:
public override IMatrix operator +(IMAtrix lhsMatrix, IMatrix rhsMatrix)
{ ... }

(2) 可以在一个Matrix类中有一个存储在类中的矩阵类型变量(可以是枚举)。根据类型,我们可以实现数据结构和算法。运算符重载将无缝工作。这里有一个问题:(a) 类会很大,并且可能需要使用switch-case语法来检查矩阵类型,然后启动特定的算法。对于每个二进制运算符,我都必须有n^2个情况,其中n是我想要实现的矩阵类型的数量。这也可能是维护的噩梦。

看起来,没有运算符重载的详细信息,我可以使用工厂模式访问者模式,但是不能使用运算符重载。那么,如何解决这个问题呢?

到目前为止,我找到的资源:

  1. 这里有一个相关主题
  2. 另一个操作系统 C# 数字包的开发者面临类似问题的解释

编辑:

2011年4月25日: 我找到了更多关于这个问题的资源。


有一些现有的数学库,你可以使用它们或者从中获取灵感:https://dev59.com/cHE95IYBdhLWcg3wY81l - Merlyn Morgan-Graham
@Merlyn:我已经考虑过了,但是有政策问题,所以我必须自己开发。不过还是谢谢你的建议。 - Samik R
1个回答

7
如果这是我的项目,我会选择 #1 的变体:定义一个抽象的 Matrix 类,该类被更具体的类型(如 TriangularMatrix)继承。这将允许您创建运算符(即使这些运算符只是抛出 NotImplementedException),然后在派生类中重写它们。它还将允许您将任何矩阵视为 Matrix,并使用该通用功能集来处理它。
您唯一会失去的是编译器检查您是否确实重写了方法和运算符;由于运算符是静态的,它们不能被设为抽象。如果您希望绕过此问题,可以在基类中使运算符仅调用相应的命名方法(例如,+ 调用 Add 方法),该方法可以在基类中被设为抽象,从而强制子类对其进行实现。
数学问题:三角形矩阵是否可以加入矩形矩阵,或者两个加数必须匹配类型和/或尺寸?如果是前者,则考虑在基础 Matrix 类中实现运算符,并使该运算符实现策略模式,调用可以执行每个类型组合的实际操作的内部类。如果是后者,则只需重写该类型矩阵的有效运算符的基类实现。

感谢您的评论。问题的关键是操作符重载,能够在客户端代码中透明地执行C=A+B或C=A*B,而不必进行混乱的强制转换。您建议的解决方法,即使用命名方法并从操作符重载中调用它们,不会遇到同样的问题吗?我的意思是,静态的+重载如何调用实例方法Add()?另一方面,如果Add()也是静态的,它就不能是抽象的,因此不能被覆盖,对吗? - Samik R
@Samik R.:不完全是这样。如果+运算符需要一个Add()方法,那么抽象的Matrix类可以将其定义为抽象方法,并要求其子类实现它。Matrix然后可以调用Add()并信任该操作将被处理。虽然操作符是静态的,但它们必须在实例上工作,因此接受A和B的+运算符将调用A.Add(B)。 - KeithS
如果您愿意,可以通过使基类中的运算符简单地调用等效的命名方法来解决此问题。这样操作的返回类型不是派生类型的实例,而是基类的实例。 - Asad Saeeduddin
有办法解决这个问题。你可以在矩阵中包含一些元数据,用于将其转换为真正的子类型。或者,你可以在基类中定义运算符和Add方法的重载(当你不知道或不关心矩阵的类型时),并在子类级别进行定义(当你需要知道矩阵的类型时)。 - KeithS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接