繁体   English   中英

防止方法被继承

[英]Preventing methods from being inherited

我有一个具体的基类 Foo,它包含 30 个与其子类相关的方法。

现在我遇到了一个只针对基类的情况,我想创建一个不能被继承的方法,这可能吗?

Class Foo 
{
   /* ... inheritable methods ... */

   /* non-inheritable method */
   public bool FooSpecificMethod()
   { 
      return true;
   } 
}

Class Bar : Foo
{
    /* Bar specific methods */
}

var bar = new Bar();
bar.FooSpecificMethod(); /* is there any way to get this to throw compiler error */

编辑

我不确定我最初是否清楚。

我确实了解继承原则,也了解 Liskov 替换原则。 在这种情况下,有一个异常只处理“未继承”的情况,因此我不想创建“未继承的Foo”子类。

我在问在技术上是否可以创建 foo.FooSpecificMethod() 是有效且可公开访问的方法的情况,但 subclassoffoo.FooSpecificMethod() 会引发编译器错误。

本质上,我想要一个未密封类的密封方法。

我会重新考虑这样做的必要性。

如果您使用继承,则建议“Bar”是“Foo”。 如果“Bar”总是一个“Foo”,那么对“Foo”起作用的方法也应该对“Bar”起作用。

如果不是这种情况,我会将其重新设计为私有方法。 在公开场合,Bar 应该始终是 Foo。


只是为了更进一步——

如果你能做到这一点,事情就会变得非常复杂。 您可能会遇到以下情况:

Foo myBar = new Bar(); // This is legal
myBar.FooSpecificMethod(); // What should this do?  
                           // It's declared a Foo, but is acutally a Bar

不过,您实际上可以使用反射来强制这种行为。 我认为这是一个坏主意,但 FooSpecificMethod() 可以检查它的类型,如果它不是 typeof(Foo),则抛出异常。 这会非常令人困惑,并且有一种非常难闻的气味。


编辑以回应问题的编辑:

编译器无法强制执行您的要求。 如果你真的想强制编译器检查这一点,并防止这种情况发生,你真的应该考虑让 Foo 成为一个密封类。 在这种情况下,您可以使用除子类之外的其他扩展方法。

例如,您可能需要考虑使用事件或委托来扩展行为,而不是允许对象成为子类。

试图做你正在完成的事情基本上是试图阻止继承的主要目标。

布赖恩关于 Liskov 替换的权利(更新)。 里德关于“is-a”的说法是正确的(也被升级了); 事实上,他们都在告诉你同样的事情。

您的公共方法是与您的类 Foo 的用户签订的合同,即“您可以始终”在 Foo 上调用这些方法。

对 Foo 进行子类化意味着您是说在可以使用 Foo 的地方使用子类(例如 Bar)总是可以接受的。 特别是,这意味着您不会(必须)继承 Foo 的实现(您可以覆盖它,或者 Foo 可能是抽象的并且没有为方法提供特定的实现)。

实现的继承(如果有的话)是一个细节; 真正继承的是公共接口、合同、对用户的承诺,即 Bar 可以像 Foo 一样使用。

事实上,他们甚至可能永远不知道他们有一个 Bar,而不是一个 Foo:如果我创建了一个 FooFactory,并且我写了它的 Foo* getAFoo() 来返回一个指向 Bar 的指针,他们可能永远不会知道,也不必.

当您违反该合同时,您就违反了面向对象。 (而 Java Collection 类,通过抛出 NotSupported 异常,完全破坏了 OO —— 用户不能再多态地使用所谓的子类。这是糟糕的、糟糕的设计,它给许多 Java 用户带来了很大的麻烦,而不是值得效仿的东西。 )

如果有一个 Foo 子类不能使用的公共方法,那么该方法不应该在 Foo 中,它应该在 Foo 子类中,并且其他子类应该从 Foo 派生。

现在,这并不意味着 Foo 中的所有方法都应该可以在子类上调用。 不,我并不自相矛盾。 非公共方法不是类公共接口的一部分。

如果您希望 Foo 中的方法无法在 Bar 上调用,并且不应在 Foo 中公开调用,则将该方法设为私有或受保护。

不,这会违反Liskov 替换原则

实际上,您可以让它在 Bar 中“抛出 NotImplementedException()”,或者从 Foo 中删除该方法并将其向下移动到它适用的子类。

将函数设为私有将防止子类直接调用它。

如果您谈论的是不想被重载的虚函数,那么在您想要“锁定”该函数的时候将该函数标记为密封的。

即使它是一个私有函数,它仍然可以被反射调用。

您还可以在接口上声明函数,并在类上显式实现接口,这将迫使您将其强制转换为接口以使用该函数。

您可以创建一个私有函数,然后使用反射调用它。 可能有点过头了。 无论如何,只需将该函数放在您的基类中,并附上说明它只能从基类调用的注释。 甚至可能是那些在智能感知中出现的不错的 /// 评论。 然后,您可能会遇到错误,但好吧,您总是会遇到错误,您能做的最好的事情就是记录这种情况以尝试避免它。

