66

假设我有带有公共方法 A 和 B 的 BaseClass,并且我通过继承创建了 DerivedClass。

例如

public DerivedClass : BaseClass {}

现在我想在 DerivedClass 中开发一个使用 A 和 B 的方法 C。有没有一种方法可以在 DerivedClass 中覆盖方法 A 和 B 使其成为私有方法,以便只有方法 C 暴露给想要使用我的 DerivedClass 的人?

4

10 回答 10

78

这是不可能的,为什么?

在 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() 并抛出异常。但这并不能为人们提供一个友好的课程,也没有任何意义。

于 2008-09-19T23:56:51.000 回答
33

例如,当您尝试从 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)”已过时:“此类不支持。”

于 2010-07-01T10:06:57.593 回答
7

这听起来是个坏主意。利斯科夫不会留下深刻印象。

如果您不希望 DerivedClass 的消费者能够访问方法 DeriveClass.A() 和 DerivedClass.B() 我建议 DerivedClass 应该实现一些公共接口 IWhateverMethodCIsAbout 并且 DerivedClass 的消费者实际上应该与 IWhateverMethodCIsAbout 交谈并知道根本没有关于 BaseClass 或 DerivedClass 的实现。

于 2008-09-19T23:35:43.187 回答
5

你需要的是组合而不是继承。

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”而崩溃。由于基类的所有派生都被期望在需要基类参数的任何地方都是“可替换的”,因此必须禁止这样做。

于 2008-09-20T05:18:18.887 回答
4

@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();
于 2008-09-19T23:34:33.423 回答
3

我知道的唯一方法是使用 Has-A 关系并仅实现您想要公开的功能。

于 2008-09-19T23:31:32.777 回答
1

隐藏是一个很滑的斜坡。IMO 的主要问题是:

  • 它取决于实例的设计时声明类型,这意味着如果您执行 BaseClass obj = new SubClass() 之类的操作,然后调用 obj.A(),隐藏就会失败。BaseClass.A() 将被执行。

  • 隐藏可以很容易地掩盖基本类型中的行为(或行为变化)。当您拥有等式的两边,或者调用“base.xxx”是您的子成员的一部分时,这显然不那么令人担忧。

  • 如果您确实拥有基本/子类等式的两边,那么您应该能够设计出比制度化隐藏/隐藏更易于管理的解决方案。
于 2008-09-20T03:13:36.533 回答
1

我想说,如果你有一个你想要做的代码库,它不是设计最好的代码库。它通常表示层次结构的一个级别中的一个类需要某个公共签名,而从该类派生的另一个类不需要它。

即将出现的编码范例称为“组合优于继承”。这直接体现了面向对象开发的原则(尤其是单一职责原则和开放/封闭原则)。

不幸的是,我们很多开发人员被教导面向对象的方式,我们已经养成了立即考虑继承而不是组合的习惯。我们倾向于拥有具有许多不同职责的更大类,这仅仅是因为它们可能包含在同一个“真实世界”对象中。这可能导致 5 级以上的类层次结构。

开发人员在处理继承时通常不会考虑的一个不幸的副作用是,继承形成了您可以引入代码中的最强依赖形式之一。您的派生类现在强烈依赖于它继承自的类。从长远来看,这会使您的代码更加脆弱,并导致令人困惑的问题,即更改基类中的特定行为会以模糊的方式破坏派生类。

分解代码的一种方法是通过另一个答案中提到的接口。无论如何,这是一件明智的事情,因为您希望类的外部依赖项绑定到抽象,而不是具体/派生类型。这允许您在不更改接口的情况下更改实现,所有这些都不会影响依赖类中的一行代码。

我宁愿维护一个包含数百/数千/甚至更多类的系统,这些类都是小而松耦合的,而不是处理一个大量使用多态性/继承并且具有更少类更紧密耦合的系统。

也许关于面向对象开发的最佳资源是 Robert C. Martin 的书,敏捷软件开发、原则、模式和实践

于 2008-09-20T04:54:10.147 回答
0

如果它们在原始类中定义为公共的,则不能在派生类中将它们覆盖为私有。但是,您可以使公共方法抛出异常并实现您自己的私有函数。

编辑:豪尔赫费雷拉是正确的。

于 2008-09-19T23:31:03.697 回答
0

虽然这个问题的答案是“不”,但我想为其他到达这里的人指出一个提示(鉴于 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。但是,任何显式类型转换objIDisposable.

此外,您还可以包装一个基类型而不是从它继承,然后隐藏一些方法:

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...*/
}
于 2018-02-27T10:52:13.287 回答