代码之家  ›  专栏  ›  技术社区  ›  John Sonmez

是否应从编程语言中删除继承(非接口类型)?

  •  14
  • John Sonmez  · 技术社区  · 17 年前

    我已经做了大约10年的编程,我不能诚实地说我能回忆起继承解决了一个无法用其他方法解决的问题的时代。另一方面,我可以回忆起很多次我使用继承的时候,因为我觉得我必须这样做,或者因为我觉得自己很聪明,最终为此付出了代价。

    从实现的角度来看,我看不出有任何情况下不能使用聚合或其他技术来代替继承。

    (更新)

    (更新2代码示例)

    public interface IShape
    {
        void Draw();
    }
    
    public class BasicShape : IShape
    {
        public void Draw()
        {
            // All shapes in this system have a dot in the middle except squares.
            DrawDotInMiddle();
        }
    }
    
    public class Circle : IShape
    {
        private BasicShape _basicShape;
    
        public void Draw()
        {
            // Draw the circle part
            DrawCircle();
            _basicShape.Draw();
        }
    }
    
    public class Square : IShape
    {
        private BasicShape _basicShape;
    
        public void Draw()
        {
            // Draw the circle part
            DrawSquare();
        }
    }
    
    20 回复  |  直到 16 年前
        1
  •  20
  •   Jon Skeet    17 年前

    blogged about this 刚才是个古怪的主意。

    我不认为应该删除它,但我认为类应该在默认情况下密封到 不合适的继承。这是一个强大的工具,但它就像一个链锯-你真的不想使用它,除非它是最好的 完美的

    IMO表示,这些都是潜在的语言功能,比如mixins,它可以让没有它的人更容易生活。

        2
  •  10
  •   Greg Beech    17 年前

    在基类中有许多方法对每个派生类具有相同的实现的情况下,继承非常有用,以避免每个派生类都必须实现锅炉板代码。以.NET为例 Stream 类,该类定义了以下方法:

    public virtual int Read(byte[] buffer, int index, int count)
    {
    }
    
    public int ReadByte()
    {
        // note: this is only an approximation to the real implementation
        var buffer = new byte[1];
        if (this.Read(buffer, 0, 1) == 1)
        {
            return buffer[0];
        }
    
        return -1;
    }
    

    因为继承是可用的,所以基类可以实现 ReadByte StreamUtil 键入他们可以调用的类(yuk!)。

    为了澄清这一点,使用继承创建 DerivedStream

    public class DerivedStream : Stream
    {
        public override int Read(byte[] buffer, int index, int count)
        {
            // my read implementation
        }
    }
    

    而如果我们使用的是接口和中方法的默认实现 StreamUtil

    public class DerivedStream : IStream
    {
        public int Read(byte[] buffer, int index, int count)
        {
            // my read implementation
        }
    
        public int ReadByte()
        {
            return StreamUtil.ReadByte(this);
        }
    }
    

    }

    因此,这不是一个庞大的代码量,而是将它乘以类上的几个方法,这只是编译器可以处理的不必要的锅炉板内容。为什么实施起来比必要的更痛苦?我不认为继承是万无一失的,但如果使用得当,它会非常有用。

        3
  •  6
  •   Norman Ramsey    17 年前

    当然,你可以在没有对象和继承的情况下愉快地编写伟大的程序;函数式程序员总是这样做。但我们不要草率行事。任何对这个话题感兴趣的人都应该看看Xavier Leroy受邀演讲的幻灯片 classes vs modules in Objective Caml . Xavier做了一个很好的工作,列出了在不同类型的继承中哪些继承做得好,哪些继承做得不好

    所有语言都是图灵完备的,所以当然不需要继承。但作为对继承价值的论证,我提出 Smalltalk blue book ,尤其是 数字层次 . 一个熟练的专家可以在不干扰现有系统的情况下添加一种全新的数字(或集合),这给我留下了深刻的印象。

    话虽如此,我认为继承有其固有的弱点(你的程序逻辑在一大组类中被抹掉了),而且 它应该很少使用,而且只能由熟练的专业人员使用

    对于使用完全不同的机制解决类似问题的有趣想法,人们可能会想看看 Haskell type classes

        4
  •  6
  •   Clint Miller    17 年前

    我希望语言能够提供一些机制,使委托给成员变量更容易。例如,假设接口I有10个方法,类C1实现了这个接口。假设我想要实现类C2,它与C1类似,但方法m1()被重写。在不使用继承的情况下,我将按如下方式(在Java中)执行此操作:

    public class C2 implements I {
        private I c1;
    
        public C2() {
           c1 = new C1();
        }
    
        public void m1() {
            // This is the method C2 is overriding.
        }
    
        public void m2() {
            c1.m2();
        }
    
        public void m3() {
            c1.m3();
        }
    
        ...
    
        public void m10() {
            c1.m10();
        }
    }
    

    换句话说,我必须显式地编写代码,将方法m2..m10的行为委托给成员变量m1。这有点痛。它还使代码变得混乱,因此很难看到C2类中的真正逻辑。这还意味着,每当新方法被添加到接口I时,我必须显式地向C1添加更多代码,以便将这些新方法委托给C1。

    我希望语言允许我这样说:C1实现了I,但如果C1缺少来自I的某个方法,则会自动委托给成员变量C1。这将把C1的大小减少到

    public class C2 implements I(delegate to c1) {
        private I c1;
    
        public C2() {
           c1 = new C1();
        }
    
        public void m1() {
            // This is the method C2 is overriding.
        }
    }
    

    如果语言允许我们这样做,那么避免使用继承就会容易得多。

    这里有一个 blog article 我写过关于自动授权的文章。

        5
  •  5
  •   Lasse V. Karlsen    17 年前

    让我们以我目前的世界为例,主要是C#development。

    换句话说,以下代码:

    public interface IPerson { ... }
    public interface IEmployee : IPerson { ... }
    public class Employee : IEmployee
    {
        private Person _Person;
        ...
    
        public String FirstName
        {
            get { return _Person.FirstName; }
            set { _Person.FirstName = value; }
        }
    }
    

    这基本上要短得多,否则我会有很多这样的属性,只是为了让我的类足够好地模仿一个人,比如:

    public class Employee : IEmployee
    {
        private Person _Person implements IPerson;
        ...
    }
    

    这可以自动创建所需的代码,而不是我必须编写它。如果我将对象强制转换为IPerson,只返回内部引用是没有好处的。

    此外,还可以删除可见性之类的内容。一个界面实际上只有两个可见性设置:There和NotThere。在某些情况下,或者我认为,您将被迫公开更多的内部数据,以便其他人可以更轻松地使用您的类。

    对于基于类的继承,您通常可以公开一些子代可以使用的访问点,但外部代码不能,您通常只需要删除这些访问点,或者让它们向所有人开放。我不确定我喜欢哪一种选择。

    真正地

    具有 Delphi中的关键字。这个关键词不仅仅像是在射你自己的脚,它就像是编译器买了一把猎枪,来到你家,瞄准你。

    现在我把猎枪放在哪里了。。。

        6
  •  5
  •   brianjlowry    17 年前

    在所有类上实现ISystemObject会很有趣,这样您就可以访问ToString()和GetHashcode()。

    如果您不喜欢继承,我的建议是停止同时使用.NET。有太多的场景可以节省时间(请参阅DRY:不要重复自己)。

    如果使用继承破坏了您的代码,那么您需要后退一步,重新思考您的设计。

    我更喜欢界面,但它们不是万能的。

        7
  •  4
  •   digiarnie    17 年前

    对我来说,这不仅更具可读性,而且更易于测试,而且使重构变得更容易。

        8
  •  3
  •   jmucchiello    17 年前

    不,有时候你需要继承。对于那些你不使用它的时候。您总是可以“只”使用接口(在有接口的语言中),而没有数据的ADP就像没有接口的语言中的接口一样工作。但我看不出有什么理由仅仅因为您觉得它并不总是必要的,就删除它。

        9
  •  3
  •   Spoike Otávio Décio    17 年前

    // Alternative 1:
    public class MyThread extends Thread {
    
        // Abstract method to implement from Thread
        // aka. "template method" (GoF design pattern)
        public void run() {
            // ...
        }
    }
    
    // Usage:
    MyThread t = new MyThread();
    t.start();
    

    在我看来,另一种选择是,

    // Alternative 2:
    public class MyThread implements Runnable {
        // Method to implement from Runnable:
        public void run() {
            // ...
        }
    }
    
    // Usage:
    MyThread m = new MyThread();
    Thread t = new Thread(m);
    t.start();
    // …or if you have a curious perversion towards one-liners
    Thread t = new Thread(new MyThread());
    t.start();
    

    我想你可能会说,第二个实现的好处是依赖注入或关注点分离,这有助于设计可测试类。根据您对接口的定义(我至少听说过三个),抽象类可以被视为接口。

        10
  •  3
  •   David Thornley    17 年前

    因此,我觉得这类问题毫无用处。

    像“什么时候应该使用继承”或“什么时候不应该”这样的问题更有用。

        11
  •  2
  •   Harper Shelby damiankolasa    17 年前

    不,只是因为它不经常需要,并不意味着它永远不需要。与工具箱中的任何其他工具一样,它可能(过去、现在和将来)被滥用。然而,这并不意味着它应该这样做 从不

        12
  •  2
  •   Jimmy    17 年前

    很多时候,我发现自己选择基类而不是接口,只是因为我有一些标准功能。在C#中,我现在可以使用扩展方法来实现这一点,但在某些情况下它仍然无法实现同样的效果。

        13
  •  2
  •   JeremyDWill    17 年前

    不,不是 ,但这并不意味着它不能带来整体利益,我认为这比担心它是否绝对必要更重要。

    最后,几乎所有的现代软件语言结构都是语法糖——如果我们真的需要,我们都可以编写汇编代码(或使用穿孔卡片,或使用真空管)。

    我发现继承在我真正想表达“是一种”关系的时候非常有用。继承似乎是表达这种意图的最清晰的方式。如果我将委托用于所有实现重用,我将失去这种表达能力。

    这允许滥用吗?当然有。我经常看到这样的问题:开发人员如何从类继承而隐藏一个方法,因为该方法不应该存在于子类中。这个人显然忽略了继承的意义,而应该指向委托。

    我不使用继承,因为它是必要的,我使用它,因为它有时是工作的最佳工具。

        14
  •  2
  •   Jim Cooper    17 年前

    真的需要继承吗?这取决于你所说的“真的”是什么意思。理论上,你可以回到穿孔卡片或拨动拨动开关,但这是一种开发软件的可怕方式。

    在过程语言中,类继承无疑是一个福音。它提供了一种在特定情况下优雅地组织代码的方法。它不应该被过度使用,因为任何其他功能都不应该被过度使用。

    他的一些观点:

    这有助于测试并提高可读性

    使用接口,我会得到同样的效果,但我会通过使用 “授权”。

    委派是一个非常好的想法,应该经常使用委派而不是继承(例如,策略模式就是要这样做)。但接口与委托无关,因为您根本无法在接口中指定任何行为。

    对接口的早期承诺通常会使重构变得更困难,而不是更容易,因为这样会有更多的地方需要更改。早期过度使用继承比过度使用接口更好(好的,坏的更少),因为如果被修改的类没有实现任何接口,则拉出委托类更容易。通常,这些代表会提供有用的接口。

    所以过度使用继承是一件坏事。过度使用接口是一件坏事。理想情况下,一个类既不会继承任何东西(除了“对象”或语言等价物),也不会实现任何接口。但这并不意味着这两项功能都应该从语言中删除。

        15
  •  1
  •   JamesSugrue    17 年前

    我最初的想法是,你疯了。但是想了一会儿,我有点同意你的看法。我并不是说完全删除类继承(例如,使用部分实现的抽象类可能会很有用),但我经常继承(双关语)编写糟糕的OO代码,使用多级类继承,除了膨胀之外,什么都没有添加到代码中。

        16
  •  1
  •   Steve Jessop    17 年前

    请注意,继承意味着不再可能通过依赖项注入来提供基类功能,以便在与父类隔离的情况下对派生类进行单元测试。

    所以,如果您对依赖注入非常认真(我不是,但我确实想知道我是否应该如此),那么您无论如何也不能从继承中获得太多的使用。

        18
  •  1
  •   Ed Marty    17 年前

    如果有一个框架类几乎完全满足您的要求,但其接口的特定函数引发NotSupported异常,或者出于其他原因,您只想重写一个方法来执行特定于您的实现的操作,编写一个子类并重写一个方法要比编写一个全新的类并为类中其他27个方法中的每一个编写传递容易得多。

    类似地,比如Java,每个对象都继承自object,因此自动具有equals、hashcode等的实现。我不必重新实现它们,当我想将对象用作哈希表中的键时,它“只起作用”。我不必向 Hashtable.hashcode(Object o) 方法,坦率地说,它似乎在移动 从面向对象。

        19
  •  1
  •   Jordão    16 年前

    traits . 检查这个 link (pdf) 关于这一点,包括性状和混合体之间的区别,以及为什么性状会受到青睐。

    C# (pdf) .

    Moose::Roles mixins ,如 Ruby

        20
  •  0
  •   Daniel Paull    17 年前

    问题是,“应该从编程语言中删除(非接口类型的)继承吗?”

    除此之外,您是否应该使用继承,而不是接口的继承?我主要是C++程序员,我遵循严格的对象模型 接口的多重继承,然后是类的单一继承链 . 具体类是组件及其朋友的“秘密”,因此,它们之间的关系不重要。

    话虽如此,Mixin范式对于C++来说是非常独特的。如果没有这一点,我希望继承对实用主义程序员非常有吸引力。

    推荐文章