Java 为什么要使用继承?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3351666/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why use inheritance at all?
提问by KaptajnKold
I know the question has been discussed before, but it seems always under the assumption that inheritance is at least sometimes preferable to composition. I'd like to challenge that assumption in hopes of gaining some understanding.
我知道之前已经讨论过这个问题,但似乎总是假设继承至少有时比组合更可取。我想挑战这个假设,希望得到一些理解。
My question is this: Sinceyou can accomplish anything with object composition that you can with classical inheritance and sinceclassical inheritance is very often abused[1] and sinceobject composition gives you flexibility to change the delegate object runtime, why the would you everuse classical inheritance?
我的问题是:既然你可以,你可以用经典的继承与对象组合一事无成,自经典的继承是很经常被滥用[1] ,自对象组合为您提供了灵活地改变委托对象运行时,为什么你会永远使用经典继承?
I can sort of understand why you would recommend inheritance in some languages like Java and C++ that do not offer convenient syntax for delegation. In these languages you can save a lot of typing by using inheritance whenever it is not clearly incorrect to do so. But other languages like Objective C and Ruby offer both classical inheritance andvery convienient syntax for delegation. The Go programming language is the only langage that to my knowledge has decided that classical inheritance is more trouble than it's worth and supports only delegation for code reuse.
我可以理解为什么你会推荐在一些语言中使用继承,比如 Java 和 C++,这些语言不提供方便的委托语法。在这些语言中,只要没有明显错误,就可以通过使用继承来节省大量输入。但是其他语言,如 Objective C 和 Ruby,既提供了经典继承,又提供了非常方便的委托语法。据我所知,Go 编程语言是唯一一种语言,它认为经典继承比它的价值更麻烦,并且仅支持代码重用委托。
Another way to state my question is this: Even if you know that classical inheritance is not incorrect to implement a certain model, is that reason enough to use it instead of composition?
表达我的问题的另一种方式是:即使您知道经典继承对于实现某个模型并没有错,但这个理由是否足以使用它代替组合?
[1] Many people use classical inheritance to achieve polymorphism instead of letting their classes implement an interface. The purpose of inheritance is code reuse, not polymorphism. Furthermore, some people use inheritance to model their intuitive understanding of an "is-a" relationship which can often be problematic.
[1] 许多人使用经典继承来实现多态,而不是让他们的类实现接口。继承的目的是代码重用,而不是多态。此外,有些人使用继承来模拟他们对“is-a”关系的直观理解,这通常是有问题的。
Update
更新
I just want to clarify what I mean exactly when I talk about inheritance:
当我谈论继承时,我只想澄清我的意思:
I am talking about the kind of inheritance whereby a class inherits from a partially or fully implemented base class. I am nottalking about inheriting from a purely abstract base class which amounts to the same thing as implementing an interface, which I for the record am not arguing against.
我说的是一种继承,即类继承自部分或完全实现的基类。我不是在谈论从纯粹的抽象基类继承,这相当于实现一个接口,我并不反对。
Update 2
更新 2
I understand that inheritance is the only way to achieve polymorphism i C++. In that case it's obvious why you must use it. So my question is limited to languages such as Java or Ruby that offer distinct ways to achieve polymorphism (interfaces and duck typing, respectively).
我明白继承是实现多态的唯一途径我 C++。在这种情况下,很明显为什么必须使用它。所以我的问题仅限于 Java 或 Ruby 等语言,它们提供了实现多态性的独特方法(分别是接口和鸭子类型)。
采纳答案by dsimcha
If you delegate everything that you haven't explicitly overridden to some other object implementing the same interface (the "base" object), then you've basically Greenspunned inheritance on top of composition, but (in most languages) with a lot more verbosity and boilerplate. The purpose of using composition instead of inheritance is so that you can only delegate the behaviors you want to delegate.
如果您将没有明确覆盖的所有内容委托给实现相同接口的其他对象(“基础”对象),那么您基本上已经在组合之上进行了 Greenspunned 继承,但是(在大多数语言中)更加冗长和样板。使用组合而不是继承的目的是让您只能委托您想要委托的行为。
If you want the object to use all the behavior of the base class unless explicitly overridden, then inheritance is the simplest, least verbose, most straightforward way to express it.
如果您希望对象使用基类的所有行为,除非显式覆盖,那么继承是最简单、最不冗长、最直接的表达方式。
回答by KaptajnKold
The main reason for using inheritance is notas a form of composition - it is so you can get polymorphic behaviour. If you don't need polymorphism, you probably should not be using inheritance, at least in C++.
使用继承的主要原因不是作为一种组合形式 - 它是这样你可以获得多态行为。如果您不需要多态性,您可能不应该使用继承,至少在 C++ 中是这样。
回答by Aaron Digulla
Inheritance is to be preferred if:
在以下情况下优先考虑继承:
- You need to expose the whole API of the class you extend (with delegation, you will need to write lots of delegating methods) andyour language doesn't offer a simple way to say "delegate all unknown methods to".
- You need to access protected fields/methods for languages that have no concept of "friends"
- The advantages of delegation are somewhat reduced if your language allows multi-inheritance
- You usually have no need delegation at all if your language allows to dynamically inherit from a class or even an instance at runtime. You don't need it at all if you can control which methods are exposed (and how they are exposed) at the same time.
- 您需要公开您扩展的类的整个 API(使用委托,您将需要编写大量委托方法)并且您的语言没有提供一种简单的方法来说明“将所有未知方法委托给”。
- 您需要访问没有“朋友”概念的语言的受保护字段/方法
- 如果您的语言允许多重继承,则委托的优势会有所减少
- 如果您的语言允许在运行时从类甚至实例动态继承,您通常根本不需要委托。如果您可以同时控制公开哪些方法(以及如何公开它们),则根本不需要它。
My conclusion: Delegation is a workaround for a bug in a programming language.
我的结论:委托是一种解决编程语言错误的方法。
回答by Frerich Raabe
You wrote:
你写了:
[1] Many people use classical inheritance to achieve polymorphism instead of letting their classes implement an interface. The purpose of inheritance is code reuse, not polymorphism. Furthermore, some people use inheritance to model their intuitive understanding of an "is-a" relationship which can often be problematic.
[1] 许多人使用经典继承来实现多态,而不是让他们的类实现接口。继承的目的是代码重用,而不是多态。此外,有些人使用继承来模拟他们对“is-a”关系的直观理解,这通常是有问题的。
In most languages, the line between 'implementing an interface' and 'deriving a class from another' is very thin. In fact, in languages like C++, if you're deriving a class B from a class A, and A is a class which consists of only pure virtual methods, you areimplementing an interface.
在大多数语言中,“实现一个接口”和“从另一个类派生一个类”之间的界线非常细。事实上,在像 C++ 这样的语言中,如果你从一个类 A 派生出一个类 B,而 A 是一个只包含纯虚方法的类,那么你就是在实现一个接口。
Inheritance is about interface reuse, not implementation reuse. It is notabout code reuse, as you wrote above.
继承是关于接口重用,而不是实现重用。正如您在上面所写的,这与代码重用无关。
Inheritance, as you correctly point out, is meant to model an IS-A relationship (the fact that many people get this wrong has nothing to do with inheritance per se). You can also say 'BEHAVES-LIKE-A'. However, just because something has an IS-A relationship to something else doesn't meant that it uses the same (or even similiar) code to fulfill this relationship.
正如您正确指出的那样,继承旨在模拟 IS-A 关系(许多人弄错了这一事实与继承本身无关)。你也可以说“BEHAVES-LIKE-A”。然而,仅仅因为某事物与其他事物具有 IS-A 关系并不意味着它使用相同(甚至相似)的代码来实现这种关系。
Compare this C++ example which implements different ways to output data; two classes use (public) inheritance so that they can be access polymorphically:
比较这个 C++ 示例,它实现了不同的数据输出方式;两个类使用(公共)继承,以便它们可以多态访问:
struct Output {
virtual bool readyToWrite() const = 0;
virtual void write(const char *data, size_t len) = 0;
};
struct NetworkOutput : public Output {
NetworkOutput(const char *host, unsigned short port);
bool readyToWrite();
void write(const char *data, size_t len);
};
struct FileOutput : public Output {
FileOutput(const char *fileName);
bool readyToWrite();
void write(const char *data, size_t len);
};
Now imagine if this was Java. 'Output' was no struct, but an 'interface'. It might be called 'Writeable'. Instead of 'public Output' you would say 'implements Writable'. What's the difference as far as the design is concerned?
现在想象一下这是不是 Java。“输出”不是结构,而是“接口”。它可能被称为“可写”。你会说“实现可写”而不是“公共输出”。就设计而言,有什么区别?
None.
没有任何。
回答by this. __curious_geek
Interfaces only define what an object can do and not how. So in simple terms interfaces are just contracts. All objects that implement the interface will have to define their own implementation of the contract. In practical world, this gives you separation of concern
. Imagine yourself writing an application that needs to deal with various objects you don't know them in advance, still you need to deal with them, only thing you know is what all different things those objects are supposed to do. So you'll define an interface and mention all operations in the contract. Now you'll write your application against that interface. Later whoever wants to leverage your code or application will have to implement the interface on the object to make it work with your system. Your interface will force their object to define how each operation defined in the contract is supposed to be done. This way anyone can write objects that implement your interface, in order to have them flawlessly adapt to your system and all you know is what needs to be done and it is the object that needs to define how it is done.
接口只定义一个对象可以做什么,而不是如何。所以简单来说,接口就是契约。所有实现接口的对象都必须定义自己的契约实现。在实际世界中,这给了你separation of concern
. 想象一下,您正在编写一个应用程序,它需要处理您事先不知道的各种对象,但您仍然需要处理它们,您唯一知道的是这些对象应该做什么所有不同的事情。因此,您将定义一个接口并在合同中提及所有操作。现在,您将针对该接口编写应用程序。以后任何想要利用您的代码或应用程序的人都必须在对象上实现接口以使其与您的系统一起工作。您的接口将强制他们的对象定义合同中定义的每个操作应该如何完成。通过这种方式,任何人都可以编写实现您的接口的对象,以便让它们完美地适应您的系统,您只知道需要做什么,而对象需要定义如何完成。
In real-world development this practice is generally known as
Programming to Interface and not to Implementation
.Interfaces are just contracts or signatures and they don't know anything about implementations.
在现实世界的开发中,这种做法通常被称为
Programming to Interface and not to Implementation
.接口只是合同或签名,他们对实现一无所知。
Coding against interface means, the client code always holds an Interface object which is supplied by a factory. Any instance returned by the factory would be of type Interface which any factory candidate class must have implemented. This way the client program is not worried about implementation and the interface signature determines what all operations can be done. This can be used to change the behavior of a program at run-time. It also helps you to write far better programs from the maintenance point of view.
针对接口编码意味着,客户端代码始终持有一个由工厂提供的接口对象。工厂返回的任何实例都属于任何工厂候选类必须实现的接口类型。这样客户端程序就不用担心实现了,接口签名决定了所有的操作可以做什么。这可用于在运行时更改程序的行为。从维护的角度来看,它还可以帮助您编写更好的程序。
Here's a basic example for you.
这是一个基本的例子。
public enum Language
{
English, German, Spanish
}
public class SpeakerFactory
{
public static ISpeaker CreateSpeaker(Language language)
{
switch (language)
{
case Language.English:
return new EnglishSpeaker();
case Language.German:
return new GermanSpeaker();
case Language.Spanish:
return new SpanishSpeaker();
default:
throw new ApplicationException("No speaker can speak such language");
}
}
}
[STAThread]
static void Main()
{
//This is your client code.
ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
speaker.Speak();
Console.ReadLine();
}
public interface ISpeaker
{
void Speak();
}
public class EnglishSpeaker : ISpeaker
{
public EnglishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak English.");
}
#endregion
}
public class GermanSpeaker : ISpeaker
{
public GermanSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak German.");
}
#endregion
}
public class SpanishSpeaker : ISpeaker
{
public SpanishSpeaker() { }
#region ISpeaker Members
public void Speak()
{
Console.WriteLine("I speak Spanish.");
}
#endregion
}
回答by C Johnson
When you asked:
当你问:
Even if you know that classical inheritance is not incorrect to implement a certain model, is that reason enough to use it instead of composition?
即使你知道经典继承对于实现某个模型并没有错,那么使用它代替组合的理由就足够了吗?
The answer is no. If the model is incorrect (using inheritance), than it's wrong to use no matter what.
答案是不。如果模型不正确(使用继承),那么无论如何使用都是错误的。
Here are some problems with inheritance that I've seen:
以下是我见过的一些继承问题:
- Always having to test the run time type of derived class pointers to see if they can be cast up (or down too).
- This 'testing' can be achieved in various ways. You may have some sort of virtual method that returns a class identifier. Or failing that you may have to implement RTTI (Run time type identification) (At least in c/c++) which can give you a performance hit.
- class types that fail to get 'cast' up can be potentially problematic.
- There are many ways to cast your class type up and down the inheritance tree.
- 总是必须测试派生类指针的运行时类型,以查看它们是否可以向上(或向下)强制转换。
- 这种“测试”可以通过多种方式实现。您可能有某种返回类标识符的虚拟方法。或者,您可能必须实现 RTTI(运行时类型识别)(至少在 c/c++ 中),这可能会给您带来性能损失。
- 无法“强制转换”的类类型可能存在潜在问题。
- 有很多方法可以在继承树中上下转换您的类类型。
回答by dsimcha
What about the template method pattern? Let's say you have a base class with tons of points for customizable policies, buta strategy pattern doesn't make sense for at least one of the following reasons:
那么模板方法模式呢?假设您有一个包含大量可自定义策略点的基类,但至少由于以下原因之一,策略模式没有意义:
The customizable policies need to know about the base class, can only be used with the base class and don't make sense in any other context. Using strategy instead is do-able but a PITA because both the base class and the policy class need to have references to each other.
The policies are coupled to each other in that it wouldn't make sense to freely mix-and-match them. They only make sense in a very limited subset of all possible combinations.
可定制的策略需要了解基类,只能与基类一起使用,在任何其他上下文中都没有意义。改用策略是可行的,但需要 PITA,因为基类和策略类都需要相互引用。
这些策略相互耦合,因为自由混合和匹配它们是没有意义的。它们仅在所有可能组合的非常有限的子集中有意义。
回答by ChaosPandion
I always think twice before using inheritance as it can get tricky fast. That being said there are many cases where it simply produces the most elegant code.
在使用继承之前,我总是三思而后行,因为它很快就会变得棘手。话虽如此,但在很多情况下,它只是生成最优雅的代码。
回答by Powerlord
Classical inheritance's main usefulness is if you have a number of related classes that will have identical logic for methods that operate on instance variables/properties.
经典继承的主要用途是,如果您有许多相关的类,这些类对于对实例变量/属性进行操作的方法具有相同的逻辑。
There are really 3 ways to handle that:
真的有 3 种方法来处理:
- Inheritance.
- Duplicate the code (code smell"Duplicated code").
- Move the logic to yet another class (code smells "Lazy Class," "Middle Man," "Message Chains," and/or "Inappropriate Intimacy").
- 遗产。
- 复制代码(代码味道“重复的代码”)。
- 将逻辑移到另一个类(代码有“懒惰类”、“中间人”、“消息链”和/或“不适当的亲密关系”的味道)。
Now, there can be misuse of inheritance. For example, Java has the classes InputStream
and OutputStream
. Subclasses of these are used to read/write files, sockets, arrays, strings, and several are used to wrap other input/output streams. Based on what they do, these should have been interfaces rather than classes.
现在,可能会滥用继承。例如,Java 具有类InputStream
和OutputStream
. 它们的子类用于读/写文件、套接字、数组、字符串,还有几个用于包装其他输入/输出流。根据他们的工作,这些应该是接口而不是类。
回答by Brad Barker
One of the most useful ways I see to use inheritance is in GUI objects.
我看到使用继承的最有用的方法之一是在 GUI 对象中。