What's a good example for class inheritance? [

2020-02-08 12:46发布

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?

22条回答
冷血范
2楼-- · 2020-02-08 13:02

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)
查看更多
Deceive 欺骗
3楼-- · 2020-02-08 13:02

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.

查看更多
Evening l夕情丶
4楼-- · 2020-02-08 13:03

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.

查看更多
干净又极端
5楼-- · 2020-02-08 13:04

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.

查看更多
叼着烟拽天下
6楼-- · 2020-02-08 13:04

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)
查看更多
老娘就宠你
7楼-- · 2020-02-08 13:05

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.

查看更多
登录 后发表回答