我正在编写面向对象语言的文档,我想知道哪种类是继承的好例子。
一些常见的例子:
class Person {
}
class Employee extends Person {
}
目前我最喜欢的,但我不喜欢Person-> Employee,因为'Employee'看起来不太有趣。
class Bicycle {
}
class MountainBike extends Bicycle {
}
我在一些Java tutorial中发现了这一点,但是自行车的属性并不是很明显。
class Animal {
}
class Bird extends Animal {
}
与自行车相同。
class A {
}
class B extends A {
}
太抽象了。主要问题是这样的类需要更抽象的属性和方法。
有没有人有一个简单的类层次结构的更好的例子?
答案 0 :(得分:19)
我喜欢Stream
层次结构。我们的想法是,任何东西都可以使用流,而无需关心它是什么类型的流,并且各个子类以不同方式处理存储(例如.NET中的NetworkStream
,MemoryStream
和FileStream
。
如果您对接口感兴趣,那么.NET中的IEnumerable<T>
就是一个很好的接口 - 您可以迭代任何集合而无需关心底层数据结构。
答案 1 :(得分:18)
汽车零件可能很有趣,例如你可能有
class part
{
OEM
Manufacturer
Number
Description
}
class Tire extends Part
{
Speed
Rating
}
答案 2 :(得分:17)
我同意Jon Skeet关于他的溪流的例子。也许它并不完美,但它比这里的大多数例子都有一个优势:
这是现实的
自行车,人或动物,形状或武器不会在实际项目中通过继承来建模。 (特别是形状,非常危险,因为它不起作用。)
这是我遗憾的宠儿。它通常被教导为必须用于表达您可以找到的每个层次结构的东西。员工是一个人,对吧?所以Employee类必须从Person类继承。但是一个人也是一个LivingCreature,所以我们最好也有一个这样的课程。 LivingCreature也是一个生物,所以我们有另一个类。有机体就是......随时可以继续。
我认为,如果有人在某个地方实际上通过解释何时应该使用继承来教导继承,而不仅仅是如何在任何层级上强制它,无论它是否有益,都会很好。
Streams(或ChrisW示例中的设备)具有以下优势: sense 。您希望能够将所有流处理为相同,无论它们是连接到内存缓冲区,文件还是网络套接字。并且所有硬件设备都有许多共同的行为,这些行为可能会被合理地分解为设备基类。
答案 3 :(得分:12)
由于种种原因,Animal类是类继承的典型示例。
首先,有明显的方法来扩展潜在的动物类。你可能会从哺乳动物,鸟类,甲壳动物等子类开始。
某些类,如Mammal,将通过添加相当明显的属性(“Warm-Blooded”等)来扩展Animal。
如果用动物继承来说明,那么在开发类层次结构时非常常见的其他更有问题的问题是非常明显的 - 出于解释的目的,这是一个好的事物。鸟飞,对吗?好吧,不是所有的鸟......所以,你怎么代表飞行?当然,网上有关于如何解决每个解决方案引入的问题和权衡的经典解决方案和大量讨论信息。
因此,我强烈建议使用“动物”作为你的例子,因为有丰富的可用信息和例子。
答案 4 :(得分:9)
很多人使用Shapes示例,但实际上这是一个危险的例子。当您直观地确定正方形是矩形的子类时,就会出现问题。
当谈到行为时,正方形比矩形更有限,破坏了可替代性。例如,我们可以要求一个矩形对象来改变它的高度。如果一个正方形是矩形的子类,那意味着我们应该能够问一个正方形。但是,改变正方形的高度意味着它不再是正方形!当然,我们可以相应地增加宽度,但是当我们要求一个声明类型矩形的对象(实际上是下面的正方形)来改变其高度时,这不是我们所期望的。
它被称为Liskov substitution principle,你在进行任何严肃的OO开发时都应该注意它。
正方形当然是矩形的集,而不是子类。这是面向数据的方法和面向行为的方法之间的区别。
像Jon一样,我更喜欢Streams。即使对非程序员来说,也不难解释它的清晰行为导向,避免形状的反直觉 - 例子。
答案 5 :(得分:8)
如果您正在进行视频游戏,可能会是这样的:
class enemy{
Health
Posx
posy
Etc
}
class orc : extends enemy{
speed
Strength
etc
}
答案 6 :(得分:6)
代数表达式的层次结构怎么样?这是一个很好的例子,因为它同时使用了继承和组合:
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
答案 7 :(得分:5)
我认为Shape是一个很好的抽象类。有2D和3D形状。 2D形状通常具有面积,而3D形状具有体积。两者都可以有“位置”或“群众中心”。
一些建议:
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 {...}
答案 8 :(得分:4)
作为反击游戏的崇拜者,这是我想要分享的内容:
答案 9 :(得分:3)
我用来展示国际象棋棋子。基类是一般的ChessPiece,King,Rook,Bishop等继承。
我喜欢这个例子:
答案 10 :(得分:3)
我建议'设备'。没有人真正使用软件对动物进行建模,但他们确实使用模型设备。
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)
我一直很喜欢:
class Shape {
}
class Square extends Shape {
}
但你引用的前三名中的任何一个都没问题。 MountainBike听起来最令人兴奋。当然,你可以用汽车做类似的事情。
答案 12 :(得分:1)
网上商店也是一个不错的选择。
Product(id, title, price, description)
Book(isbn, publisher, pageNr) extends Product
CD(company, duration, genre, taskList) extends Product
答案 13 :(得分:1)
我喜欢使用Printers作为例子。假设您正在为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方法是一个很好的抽象候选者。子类需要实现自己的初始化过程。
您还可以扩展此示例并开始演示Interfaces和Composition的正确用法。 IS-A和HAS-A概念有什么区别?某些打印机可能具有WiFi和蓝牙功能。或者,某些InkJet打印机可能具有扫描功能,而其他打印机仅具有馈线等。您将如何实现此功能?
我认为使用打印机通常与计算机和计算机科学关系更密切。您可以使用此示例,甚至进一步演示处理嵌入式系统与PC,SmartPhones和其他IoT之间网络的示例。
答案 14 :(得分:0)
示例:
“一切都来自对象”方法。
狗 - &gt;动物 - &gt;生物 - &gt;对象
一只狗是一种动物,它是一种生物,它又是一种物体。
答案 15 :(得分:0)
我遇到的最好的例子(并在许多书中阅读)是使用Shape的那个。
关于这一点最好的事情是,你可以很容易地向任何与他的经历无关的程序员解释与类,对象,继承,抽象,封装,多态等OOP相关的所有概念(包括艰难的概念)。 / p>
答案 16 :(得分:0)
继承可能很复杂。让我们从最简单的'em all - 行为继承开始。在这里,您只是继承行为而没有状态。例如:Person和Animal都是具有IsAlive行为的Animate对象。要使用您的示例:
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 ++语法编写了这个,如果您在理解其中任何一个时遇到问题,就这么说吧!
答案 17 :(得分:0)
怎么样?
class Weapon
{
}
class Gun : extends Weapon
{
}
class Knife : extends Weapon
{
}
等。
答案 18 :(得分:0)
我喜欢车辆示例,因为它允许相对干净的扩展包括讨论的接口(IAutomaticGearbox,任何人?)
答案 19 :(得分:0)
您可以在设计模式中找到类继承的好例子。
Abstract_factory_pattern:提供一种封装一组具有共同主题但没有指定其具体类的单个工厂的方法
Template_method_pattern:这是一种行为设计模式,用于定义操作中算法的程序框架,将一些步骤推迟到子类。
Decorator_pattern:这是一种设计模式,允许将行为静态或动态地添加到单个对象,而不会影响同一类中其他对象的行为
请参阅以下帖子了解真实世界的例子:
答案 20 :(得分:-1)
形状层次结构
我完全理解矩形和正方形之间关于Liskov替换原理的关系。但是,有一些方法可以保留这个原则,并且Square仍然是Rectangle的子类。例如(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
我们可以通过以下方式定义Square:
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)
这样我们总是可以在需要Rectangle实例时使用Square实例,从而保留了Liskov。
现在,根据我的经验,我们还可以使用继承来实施策略(通常是限制性的)。例如,假设我们有一个带有color属性的Shape类和相应的setter(setColor)。
class Shape:
def __init__(self, ..., color, ...):
# ...
self.color = color
# ...
def setColor(self, newcolor):
self.color = newcolor
#...
假设我们要创建一个“RedShape”类。我们如何防止客户端代码改变颜色?答案:通过覆盖它来取消激活setColor方法。
class RedShape(Shape):
def __init__(self, ...):
super().__init__(self, 'red', ...)
def setColor(self, newcolor):
pass
我们在RedShape中重新定义了setColor来执行 nothing 。
RedShape中的setColor会覆盖Shape中的setColor,因此客户端代码无法更改RedShape实例的颜色。在某种程度上,我们创建了一个限制性更强的子类,因为我们没有能力改变它的颜色,但它没有违反替换原则。
当我们拥有通用(非抽象)的基类并且我们为控制创建专用类或限制基类中可用的功能时,此模型非常有用。您可以创建一个子类链,逐渐增加基类中可用的功能,或者您可以创建提供基类中可用功能的不同组合的类。
在我受到抨击之前,让我重申一下,这种用法是一种实用的模型,它利用了控制的继承机制和多态性。它与通过增强或改进现有类别来创造新类别的浪漫概念相去甚远。
答案 21 :(得分:-1)
我一直使用的最好的是形状。
然后你可以
Shape --> Quadrilateral
Quadrilateral --> Rectangle
Quadrilateral --> Trapezoid
Rectangle --> Square
Shape --> Triangle
等
方法也很容易猜到。
Shape::Area
Shape::Draw
Shape::Intersect (x,y)