What's a good example for class inheritance? [

2020-02-08 12:29发布

问题:


Want to improve this question? Update the question so it's on-topic for Stack Overflow.

Closed 2 years ago.

I'm writing documentation for an object-oriented language, and I wonder what kind of classes would be a good example for inheritance.

Some common examples:

class Person {
}
class Employee extends Person {
}

Currently my favorite, but I don't like Person->Employee because 'Employee' does not exactly look like fun.

class Bicycle {
}
class MountainBike extends Bicycle {
}

I found this in some Java tutorial, but it's not really obvious what attributes a bike should have.

class Animal {
}
class Bird extends Animal {
}

Same as the bicycle.

class A {
}
class B extends A {
}

Too abstract. The main problem is that such a class will need even more abstract attributes and methods.

Does anyone have a better example for a simple class hierarchy?

回答1:

The Animal class is the classic example of class inheritance for a number of reasons.

First, there are obvious ways to extend the underlying animal class. You'll likely start with sub-classes such as Mammal, Bird, Crustacean, etc.

Some classes, such as Mammal, will extend Animal by adding attributes that are fairly obvious ("Warm-Blooded", etc.).

Other, more problematic, issues that are quite common in developing a class hierarchy are quite obvious if you illustrate with animal inheritance - and this is a good thing for purposes of explanation. Birds fly, right? Well, not all birds... So, how do you represent flying? There are, of course, classic solutions and a wealth of discussion information online about how to solve the problems and tradeoffs that each solution introduces.

Thus, I would highly recommend using "Animal" as your example because of the richness of available information and examples.



回答2:

I like the Stream hierarchy. The idea is that anything can use a stream without usually caring what kind of stream it is, and individual subclasses handle the storage differently (e.g. NetworkStream, MemoryStream and FileStream in .NET).

If you're interested in interfaces, then IEnumerable<T> in .NET is a great one - you can iterate over any collection without caring what the underlying data structure is.



回答3:

I agree with Jon Skeet on his streams example. Perhaps it's not perfect, but it has one advantage over most of of the examples here:

It is realistic

Bicycles, persons or animals, shapes or weapons just wouldn't be modelled by inheritance in real projects. (shapes in particular, are downright dangerous, because it doesn't work.)

That's my pet peeve with inheritance. It is too often taught as something that must be used to express every hierarchy you can find. An employee is a person, right? So the Employee class must inherit from a Person class. But a person is also a LivingCreature, so we'd better have one of those classes too. And a LivingCreature is also an Organism, so there we have another class. And an Organism is.... feel free to continue.

I think it'd be nice if someone, somewhere, actually taught inheritance by explaining when it should be used, and not just how you can force it down over any hierarchy, whether it's beneficial or not.

Streams (or devices as in ChrisW's example) have the advantage that they make sense. You want to be able to treat all streams the same, whether they're connected to a memory buffer, a file or a network socket. And all hardware devices do have a lot of behavior in common that could plausibly be factored out into a Device base class.



回答4:

Auto Parts can be interesting for example you might have

class part
{
    OEM
    Manufacturer
    Number
    Description
}

class Tire extends Part
{
   Speed
   Rating

}


回答5:

Many people use the Shapes example, but that is in fact a dangerous one. The problem arises when you intuitively decide that a square is a subclass of rectangle.

When it comes to behavior, a square is more limited than a rectangle, breaking substitutability. For example, we could ask a rectangle object to change its height. If a square is a subclass of rectangle, that means we should be able to ask the same of a square. However, changing the height of a square would mean its not a square anymore! Of course, we could increase the width accordingly, but that's not what we would expect when we were to ask an object of declared type rectangle, which is actually a square underneath, to change its height.

It's called the Liskov substitution principle, and you should be aware of it when doing any serious OO development.

Squares are, of course a subset of rectangles, instead of a subclass. This is the difference between data-oriented and behaviour-oriented approaches.

Like Jon, I prefer Streams as an example. It's not difficult to explain, even to non-programmers, and its cleary behaviour-oriented, avoiding the counter-intuitivity of the shapes-example.



回答6:

If you are into video games could be something like this:

class enemy{
  Health
  Posx
  posy
  Etc
}

class orc : extends enemy{
 speed
 Strength
 etc
}


回答7:

What about a hierarchy of algebraic expressions. It is an excellent example because it uses both inheritance and composition:

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);
        }
    }

}

Then you can provide a motivating example like:

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());

}

Which would yield:

(  ( ( 2  +  4 )  *  - 1  )  ^ 2 +  1 )  = 37


回答8:

I think Shape is a good abstract class. There are both 2D and 3D shapes. The 2D shapes typically have area while 3D shapes have volume. Both can have a "location" or "mass center".

Some suggestions:

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:

Being a counter-strike game admirer this is what I would love to share:



