假设我有带有公共方法 A 和 B 的 BaseClass,并且我通过继承创建了 DerivedClass。
例如
public DerivedClass : BaseClass {}
现在我想在 DerivedClass 中开发一个使用 A 和 B 的方法 C。有没有一种方法可以在 DerivedClass 中覆盖方法 A 和 B 使其成为私有方法,以便只有方法 C 暴露给想要使用我的 DerivedClass 的人?
这是不可能的,为什么?
在 C# 中,如果您继承公共方法,则必须将它们公开。否则,他们希望您首先不要从该类派生。
您必须使用 has-a 关系,而不是使用 is-a 关系。
语言设计者故意不允许这样做,以便您更正确地使用继承。
例如,人们可能会不小心将类 Car 混淆为从类 Engine 派生以获得它的功能。但是引擎是汽车使用的功能。所以你会想使用has-a关系。Car 的用户不想访问引擎的界面。Car 本身不应该将 Engine 的方法与它自己的方法混淆。也不是 Car 的未来衍生产品。
因此,他们不允许它保护您免受不良继承层次结构的影响。
你应该怎么做?
相反,您应该实现接口。这使您可以自由地使用具有关系的功能。
其他语言:
在 C++ 中,您只需在私有、公共或受保护的基类之前指定一个修饰符。这使得对指定访问级别公开的所有基础成员。对我来说,你不能在 C# 中做同样的事情似乎很愚蠢。
重组后的代码:
interface I
{
void C();
}
class BaseClass
{
public void A() { MessageBox.Show("A"); }
public void B() { MessageBox.Show("B"); }
}
class Derived : I
{
public void C()
{
b.A();
b.B();
}
private BaseClass b;
}
我知道上述类的名称有点没有实际意义:)
其他建议:
其他人建议公开 A() 和 B() 并抛出异常。但这并不能为人们提供一个友好的课程,也没有任何意义。
例如,当您尝试从 a 继承List<object>
,并且想要隐藏直接Add(object _ob)
成员时:
// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
throw NotImplementedException("Don't use!!");
}
这并不是最可取的解决方案,但它确实可以完成工作。Intellisense 仍然接受,但在编译时会出现错误:
错误 CS0619:“TestConsole.TestClass.Add(TestConsole.TestObject)”已过时:“此类不支持。”
这听起来是个坏主意。利斯科夫不会留下深刻印象。
如果您不希望 DerivedClass 的消费者能够访问方法 DeriveClass.A() 和 DerivedClass.B() 我建议 DerivedClass 应该实现一些公共接口 IWhateverMethodCIsAbout 并且 DerivedClass 的消费者实际上应该与 IWhateverMethodCIsAbout 交谈并知道根本没有关于 BaseClass 或 DerivedClass 的实现。
你需要的是组合而不是继承。
class Plane
{
public Fly() { .. }
public string GetPilot() {...}
}
现在,如果您需要一种特殊的飞机,例如具有 PairOfWings = 2 的飞机,但除此之外,飞机可以做的一切......您继承飞机。通过这种方式,您声明您的派生符合基类的约定,并且可以在需要基类的任何地方进行替换而不会闪烁。例如 LogFlight(Plane) 将继续使用 BiPlane 实例。
但是,如果您只需要为要创建的新 Bird 的 Fly 行为而不愿意支持完整的基类契约,则可以改用 compose。在这种情况下,重构方法的行为以重用为新类型 Flight。现在在 Plane 和 Bird 中创建并保存对此类的引用。你不继承是因为 Bird 不支持完整的基类契约……(例如它不能提供 GetPilot() )。
出于同样的原因,您不能在覆盖时降低基类方法的可见性。您可以在派生中覆盖并公开基类私有方法,但反之则不行。例如,在这个例子中,如果我派生出一种平面“BadPlane”,然后覆盖并“隐藏”GetPilot() - 将其设为私有;客户端方法 LogFlight(Plane p) 将适用于大多数飞机,但如果 LogFlight 的实现碰巧需要/调用 GetPilot(),则会因“BadPlane”而崩溃。由于基类的所有派生都被期望在需要基类参数的任何地方都是“可替换的”,因此必须禁止这样做。
@Brian R. Bondy 向我指出了一篇关于通过继承和新关键字隐藏的有趣文章。
http://msdn.microsoft.com/en-us/library/aa691135(VS.71).aspx
因此,作为解决方法,我建议:
class BaseClass
{
public void A()
{
Console.WriteLine("BaseClass.A");
}
public void B()
{
Console.WriteLine("BaseClass.B");
}
}
class DerivedClass : BaseClass
{
new public void A()
{
throw new NotSupportedException();
}
new public void B()
{
throw new NotSupportedException();
}
public void C()
{
base.A();
base.B();
}
}
这样的代码将抛出NotSupportedException:
DerivedClass d = new DerivedClass();
d.A();
我知道的唯一方法是使用 Has-A 关系并仅实现您想要公开的功能。
隐藏是一个很滑的斜坡。IMO 的主要问题是:
它取决于实例的设计时声明类型,这意味着如果您执行 BaseClass obj = new SubClass() 之类的操作,然后调用 obj.A(),隐藏就会失败。BaseClass.A() 将被执行。
隐藏可以很容易地掩盖基本类型中的行为(或行为变化)。当您拥有等式的两边,或者调用“base.xxx”是您的子成员的一部分时,这显然不那么令人担忧。
我想说,如果你有一个你想要做的代码库,它不是设计最好的代码库。它通常表示层次结构的一个级别中的一个类需要某个公共签名,而从该类派生的另一个类不需要它。
即将出现的编码范例称为“组合优于继承”。这直接体现了面向对象开发的原则(尤其是单一职责原则和开放/封闭原则)。
不幸的是,我们很多开发人员被教导面向对象的方式,我们已经养成了立即考虑继承而不是组合的习惯。我们倾向于拥有具有许多不同职责的更大类,这仅仅是因为它们可能包含在同一个“真实世界”对象中。这可能导致 5 级以上的类层次结构。
开发人员在处理继承时通常不会考虑的一个不幸的副作用是,继承形成了您可以引入代码中的最强依赖形式之一。您的派生类现在强烈依赖于它继承自的类。从长远来看,这会使您的代码更加脆弱,并导致令人困惑的问题,即更改基类中的特定行为会以模糊的方式破坏派生类。
分解代码的一种方法是通过另一个答案中提到的接口。无论如何,这是一件明智的事情,因为您希望类的外部依赖项绑定到抽象,而不是具体/派生类型。这允许您在不更改接口的情况下更改实现,所有这些都不会影响依赖类中的一行代码。
我宁愿维护一个包含数百/数千/甚至更多类的系统,这些类都是小而松耦合的,而不是处理一个大量使用多态性/继承并且具有更少类更紧密耦合的系统。
也许关于面向对象开发的最佳资源是 Robert C. Martin 的书,敏捷软件开发、原则、模式和实践。
如果它们在原始类中定义为公共的,则不能在派生类中将它们覆盖为私有。但是,您可以使公共方法抛出异常并实现您自己的私有函数。
编辑:豪尔赫费雷拉是正确的。
虽然这个问题的答案是“不”,但我想为其他到达这里的人指出一个提示(鉴于 OP 有点暗示第 3 方的装配访问权)。当其他人引用程序集时,Visual Studio 应该尊重以下属性,因此它不会在智能感知中显示(隐藏,但仍然可以调用,所以要小心):
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
如果您别无选择,您应该能够new
在隐藏基类型方法的方法上使用 return => throw new NotSupportedException();
,并将其与上面的属性结合起来。
另一个技巧取决于尽可能不从基类继承,其中基类具有相应的接口(例如IList<T>
for List<T>
)。“显式”实现接口还将在类类型上对智能感知隐藏这些方法。例如:
public class GoodForNothing: IDisposable
{
void IDisposable.Dispose() { ... }
}
在 的情况下var obj = new GoodForNothing()
,该Dispose()
方法将不可用obj
。但是,任何显式类型转换obj
为IDisposable
.
此外,您还可以包装一个基类型而不是从它继承,然后隐藏一些方法:
public class MyList<T> : IList<T>
{
List<T> _Items = new List<T>();
public T this[int index] => _Items[index];
public int Count => _Items.Count;
public void Add(T item) => _Items.Add(item);
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
/*...etc...*/
}