代码之家  ›  专栏  ›  技术社区  ›  Tim Jansen

什么是类继承的好例子?[关闭]

  •  26
  • Tim Jansen  · 技术社区  · 15 年前

    我正在为面向对象的语言编写文档,我想知道什么样的类是继承的好例子。

    一些常见的例子:

    class Person {
    }
    class Employee extends Person {
    }
    

    目前是我最喜欢的,但我不喜欢“人”->员工,因为“员工”看起来不太有趣。

    class Bicycle {
    }
    class MountainBike extends Bicycle {
    }
    

    我在一些 Java tutorial 但是,自行车应该具备哪些特征并不十分明显。

    class Animal {
    }
    class Bird extends Animal {
    }
    

    和自行车一样。

    class A {
    }
    class B extends A {
    }
    

    过于抽象。主要的问题是这样的类需要更多的抽象属性和方法。

    对于简单的类层次结构,有人有更好的例子吗?

    22 回复  |  直到 7 年前
        1
  •  12
  •   Mark Brittingham    15 年前

    由于许多原因,动物类是类继承的经典例子。

    首先,有明显的方法来扩展潜在的动物类。你可能从哺乳动物、鸟类、甲壳类动物等亚类开始。

    有些种类,如哺乳动物,将通过添加相当明显的属性(温血等)来扩展动物。

    另外,更麻烦的是,如果你用动物继承来说明,在开发一个类层次结构中很常见的问题是非常明显的——这是一个 好的 为了解释的目的。鸟会飞,对吧?不是所有的鸟…那么,你是如何代表飞行的呢?当然,在网上有一些经典的解决方案和大量的讨论信息,这些信息是关于如何解决每个解决方案所引入的问题和权衡问题的。

    因此,我强烈推荐使用“动物”作为您的例子,因为可用信息和例子的丰富性。

        2
  •  18
  •   Jon Skeet    15 年前

    我喜欢 Stream 等级制度。其思想是,任何东西都可以使用流,而不必关心它是什么类型的流,并且各个子类处理存储的方式也不同(例如 NetworkStream , MemoryStream FileStream 在.NET中)。

    如果你对接口感兴趣,那么 IEnumerable<T> 在.NET中是一个很好的集合-您可以迭代任何集合,而不必关心底层数据结构是什么。

        3
  •  17
  •   JoshBerke    15 年前

    汽车零件可能很有趣,例如

    class part
    {
        OEM
        Manufacturer
        Number
        Description
    }
    
    class Tire extends Part
    {
       Speed
       Rating
    
    }
    
        4
  •  17
  •   Stack Overflow is garbage    15 年前

    我同意乔恩·斯基特关于他的溪流的例子。也许它并不完美,但是它比这里的大多数例子都有一个优势:

    这是现实的

    自行车、人或动物、形状或武器不会被真正的项目中的继承所模仿。(尤其是形状,非常危险,因为 它不起作用 )

    那是我最讨厌的继承权。它经常被教导为 必须 用来表达 每一个 您可以找到层次结构。员工就是一个人,对吗?所以员工班 必须 从人类继承。但是一个人也是一个活生生的生物,所以我们最好也有这样的课程。一个活着的生物也是一个有机体,所以我们有另一个类。有机体是……请随意继续。

    我认为,如果有人,在某个地方,通过解释什么时候应该使用继承,而不仅仅是你如何在任何层次上强制它,不管它是有益的还是不有益的,来教授它,那将是一件好事。

    流(或Chrisw例子中的设备)具有它们所带来的优势 感觉 .您希望能够处理所有流,不管它们是连接到内存缓冲区、文件还是网络套接字。而且所有的硬件设备都有很多共同的行为,这些行为可以被合理地分解成一个设备类。

        5
  •  8
  •   Rik elirevach    15 年前

    许多人使用形状的例子,但实际上这是一个危险的例子。当你凭直觉判断一个正方形是矩形的一个子类时,问题就出现了。

    当涉及到行为时,正方形比矩形更受限制,破坏了可替换性。例如,我们可以要求矩形对象更改其高度。如果一个正方形是矩形的一个子类,那意味着我们可以求一个正方形的子类。然而,改变一个正方形的高度就意味着它不再是一个正方形了!当然,我们可以相应地增加宽度,但是当我们要求一个声明类型为rectangle的对象(实际上是下面的一个正方形)改变它的高度时,这不是我们所期望的。

    它叫做 Liskov substitution principle ,并且在进行任何严重的OO开发时,您应该知道这一点。

    当然,正方形是一个子 设置 矩形,而不是Sub . 这就是面向数据和面向行为的方法之间的区别。

    和乔恩一样,我更喜欢以溪流为例。即使对非程序员来说,也不难解释其清晰的行为导向,避免了图形示例的反直观性。

        6
  •  8
  •   pb2q    12 年前

    如果你喜欢电子游戏,可以是这样的:

    class enemy{
      Health
      Posx
      posy
      Etc
    }
    
    class orc : extends enemy{
     speed
     Strength
     etc
    }
    
        7
  •  6
  •   Edwin Dalorzo    11 年前

    代数表达式的层次结构怎么样?这是一个很好的例子,因为它同时使用继承和组合:

    public interface Expression {
        int evaluate();
    
        public class Constant implements Expression {
    
            private final int value;
    
            public Constant(int value) {
                this.value = value;
            }
    
            @Override
            public int evaluate() {
                return this.value;
            }
    
            @Override
            public String toString() {
                return String.format(" %d ", this.value);
            }
    
        }
    
        public class Negate implements Expression {
    
            private final Expression expression;
    
            public Negate(Expression expression) {
                this.expression = expression;
            }
    
            @Override
            public int evaluate() {
                return -(this.expression.evaluate());
            }
    
            @Override
            public String toString() {
                return String.format(" -%s ", this.expression);
            }
        }
    
        public class Exponent implements Expression {
    
            private final Expression expression;
            private final int exponent;
    
            public Exponent(Expression expression, int exponent) {
                this.expression = expression;
                this.exponent = exponent;
            }
    
            @Override
            public int evaluate() {
                return (int) Math.pow(this.expression.evaluate(), this.exponent);
            }
    
            @Override
            public String toString() {
                return String.format(" %s ^ %d", this.expression, this.exponent);
            }
    
        }
    
        public class Addition implements Expression {
    
            private final Expression left;
            private final Expression right;
    
            public Addition(Expression left, Expression right) {
                this.left = left;
                this.right = right;
            }
    
            @Override
            public int evaluate() {
                return this.left.evaluate() + this.right.evaluate();
            }
    
            @Override
            public String toString() {
                return String.format(" (%s + %s) ", this.left, this.right);
            }
        }
    
        public class Multiplication implements Expression {
    
            private final Expression left;
            private final Expression right;
    
            public Multiplication(Expression left, Expression right) {
                this.left = left;
                this.right = right;
            }
    
            @Override
            public int evaluate() {
                return this.left.evaluate() *  this.right.evaluate();
            }
    
            @Override
            public String toString() {
                return String.format(" (%s * %s) ", this.left, this.right);
            }
        }
    
    }
    

    然后你可以提供一个激励性的例子,比如:

    public static void main(String[] args) {
    
        Expression two = new Constant(2);
        Expression four = new Constant(4);
        Expression negOne = new Negate(new Constant(1));
        Expression sumTwoFour = new Addition(two, four);
        Expression mult = new Multiplication(sumTwoFour, negOne);
        Expression exp = new Exponent(mult, 2);
        Expression res = new Addition(exp, new Constant(1));
    
        System.out.println(res + " = " + res.evaluate());
    
    }
    

    这将产生:

    (  ( ( 2  +  4 )  *  - 1  )  ^ 2 +  1 )  = 37
    
        8
  •  5
  •   SteinNorheim    15 年前

    我认为形状是一门很好的抽象课。有二维和三维形状。二维形状通常具有面积,而三维形状具有体积。两者都可以有一个“位置”或“质量中心”。

    一些建议:

    class Shape {..}
    
    class Shape2D extends Shape {...}
    
    class Circle extends Shape2D {...}
    
    class Rectangle extends Shape2D {...}
    
    class Polygon extends Shape2D {...}
    
    class Shape3D extends Shape {...}
    
    class Sphere extends Shape3D {...}
    
        9
  •  4
  •   Yasser Shaikh    10 年前

    作为一个反恐精英游戏爱好者,我想分享的是:

    enter image description here

        10
  •  3
  •   ChrisW    15 年前

    我建议使用“设备”。没有人真的用软件给动物做模型,但他们做模型装置。

    class Device
    {
      void start();
      void stop();
      DeviceStatus status { get; }
    }
    
    class VideoDevice : Device
    {
      ... methods for any/all video devices ...
    }
    
    class DiskDevice : Device
    {
      ... methods for any/all disk devices ...
    }
    
        11
  •  2
  •   Iain M Norman    15 年前

    我一直喜欢:

    class Shape {
    }
    class Square extends Shape {
    }
    

    但你所引用的前三名中的任何一个都可以。山地车听起来最刺激。当然,你也可以对汽车做类似的事情。

        12
  •  1
  •   Gergely Feldhoffer    7 年前

    我过去常展示棋子。基本阶级是一般棋子、国王、车、主教等的继承者。

    我喜欢这个例子:

    • 是真的,如果你下国际象棋,这是个解决办法
    • 在C++中,你可以显示哪个函数是虚函数,而不是函数。
    • 可以显示抽象类
    • 先进的例子可以发展为定向块(rook和bishop)的公共基本类,甚至进一步发展为具有阶跃限制的广义定向块(除了兵以外的所有东西)。
        13
  •  1
  •   Victor    7 年前

    webshop也是一个不错的选择。

    Product(id, title, price, description)
    Book(isbn, publisher, pageNr) extends Product
    CD(company, duration, genre, taskList) extends Product
    
        14
  •  1
  •   Emir Memic    7 年前

    我喜欢以打印机为例的想法。假设您正在为HP、Brother、Lexmark或其他实体工作,您需要开发一个能够为各种操作系统生成驱动程序特定模块的程序。

    class Printer {
        private String brand;
        private String model;
    
        public void powerOn() {
            // Logic to power on
        }
    
        public void powerOff() {
            // Logic to power off
        }
    
        public void init() {
            // Bootstrap/prep the system
            // Check cartridge
            // Check paper
            // Ready for usage
    }
    
    class InkJetPrinter extends Printer {
        // Inherits fields and methods from Printer
        // Demonstrate method overriding and overloading
        // Add new InkJet specific behaviors, components etc.
    }
    
    class LaserPrinter extends Printer {
        // Inherits fields and methods from Printer
        // Demonstrate method overriding and overloading
        // Add new Laser specific behaviors, components etc.
    }
    
    class LabelPrinter extends Printer {
        // Inherits fields and methods from Printer
        // Demonstrate method overriding and overloading
        // Add new Label specific behaviors, components etc.
    }
    

    演示继承之后,您可以转向抽象和方法重载/重写。 printer类中的init方法是一个很好的抽象候选方法。子类需要实现自己的初始化过程。

    您还可以扩展这个示例,并开始演示接口和组合的正确用法。IS-A和HAS-A的概念有什么区别?某些打印机可能具有WiFi和蓝牙功能。或者,某些喷墨打印机可能具有扫描功能,而其他打印机只有进纸器等。您将如何实现这一点?

    我认为使用打印机与计算机和一般的计算机科学有更密切的关系。您可以使用此示例,甚至进一步演示处理嵌入式系统与PC、智能手机和其他物联网之间的网络连接的示例。

        15
  •  0
  •   Rowland Shaw    15 年前

    我喜欢车辆的例子,因为它允许一个相对干净的扩展将接口包含到讨论中(IAutomaticGearbox,任何人?)

        16
  •  0
  •   Muad'Dib    15 年前

    怎么样

    class Weapon 
    {
    }
    
    class Gun : extends Weapon
    {
    }
    
    class Knife : extends Weapon
    {
    }
    

    ET.

        17
  •  0
  •   dirkgently    15 年前

    继承可能很复杂。让我们从最简单的行为继承开始。这里您只继承行为,没有状态。例如:人和动物都是有生命的物体,表现出一种真实的行为。要使用您的示例:

    class LivingThing {
       /* We propose a new type */
      public:
        virtual bool IsAlive() = 0;
        virtual void Birth() = 0;
        virtual void Death() = 0;
    };
    
    class Person : public  LivingThing {
        /* A real living thing */
      public:
        virtual bool IsAlive() { return true; }
        virtual void Birth() {}
        virtual void Death() {}
        /* .. and has some special behavior */
        void Marry();
        void Divorce();
    };
    
    class Animal: public  LivingThing {
        /* A real living thing */
      public:
        virtual bool IsAlive() { return true; }
        virtual void Birth() {}
        virtual void Death() {}
        /* .. and has some special behavior */
        void Bite();
        void Bark();
    };
    

    我用C++语法写的,如果你有任何问题理解它,就说吧!

        18
  •  0
  •   Santosh Gokak    15 年前

    我遇到的最好的例子是使用形状。

    最好的一点是,你可以很容易地向任何与他经验无关的程序员解释所有与OOP相关的概念(包括困难的概念),比如类、对象、继承、抽象、封装、多态性等。

        19
  •  0
  •   Elroy    15 年前

    例子:

    “一切都源于对象”的方法。

    狗---gt;动物---gt;生物---gt;物体

    狗是一种动物,它是一种生物,反过来又是一种物体。

        20
  •  0
  •   Ravindra babu    7 年前

    您可以在设计模式中找到类继承的好例子。

    1. Abstract_factory_pattern :提供一种方法来封装一组具有共同主题的单个工厂,而不指定它们的具体类。

    2. Template_method_pattern :这是一种行为设计模式,它定义操作中算法的程序框架,将某些步骤延迟到子类。

    3. Decorator_pattern :它是一种设计模式,允许将行为静态或动态地添加到单个对象中,而不影响同一类中其他对象的行为。

    请参阅以下文章了解真实世界的示例:

    When to Use the Decorator Pattern?

    Template design pattern in JDK, could not find a method defining set of methods to be executed in order

        21
  •  -1
  •   Hortitude    15 年前

    我一直用的最好的一个是形状。

    然后你就可以

    Shape --> Quadrilateral
    Quadrilateral --> Rectangle
    Quadrilateral --> Trapezoid
    Rectangle --> Square
    Shape --> Triangle
    

    等。

    方法也很容易猜测。

    Shape::Area
    Shape::Draw
    Shape::Intersect (x,y)
    
        22
  •  -1
  •   arevirlegna    10 年前

    形状层次

    我完全理解关于矩形和正方形之间的关系,关于Liskov的代换原理。然而,有一些方法可以保留这一原则,并且仍然将正方形作为矩形的子类。例如(python),假设如下:

    class Rectangle:
      def __init__(self, w, h):
         self.width = w
         self.height = h
    
      def setWidth(self, neww):
         if neww > 0: self.width = neww
    
      def setHeight(self, newh):
         if newh > 0: self.height = newh
    

    我们可以用以下方式定义正方形:

    class Square(Rectangle):
       def __init__(self, side):
           super().__init__(self, side, side) # width = height = side
    
       def setWidth(self, newside):
           super().setWidth(self, newside)
           super().setHeight(self, newside) # force height to match
    
       def setHeight(self, newside):
           super().setWidth(self, newside)  # force width to match
           super().setHeight(self, newside)
    

    这样,每当需要矩形实例时,我们总是可以使用方形实例,从而保留Liskov的。

    现在,根据我的经验,我们还可以使用继承来执行策略(通常是限制性的)。例如,假设我们有一个具有颜色属性和相应的setter(setcolor)的shape类。

    class Shape:
    
       def __init__(self, ..., color, ...):
            # ...
            self.color = color
            # ...
    
       def setColor(self, newcolor):
            self.color = newcolor
    
       #...
    

    假设我们想要创建一个“红色形状”类。如何防止客户端代码更改颜色?答:通过重写setcolor方法来取消激活它。

    class RedShape(Shape):
       def __init__(self, ...):
          super().__init__(self, 'red', ...)
    
    
       def setColor(self, newcolor):
          pass
    

    我们重新定义了红色的setcolor 没有什么 .

    redshape中的setcolor将覆盖shape中的setcolor,因此客户端代码无法更改redshape实例的颜色。在某种程度上,我们创建了一个更严格的子类,因为我们没有改变颜色的能力,但是它没有违反替换原则。

    当我们有通用(而非抽象)的基类,并且我们创建专门的类来 控制 或者限制基类中可用的功能。您可以创建一系列子类,这些子类越来越多地支持基类中可用的功能,或者您可以创建提供基类中可用功能的不同组合的类。

    在我被激怒之前,让我重申一下,这种用法是一种实用模型,它利用了 控制 . 它与通过加强或改进现有阶级而创造新阶级的浪漫继承观念相去甚远。