派生类可以在不违反 Liskov 替换原则的情况下隐藏基类成员的原因有两个:

  1. 如果所讨论的成员是“受保护的”,则它本质上仅代表父类和派生类之间的契约。 因此,它不会对派生类施加任何义务,除非与父类的合同要求。 如果基类的公共成员依赖于具有特定含义的外部实例的某个非公共特定成员,则即使成员本身不是公共的,更改该成员含义的派生类也可能违反 LSP,因为更改其含义可能改变使用它的公共成员的行为。 但是,在基类定义受保护成员但不使用它的情况下,派生类可以对该成员执行任何它喜欢的操作,而不会违反 LSP。 理解受保护成员的一个关键点是,`ToyotaCar` 不应该改变`Car` 公共成员的语义的原因是对`ToyotaCar` 的引用可能会被赋予期望`Car` 而不是比“丰田汽车”。 另一方面,如果`ToyotaCar`以任何公共成员都没有公开的方式改变了`Car`的受保护方法的行为,那么唯一可以注意到这种改变的代码是在任一`ToyotaCar`中的代码或者它的衍生物,这样的代码会知道它有一个“ToyotaCar”。 在一些其他`Car` 派生类(例如`FordCar`)中的代码可以访问`Car` 的受保护成员,但保证*不能*访问`ToyotaCar` 实例上的这些方法。
  2. 有时让一个基类型或接口公开一些成员是有用的,这些成员并非都适用于每个实现类型。 虽然接口隔离原则建议人们应该避免“厨房-水槽”接口,但在某些情况下,此类接口可能比任何替代方案都更实用。 例如,某些类型的“流”类可能支持不同的握手模式,但其他类型则不支持。 如果引用流将通过不关心握手的代码传递,那么让 Stream 类包含一个可能可用也可能不可用的握手选择属性可能比要求代码尝试转换传入的更容易引用`IHandshakeSelector` 并设置`IHandshakeSelector.HandshakeMode` 如果是这样。 另一方面,如果一个特定的流类对于除“XonXoff”之外的所有握手模式的行为都相同,如果代码尝试设置它会抛出异常,那么该类隐藏“HandshakeMode”属性可能是有意义的。

我认为,虽然隐藏基类成员通常是糟糕设计的标志,但在某些情况下,隐藏基类成员比让它们暴露更合适; 如果派生类不可能正确使用受保护的方法(就像List<T>的派生类可用的MemberwiseClone方法的情况),则没有理由公开它。 不幸的是,没有完全干净的方法来隐藏这些成员。 最好的办法是声明一个同名的公共成员(使用“new”修饰符),这将阻止对基类成员的所有访问。 例如,代码可以定义一个名为MemberwiseClone且没有成员的嵌套公共静态类。 EditorBrowseable属性可用于防止 Intellisense 显示该嵌套类,但即使代码确实尝试使用该类,它也没有成员,并且无法创建该类型的存储位置,因此它应该是无害的。

如果您想实现您想要做的事情并从私有对象(即您当前继承的对象)公开方法,您应该考虑 Composition [ http://en.wikipedia.org/wiki/Composite_pattern } 而不是继承。

正如其他人指出的那样,这是不可能的,最重要的是,它不应该是必要的。

当我有以下结构时,我遇到了同样的问题:
- 具有子类属性的父类。
- 子类会暴露不同的属性,但都有相同的初始化过程,所以我把代码放在父类中。

public class Parent
{
    public CHILD1 Child1;
    public CHILD2 Child2;

    public Parent(params args){
        Child1 = new CHILD1(arg1, arg2);
        Child2 = new CHILD2(arg1, arg2);
    }
    //split here
    public Parent(object arg1, object arg2) {
        PropertyInfo[] list = this.GetType().GetProperties();
        foreach (var pi in list) {
            pi.SetValue(this, BL.Method(arg1, arg2, this.GetType().Name, pi.Name) // get data
        }
    }
}

public class CHILD1 : Parent
{
    public CHILD1(object arg1, object arg2) : base(arg1, arg2) { }

    public object C1Property1 { get; set; }
    public object C1Property2 { get; set; }
    // ..
}

public class CHILD2 : Parent
{
    public CHILD2(object arg1, object arg2) : base(arg1, arg2) { }

    public object C2Property1 { get; set; }
    public object C2Property2 { get; set; }
    // ..
}

现在我可以打电话给parent.Child1.C1Property1 但是代码也允许调用以下内容: parent.Child1.Child1.Property1 ,这没有意义并导致异常,因为Child1.Child1属性将始终为null

为了解决这个问题,我所要做的就是将父类的代码分成两个类:

  • 一个持有 Child 属性并调用子构造函数
  • 和一个用构造函数逻辑来填充属性。

也许您可以简单地将它创建为父类上的虚拟方法然后覆盖它,在继承它的子类上抛出 NotImplementedException 或类似的东西。

此外,但前提是您愿意忍受一些意大利面,您可以将方法的签名放在接口中,然后让所有需要这种方法的类实现它 缺点是您必须在所有这些孩子中声明相同的方法。

我不确定这是否可能,但也许可以为该接口编写扩展方法?

请记住,这些都违反了 OOP 的封装原则,尽管是轻微的,这让它们变得更加有趣。

我一直在想这个实现自己和整个这个帖子里面有没有实现的海报是什么后,一些伟大的答案来了,但一种方法来执行此操作。 您需要覆盖现有方法,然后使用 Obsolete 属性。 将 Obsolete 属性的 error 属性设置为 true 将阻止编译。

Class Foo 
{
   /* ... inheritable methods ... */

   /* non-inheritable method */
   public bool FooSpecificMethod()
   { 
      return true;
   } 
}

Class Bar : Foo
{
    /* Bar specific methods */

   [Obsolete("Don't use this for whatever reason", true)]//true triggers the compiler error
   public new bool FooSpecificMethod()
   { 
      return true;
   } 
}

var bar = new Bar();
bar.FooSpecificMethod(); /* this now throws a compiler error */

我最终使用的解决方案是创建一个公共内部继承类。 这样它就能够访问基类的私有变量(因为它需要)并且我不需要公开公开任何这些私有变量或函数(我不想公开)。

非常感谢您的所有建议,这让我重新思考我需要从这种安排中得到什么。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM