组合和继承是一样的吗?如果我想实现组合模式,我该如何在 Java 中做到这一点?
17 回答
它们完全不同。继承是一种“is-a”关系。组合是一个“有”。
您可以通过将另一个类的实例C
作为类的字段来进行组合,而不是扩展C
. 组合比继承要好得多的一个很好的例子是java.util.Stack
,它目前是扩展java.util.Vector
的。这现在被认为是一个错误。堆栈“is-NOT-a”向量;你不应该被允许任意插入和删除元素。它应该是合成的。
不幸的是,纠正这个设计错误为时已晚,因为现在更改继承层次结构会破坏与现有代码的兼容性。使用Stack
组合而不是继承,它总是可以修改为使用另一个数据结构而不会违反 API。
我强烈推荐 Josh Bloch 的《Effective Java 2nd Edition 》一书
- 第 16 条:优先组合而不是继承
- 第 17 项:设计和文件继承或禁止继承
好的面向对象设计不是自由扩展现有的类。你的第一直觉应该是作曲。
也可以看看:
组合意味着HAS A
继承意味着IS A
Example
: 汽车有引擎,汽车是汽车
在编程中,这表示为:
class Engine {} // The Engine class.
class Automobile {} // Automobile class which is parent to Car class.
class Car extends Automobile { // Car is an Automobile, so Car class extends Automobile class.
private Engine engine; // Car has an Engine so, Car class has an instance of Engine class as its member.
}
继承怎么可能是危险的?
举个例子
public class X{
public void do(){
}
}
Public Class Y extends X{
public void work(){
do();
}
}
1) 如上代码所示,Y 类与 X 类具有很强的耦合性。如果超类 X 发生任何变化,Y 可能会急剧中断。假设在未来类 X 实现了一个具有以下签名的方法
public int work(){
}
更改在 X 类中完成,但会使 Y 类无法编译。所以这种依赖可以上升到任何水平,而且非常危险。每次超类可能无法完全了解其所有子类中的代码,并且子类可能一直在关注超类中发生的事情。所以我们需要避免这种强耦合和不必要的耦合。
组合如何解决这个问题?
让我们通过修改相同的示例来看看
public class X{
public void do(){
}
}
Public Class Y{
X x = new X();
public void work(){
x.do();
}
}
这里我们在 Y 类中创建 X 类的引用,并通过创建 X 类的实例来调用 X 类的方法。现在所有的强耦合都消失了。超类和子类现在高度独立。类可以自由地进行在继承情况下危险的更改。
2)组合的第二个非常好的优势在于它提供了方法调用的灵活性,例如:
class X implements R
{}
class Y implements R
{}
public class Test{
R r;
}
在使用 r 引用的测试类中,我可以调用 X 类和 Y 类的方法。这种灵活性在继承中从未存在
3)另一个很大的优势:单元测试
public class X {
public void do(){
}
}
Public Class Y {
X x = new X();
public void work(){
x.do();
}
}
在上面的示例中,如果 x 实例的状态未知,则可以通过使用一些测试数据轻松模拟它,并且可以轻松测试所有方法。这在继承中根本不可能,因为您严重依赖超类来获取实例的状态并执行任何方法。
4)我们应该避免继承的另一个好理由是Java不支持多重继承。
让我们举个例子来理解这一点:
Public class Transaction {
Banking b;
public static void main(String a[])
{
b = new Deposit();
if(b.deposit()){
b = new Credit();
c.credit();
}
}
}
很高兴知道 :
组合在运行时很容易实现,而继承在编译时提供其功能
组合也称为HAS-A关系,继承也称为IS-A关系
因此,出于上述各种原因,请养成始终喜欢组合而不是继承的习惯。
@Michael Rodrigues 给出的答案不正确(我很抱歉;我无法直接发表评论),并可能导致一些混乱。
接口实现是继承的一种形式……当你实现一个接口时,你不仅继承了所有的常量,而且你将你的对象提交为接口指定的类型;它仍然是一种“ is-a ”关系。如果汽车实现了Fillable,那么汽车“ is-a ” Fillable,并且可以在您的代码中使用Fillable的任何地方。
组合从根本上不同于继承。当您使用组合时,您(正如其他答案所指出的)在两个对象之间建立“ has-a ”关系,而不是在使用时建立的“ is-a ”关系继承。
因此,从其他问题中的汽车示例中,如果我想说汽车“有”油箱,我会使用组合,如下所示:
public class Car {
private GasTank myCarsGasTank;
}
希望这能消除任何误解。
继承带来了IS-A关系。组合带出HAS-A关系。策略模式解释了组合应该用于定义特定行为的算法家族的情况。
经典示例是实现飞行行为的鸭类。
public interface Flyable{
public void fly();
}
public class Duck {
Flyable fly;
public Duck(){
fly = new BackwardFlying();
}
}
因此我们可以有多个实现飞行的类,例如:
public class BackwardFlying implements Flyable{
public void fly(){
Systemout.println("Flies backward ");
}
}
public class FastFlying implements Flyable{
public void fly(){
Systemout.println("Flies 100 miles/sec");
}
}
如果是为了继承,我们将有两种不同的鸟类,它们一遍又一遍地实现飞行功能。所以继承和组合是完全不同的。
组合就像听起来一样 - 您通过插入零件来创建一个对象。
编辑此答案的其余部分错误地基于以下前提。
这是通过接口完成的。
例如,使用Car
上面的示例,
Car implements iDrivable, iUsesFuel, iProtectsOccupants
Motorbike implements iDrivable, iUsesFuel, iShortcutThroughTraffic
House implements iProtectsOccupants
Generator implements iUsesFuel
因此,通过一些标准的理论组件,您可以构建您的对象。然后,您的工作就是填写 a 如何House
保护其乘员,以及 a 如何Car
保护其乘员。
继承就像反过来。您从一个完整(或半完整)的对象开始,然后替换或覆盖您想要更改的各个位。
例如,MotorVehicle
可能会附带一个Fuelable
方法和Drive
方法。您可以保留 Fuel 方法,因为它对摩托车和汽车进行加油是相同的,但您可以覆盖该Drive
方法,因为 Motorbike 的驱动方式与Car
.
通过继承,一些类已经完全实现,而另一些类则具有您被迫重写的方法。有了Composition,什么都没有给你。(但如果你碰巧有一些东西,你可以通过调用其他类中的方法来实现接口)。
组合被认为更灵活,因为如果你有一个像 iUsesFuel 这样的方法,你可以在其他地方(另一个类,另一个项目)有一个方法,它只担心处理可以燃料的对象,不管它是否是汽车,船、炉子、烧烤等。接口要求那些说它们实现了该接口的类实际上具有该接口所涉及的方法。例如,
iFuelable Interface:
void AddSomeFuel()
void UseSomeFuel()
int percentageFull()
那么你可以在其他地方有一个方法
private void FillHerUp(iFuelable : objectToFill) {
Do while (objectToFill.percentageFull() <= 100) {
objectToFill.AddSomeFuel();
}
奇怪的例子,但它表明这个方法并不关心它在填充什么,因为对象实现了iUsesFuel
,它可以被填充。故事结局。
如果您改用继承,则需要不同的FillHerUp
方法来处理MotorVehicles
and Barbecues
,除非您有一些相当奇怪的“ObjectThatUsesFuel”基础对象可以从中继承。
组合和继承是一样的吗?
他们不一样。
组合:它使一组对象必须以与对象的单个实例相同的方式处理。组合的目的是将对象“组合”成树结构以表示部分整体层次结构
继承:一个类从它的所有超类继承字段和方法,无论是直接的还是间接的。子类可以覆盖它继承的方法,也可以隐藏它继承的字段或方法。
如果我想实现组合模式,我该如何在 Java 中做到这一点?
维基百科文章足以在 java 中实现复合模式。
主要参与者:
组件:
- 是所有组件的抽象,包括复合组件
- 声明组合中对象的接口
叶:
- 表示组合中的叶对象
- 实现所有组件方法
复合:
- 表示复合组件(具有子组件的组件)
- 实现方法来操纵孩子
- 实现所有 Component 方法,通常通过将它们委托给其子级
理解复合模式的代码示例:
import java.util.List;
import java.util.ArrayList;
interface Part{
public double getPrice();
public String getName();
}
class Engine implements Part{
String name;
double price;
public Engine(String name,double price){
this.name = name;
this.price = price;
}
public double getPrice(){
return price;
}
public String getName(){
return name;
}
}
class Trunk implements Part{
String name;
double price;
public Trunk(String name,double price){
this.name = name;
this.price = price;
}
public double getPrice(){
return price;
}
public String getName(){
return name;
}
}
class Body implements Part{
String name;
double price;
public Body(String name,double price){
this.name = name;
this.price = price;
}
public double getPrice(){
return price;
}
public String getName(){
return name;
}
}
class Car implements Part{
List<Part> parts;
String name;
public Car(String name){
this.name = name;
parts = new ArrayList<Part>();
}
public void addPart(Part part){
parts.add(part);
}
public String getName(){
return name;
}
public String getPartNames(){
StringBuilder sb = new StringBuilder();
for ( Part part: parts){
sb.append(part.getName()).append(" ");
}
return sb.toString();
}
public double getPrice(){
double price = 0;
for ( Part part: parts){
price += part.getPrice();
}
return price;
}
}
public class CompositeDemo{
public static void main(String args[]){
Part engine = new Engine("DiselEngine",15000);
Part trunk = new Trunk("Trunk",10000);
Part body = new Body("Body",12000);
Car car = new Car("Innova");
car.addPart(engine);
car.addPart(trunk);
car.addPart(body);
double price = car.getPrice();
System.out.println("Car name:"+car.getName());
System.out.println("Car parts:"+car.getPartNames());
System.out.println("Car price:"+car.getPrice());
}
}
输出:
Car name:Innova
Car parts:DiselEngine Trunk Body
Car price:37000.0
解释:
- 部分是一片叶子
- 汽车包含许多零件
- 汽车的不同零件已添加到汽车
- 汽车的价格=(各部分的价格)之和
有关组合和继承的优缺点,请参阅以下问题。
再举一个例子,考虑一个汽车类,这将是一个很好的组合使用,汽车将“拥有”发动机、变速器、轮胎、座椅等。它不会扩展这些类中的任何一个。
组合是指事物由不同的部分组成,并且与这些部分有很强的关系。如果主要部分死了,其他部分也死了,他们就无法拥有自己的生活。一个粗略的例子是人体。取出心脏,所有其他部分都消失了。
继承是您只需获取已经存在的东西并使用它。没有牢固的关系。一个人可以继承他父亲的遗产,但他可以没有它。
我不懂Java,所以我不能提供一个例子,但我可以解释一下这些概念。
在简单的单词聚合中意味着有关系..
组合是聚合的一种特殊情况。在更具体的方式中,受限制的聚合称为组合。当一个对象包含另一个对象时,如果所包含的对象没有容器对象的存在就不能存在,则称为组合。 示例:一个班级包含学生。没有班级,学生就无法存在。班级和学生之间存在着组合。
为什么使用聚合
代码可重用性
何时使用聚合
当没有 Relationship 时,代码重用也最好通过聚合来实现
遗产
继承是父子关系继承意味着是关系
Java中的继承是一种机制,其中一个对象获取父对象的所有属性和行为。
在 Java 1 代码可重用性中使用继承。2 在子类中添加额外功能以及方法覆盖(因此可以实现运行时多态性)。
两个类之间的继承,其中一个类扩展另一个类建立“ IS A ”关系。
组合在另一端包含另一个类的实例,在你的类中建立“ Has A ”关系。java中的组合很有用,因为它在技术上促进了多重继承。
我认为这个例子清楚地解释了继承和组合之间的区别。
在这个例子中,这个问题是通过继承和组合来解决的。作者注意到了这样一个事实;在继承中,超类的更改可能会导致继承它的派生类出现问题。
当您使用 UML 进行继承或组合时,您还可以在此处看到表示的差异。
尽管 Inheritance 和 Composition 都提供代码可重用性,但 Java 中的 Composition 和 Inheritance 之间的主要区别在于,Composition 允许在不扩展代码的情况下重用代码,但是对于 Inheritance,您必须扩展类才能重用代码或功能。这个事实的另一个区别是,通过使用组合,您甚至可以为不可扩展的最终类重用代码,但在这种情况下,继承不能重用代码。此外,通过使用组合,您可以重用来自许多类的代码,因为它们被声明为只是一个成员变量,但是使用继承,您可以只重用一个类的代码,因为在 Java 中您只能扩展一个类,因为 Java 不支持多个继承. 您可以在 C++ 中执行此操作,因为一个类可以扩展多个类。顺便说一句,你应该总是更喜欢 Java 中的组合而不是继承,这不仅是我,甚至Joshua Bloch在他的书中也提出了建议
继承与组合。
继承和组合都用于类行为的可重用性和扩展。
继承主要用在家族算法编程模型中,例如IS-A关系类型意味着相似种类的对象。例子。
- 除尘器是一辆车
- Safari 是一辆车
这些属于汽车家族。
组合表示HAS-A关系类型。它显示了一个对象的能力,例如Duster有五个齿轮,Safari有四个齿轮等。每当我们需要扩展现有类的能力时,就使用组合。示例我们需要在 Duster 对象中再添加一个齿轮,然后我们必须再创建一个齿轮对象并将其组合到除尘器对象中。
我们不应该在基类中进行更改,直到/除非所有派生类都需要这些功能。对于这种情况,我们应该使用组合。例如
A 类由 B 类派生
由 C 类派生的 A 类
A 类由 D 类派生。
当我们在 A 类中添加任何功能时,即使 C 和 D 类不需要这些功能,所有子类也可以使用它。对于这种情况,我们需要为这些功能创建一个单独的类并将其组合到所需的类(这是B类)。
下面是示例:
// This is a base class
public abstract class Car
{
//Define prototype
public abstract void color();
public void Gear() {
Console.WriteLine("Car has a four Gear");
}
}
// Here is the use of inheritence
// This Desire class have four gears.
// But we need to add one more gear that is Neutral gear.
public class Desire : Car
{
Neutral obj = null;
public Desire()
{
// Here we are incorporating neutral gear(It is the use of composition).
// Now this class would have five gear.
obj = new Neutral();
obj.NeutralGear();
}
public override void color()
{
Console.WriteLine("This is a white color car");
}
}
// This Safari class have four gears and it is not required the neutral
// gear and hence we don't need to compose here.
public class Safari :Car{
public Safari()
{ }
public override void color()
{
Console.WriteLine("This is a red color car");
}
}
// This class represents the neutral gear and it would be used as a composition.
public class Neutral {
public void NeutralGear() {
Console.WriteLine("This is a Neutral Gear");
}
}
组合意味着创建一个与该特定类有关系的类的对象。假设 Student 与 Accounts 有关系;
继承是,这是具有扩展功能的前一个类。这意味着这个新类是具有一些扩展功能的旧类。假设学生是学生,但所有学生都是人。因此,学生和人之间存在关系。这是继承。
继承意味着重用类的完整功能,这里我的类必须使用超类的所有方法,并且我的类将与超类紧密耦合,并且在继承的情况下,两个类中的代码将重复。
但是当我们使用组合与另一个类交谈时,我们可以克服所有这些问题。组合正在将另一个类的属性声明到我们要与之交谈的类中。以及我们可以通过使用该属性从该类中获得什么功能。
不,两者是不同的。组合遵循“HAS-A”关系,继承遵循“IS-A”关系。组合的最佳示例是战略模式。