注意事项:
- 除了逻辑上的差异,我也有兴趣了解具体反射(reflect)在 C# 中的技术差异(因此,这不是 Programmers 的问题)。
- This question有点相似,但它问的是方法,而我问的是类,所以它不是重复的。
Circle
和 Rectangular
是形状。两者都有周长和面积,但计算它们的实现方式不同。我可以看到三种不同的方式来实现这种逻辑,但我不确定这些方法之间有什么区别。
使用动态多态:
class Shape
{
public virtual double Perimeter() { /* logic */ }
public virtual double Area() { /* logic */ }
}
class Rectangular : Shape
{
public override double Perimeter() { /* logic */ }
public override double Area() { /* logic */ }
}
class Circle : Shape
{
public override double Perimeter() { /* logic */ }
public override double Area() { /* logic */ }
}
使用抽象类:
abstract class Shape
{
public abstract double Perimeter() {}
public abstract double Area() {}
}
class Rectangular : Shape
{
public override double Perimeter() { /* logic */ }
public override double Area() { /* logic */ }
}
class Circle: Shape
{
public override double Perimeter() { /* logic */ }
public override double Area() { /* logic */ }
}
使用接口(interface):
interface IShape
{
double Perimeter();
double Area();
}
class Rectangular : IShape
{
public double Perimeter() { /* logic */ }
public double Area() { /* logic */ }
}
class Circle: IShape
{
public double Perimeter() { /* logic */ }
public double Area() { /* logic */ }
}
在标题中,我提到我对一个从OOP角度的答案很感兴趣。我想了解基于 OOP 范式的方法之间的理论差异,并从中了解技术差异。例如,我知道接口(interface)方法不能有实现,而虚拟方法可以,但我不明白为什么它包含在 OOP 范式中。
请回答所有理论差异,并针对每个差异,在 C# 中派生技术差异。
非常感谢。
编辑: @AdrianoRepetti 和@Biscuits 说我的问题含糊不清。我的英语不是很好,所以我会尽量解释清楚。
我展示了做同一件事的三种不同方法。但这真的是一回事吗?从程序架构 POV 来看,它们之间有什么区别?我的意思是,当我设计程序时,我为什么要选择一个而不是另一个?什么是本质差异,这些差异在 C# 语法中是如何表达的?我希望我的问题现在更清楚了。
如果英语说得很好的人认为他/她理解我的问题并且可以将其编辑为更清晰和语法正确,我将不胜感激。
非常感谢!
最佳答案
您的三种方法完全不同,因此您无法真正比较它们。让我们看看为什么。
非抽象基类
您有一个基类,但它不是抽象的,它提供了一个默认实现。您应该问自己的第一个问题是这是否有意义。
Shape shape = new Shape();
Console.WriteLine($"Area of this shape is {shape.Area}");
- 做一个通用的
Shape
存在吗? - 你能计算未知形状的面积吗?
如果答案是是,那么您可以使用它...
非多态抽象基类
这对我来说意义不大,你不重写基类中的基类方法,想想这个:
Circle circle = new Circle();
Shape shape = circle;
// When invoking with a Shape instance you will call
// be class method, when with a Circle instance you will call derived
// class method!!!
Debug.Assert(shape.Area != circle.Area);
另请注意,基类再次为 Area
提供了实现。和 Perimeter
, 它对通用形状有意义吗?
界面
在这种情况下,IMO 接口(interface)(或抽象基类)是有意义的。简而言之,您只是要求一个接口(interface) 来访问形状对象,您没有提供通用的(不存在的?)实现和被调用的方法,这可能是您可能期望的。
缺点?假设您稍后添加一个 IEnumerable<Point> GetPoints()
IShape
的方法,为简单起见,假设此方法可能返回 null
当实现无法返回折线来绘制此形状时。现在您的代码已损坏,直到您更新所有实现 IShape
的类.如果您部署了 IShape
作为库的一部分,那么您还引入了重大更改(我不会在这里重复太多,它已在其他地方广泛讨论)。
请注意(即使我知道这只是一个虚构的例子):所有形状都存在面积吗?如果你引入一条开放的多段线呢?在这种情况下,接口(interface)更有意义:
interface IShape
{
double Perimiter { get; }
}
interface IClosedFigure : IShape
{
double Area { get; }
}
interface IHasPoints
{
IEnumerable<Point> GetPoints();
}
sealed class Circle : IClosedFigure { /* ... */ }
sealed class Polyline : IShape, IHasPoints { /* ... */ }
有其他选择吗?是的,介于第二种和第三种方法之间...
抽象基类
选择第二种方法并将基类方法标记为 abstract
.这将有效地生成与第三种方法相同的 IL 代码(没有接口(interface)的缺点):
abstract Shape
{
public abstract double Area { get; }
}
sealed Circle : Shape
{
public override double Area
{
get { /* logic */ }
}
}
请注意,如果将其标记为 abstract
,您仍然可以添加一个方法并引入重大更改。 .缺点?您现在强制使用基类,而 C# 是单继承。
请注意,您不需要只有抽象方法/属性,您可以为其中的一些/全部提供实现。在这种情况下,这种方法更类似于您提出的第一个方法。
结论
第二种方法是非常极端的情况,应该很少在设计良好的架构中使用。如果在某些情况下并且您可以提供合理的默认实现(例如,您可以通过 GetPoints()
使用慢速方法计算面积,并为已知形状提供更快的计算,第一个可能有意义) .如果不应实例化基类,则还应将其标记为 abstract
(使这种方法更接近第四种方法)。
第三种方法和另一种提议的方法(基本抽象类)在某种程度上是等价的,但意图不同。有关此主题的讨论,您可以阅读 Interface vs Base class作为起点(关于此的 Material 更多)。
关于c# - 从 C# 中反射(reflect)的 OOP 角度来看,动态多态性、抽象类和接口(interface)之间有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37315791/