代码之家  ›  专栏  ›  技术社区  ›  DevinB

防止方法被继承

  •  5
  • DevinB  · 技术社区  · 15 年前

    我有一个具体的基类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 */
    

    编辑

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

    我理解继承的原则,我也理解里斯科夫替代原则。在这种情况下,只有一个异常只处理“未继承”的情况,因此我不想创建“未继承foo”子类。

    我在问是否在技术上有可能创造一种情况 foo.foospecificMethod()是一个有效且可公开访问的方法,但subclassssoffoo.foospecificMethod()引发了一个编译器错误。

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

    9 回复  |  直到 6 年前
        1
  •  11
  •   Reed Copsey    15 年前

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

    如果您正在使用继承,那么您建议“bar”是一个“foo”。如果“bar”总是一个“foo”,那么在“foo”上工作的方法也应该在“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()可以检查这个类型,如果它不是type of(foo),则抛出一个异常。这会很混乱,而且气味很难闻。


    根据问题的编辑进行编辑:

    编译器无法强制执行您的请求。如果您真的想强制编译器检查这个,并防止它发生,那么您真的应该考虑将foo设置为一个密封类。在这种情况下,您可以使用子类化以外的其他扩展方法。

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

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

        2
  •  6
  •   tpdi    7 年前

    布赖恩关于里斯科夫替代的观点是正确的。里德关于“IS-A”的观点是正确的(也是改进的);事实上,他们都在告诉你同样的事情。

    你的公共方法是 合同 对于类foo的用户来说,“您可以”在foo上调用这些方法。

    子类化foo意味着您要说的是,在可以使用foo的地方,子类(例如bar)总是可以接受的。特别是,它意味着您继承的不是(必然)foo的实现(您可以重写它,或者foo可能是抽象的,并且不为方法提供特定的实现)。

    实现继承(如果有的话)是一个细节;你是什么 真正继承 是公共接口、合同、 承诺 对于用户来说,一个酒吧可以像比赛场地一样使用。

    事实上,他们可能永远不会知道他们有一个条,而不是一个foo:如果我创建一个foofactory,并编写它的foo*getafoo()来返回指向条的指针,他们可能永远不会知道, 而且不必 .

    当你打破这个契约,你就打破了对象方向。(Java集合类,通过抛出不支持的异常,完全打破OO -用户不能再使用多态的所谓子类。这是一个很差的设计,对很多Java用户来说,这是一个很大的问题, 要模仿的东西。)

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

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

    如果您想要一个在foo中不能在bar上调用的方法, 不应该在foo中公开调用,然后使该方法私有或受保护。

        3
  •  4
  •   Brian    15 年前

    不,这会违反 Liskov substitution principle .

    实际上,您可以让它在bar中“throw notImplementedException()”,或者从foo中移除该方法并将其向下移动到它应用的子类中。

        4
  •  2
  •   Jason Coyne    15 年前

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

    如果您谈论的是不希望重载的虚拟函数,那么在您希望“锁定”函数的工作点将该函数标记为密封的。

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

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

        5
  •  1
  •   Kibbee    15 年前

    您可以创建一个私有函数,然后使用反射调用它。可能有点落水了。无论如何,只需将函数放在基类中,并添加一些注释,说明只应从基类调用函数。也许是那些用intellisense显示的漂亮的///评论。然后,你可能会得到一个bug,但然后,你总是会得到bug,你能做的最好的事情就是记录下这种情况,试图避免它。

        6
  •  0
  •   DevinB    15 年前

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

    非常感谢你的建议,这使我重新思考我需要从这个安排。

        7
  •  0
  •   supercat    11 年前

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

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

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

        8
  •  0
  •   Denis    10 年前

    你应该考虑作文[ http://en.wikipedia.org/wiki/Composite_pattern }如果您想要实现您正在尝试做的事情,并从私有对象(即您当前继承的对象)中公开方法,则为过度继承。

        9
  •  0
  •   Damian Vogel    6 年前

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

    当我有以下结构时,我遇到了同样的问题:
    -具有子类属性的父类。
    -子类将公开不同的属性,但所有的初始化过程都相同,因此我将代码放入父类中。

    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 .

    为了解决这个问题,我只需要将父类的代码拆分为两个类:

    • 一个保存子属性并调用子构造函数
    • 以及一个使用构造函数逻辑来填充属性的。
    推荐文章