回答10:

I suggest 'devices'. Nobody really models animals using software, but they do model devices.

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:

I use to show Chess pieces. The base class is the general ChessPiece, King, Rook, Bishop, etc inherits.

What I like about this example:

  • it is real, if you implement chess, this is a solution
  • in C++ you can show which function is virtual and which is not
  • abstract class can be shown
  • advanced example may develop towards common base class for directional pieces (Rook and Bishop) or even further to generalized directional pieces with step limit (everything but Pawn)


回答12:

Webshop could be a good option as well.

Product(id, title, price, description)
Book(isbn, publisher, pageNr) extends Product
CD(company, duration, genre, taskList) extends Product


回答13:

I always was fond of:

class Shape {
}
class Square extends Shape {
}

But any of the top three you quote would be fine. MountainBike sounds the most exciting. You can do similar things with cars of course.



回答14:

I like the idea of using Printers as an example. Say you're working for HP, Brother, Lexmark, or some other entity, and you need to come up with a program that is capable of generating driver specific modules for a variety of operating systems.

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.
}

After demonstrating inheritance you can move towards abstraction and method overload/override. The init method in Printer class is a good candidate for abstraction. Subclasses will need to implement their own initialization processes.

You can also extend this example and start demonstrating proper usage of Interfaces and Composition. What is the difference between a IS-A and HAS-A concept? Some printers may have WiFi and Bluetooth capabilities. Or, Some of the InkJet printers may have scanning capabilities while other only have feeders etc. How would you implement this?

I think using printers is more closely related to computers and computer science in general. You can use this example and even further demonstrate examples that deal with networking between embedded systems and PC's, SmartPhones, and other IoT's.



回答15:

I like the vehicles example, as it allows for a relatively clean extension to include interfaces into the discussion (IAutomaticGearbox, anyone?)



回答16:

What about

class Weapon 
{
}

class Gun : extends Weapon
{
}

class Knife : extends Weapon
{
}

et.



回答17:

Inheritance can be complex. Let's start with the simplest of 'em all -- behavior inheritance. Here you are inheriting behavior only and no state. E.g: Both Person and Animal are Animate objects which exhibit an IsAlive behavior. To use your example:

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();
};

I wrote this using C++ syntax, if you have problems understanding any of it, just say so!



回答18:

The best one I have always used has been Shapes.

You can then have

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

etc.

Methods are then also easy to guess at.

Shape::Area
Shape::Draw
Shape::Intersect (x,y)


回答19:

The best example that i have came across (and read in many books) is the one that uses Shape.

The best thing about this is that you can very easily explain all the concepts (including the tough ones ) related to OOPs like Class,Object,Inheritance,Abstraction,Encapsulation,Polymorphism,etc to any programmer irrelevant of his experience.



回答20:

Example:

The "Everything derives from Object" approach.

Dog --> Animal --> Living thing --> Object

A Dog is an Animal, which is a Living thing, which in turn is an Object.



回答21:

You can find good examples of class inheritance in design-patterns.

  1. Abstract_factory_pattern : Provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete class

  2. Template_method_pattern: It is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses.

  3. Decorator_pattern: It is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class

Refer to below posts for real world examples:

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



回答22:

Hierarchy of Shapes

I completely understand the concerns regarding the relationship between Rectangles and Squares regarding Liskov's substitution principle. However, there are ways to preserve this principle and still have Square as subclass of Rectangle. For example (Python), assume the following:

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

We can define Square the following way:

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)

This way we can always use an Square instance whenever a Rectangle instance is needed, thus preserving Liskov's.

Now, from my experience, we can also use Inheritance to enforce a policy (usually restrictive). For example, assume we have a Shape class with a color property and corresponding setter (setColor).

class Shape:

   def __init__(self, ..., color, ...):
        # ...
        self.color = color
        # ...

   def setColor(self, newcolor):
        self.color = newcolor

   #...

Assume we want to create a "RedShape" class. How can we prevent client code from changing the color? Answer: de-active the setColor method by overriding it.

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


   def setColor(self, newcolor):
      pass

We redefined setColor in RedShape to do nothing.

The setColor in RedShape overrides the one in Shape so client code cannot change the color of RedShape instances. In a way, we created a more restrictive subclass because we do not have the ability of changing its color, but it does not violate the substitution principle.

This model is useful when we have base classes that are generic (not abstract) and we create specialized classes to control or limit the features available in the base class. You can create a chain of subclasses increasingly enabling features available in the base class, or you can create classes that offer different combinations of features available in the base class.

Before I get flamed, let me reiterate that this usage is a pragmatic model that takes advantage of the mechanism of inheritance and polymorphism for control. It is far removed from the romantic notion of inheritance of creating new classes by enhancing or refining existing ones.