java继承和组合的区别

要实现的目标:鸟(Bird)和狼(Wolf)都是动物(Animal),动物都有心跳(beat()),会呼吸(breath()),但是鸟会fly(fly()),狼会奔跑(run()),用Java程序实现以上描述。

InheritTest.java 使用继承方式实现目标

CompositeTest.java 使用组合方式实现目标

//InheritTest.java 使用继承方式实现目标  
class Animal{  
    private void beat(){  
        System.out.println("心脏跳动...");  
    }  
    public void breath(){  
        beat();  
        System.out.println("呼吸中...");  
    }  
}  
//继承Animal,直接复用父类的breath()方法  
class Bird extends Animal{  
    //创建子类独有的方法fly()  
    public void fly(){  
        System.out.println("我是鸟,会飞...");  
    }  
}  
//继承Animal,直接复用父类的breath()方法  
class Wolf extends Animal{  
    //创建子类独有的方法run()  
    public void run(){  
        System.out.println("我是狼,能跑...");  
    }  
}  
public class InheritTest{  
    public static void main(String[] args){  
        //创建继承自Animal的Bird对象新实例b  
        Bird b=new Bird();  
        //新对象实例b可以breath()  
        b.breath();  
        //新对象实例b可以fly()  
        b.fly();  
        Wolf w=new Wolf();  
        w.breath();  
        w.run();  
/* 
---------- 运行Java程序 ---------- 
心脏跳动... 
呼吸中... 
我是鸟,会飞... 
心脏跳动... 
呼吸中... 
我是狼,会跑... 
 
输出完毕 (耗时 0 秒) - 正常终止 
*/  
    }  
}  
  
//CompositeTest.java  使用组合方式实现目标  
class Animal{  
    private void beat(){  
        System.out.println("心脏跳动...");  
    }  
    public void breath(){  
        beat();  
        System.out.println("呼吸中...");  
    }  
}  
class Bird{  
    //定义一个Animal成员变量,以供组合之用  
    private Animal a;  
    //使用构造函数初始化成员变量  
    public Bird(Animal a){  
        this.a=a;  
    }  
    //通过调用成员变量的固有方法(a.breath())使新类具有相同的功能(breath())  
    public void breath(){  
        a.breath();  
    }  
    //为新类增加新的方法  
    public void fly(){  
        System.out.println("我是鸟,会飞...");  
    }  
}  
class Wolf{  
    private Animal a;  
    public Wolf(Animal a){  
        this.a=a;  
    }  
    public void breath(){  
        a.breath();  
    }  
    public void run(){  
        System.out.println("我是狼,会跑...");       
    }  
}  
public class CompositeTest{  
    public static void main(String[] args){  
        //显式创建被组合的对象实例a1  
        Animal a1=new Animal();  
        //以a1为基础组合出新对象实例b  
        Bird b=new Bird(a1);  
        //新对象实例b可以breath()  
        b.breath();  
        //新对象实例b可以fly()  
        b.fly();  
        Animal a2=new Animal();  
        Wolf w=new Wolf(a2);  
        w.breath();  
        w.run();  
/* 
---------- 运行Java程序 ---------- 
心脏跳动... 
呼吸中... 
我是鸟,会飞... 
心脏跳动... 
呼吸中... 
我是狼,会跑... 
 
输出完毕 (耗时 0 秒) - 正常终止 
*/  
    }  
}  

 

组合与继承的区别和联系


在继承结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种 白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性)

组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用 。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)

继承在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)

组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。

组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。

组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。

当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。

最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。

还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。

从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。

这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。

引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活”。

 

继承还是组合?


首先它们都是实现系统功能重用,代码复用的最常用的有效的设计技巧,都是在设计模式中的基础结构。

很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出,组合确实比继承更加灵活,也更有助于代码维护。

所以,建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效。

注意,并不是说继承就一点用都没有了,前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的,或者是更适合使用继承。

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。

只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者之间确实存在 is-a 关系的时候,类B才应该继承类A。
 

 

上一篇:Day04有关继承的小结(一)


下一篇:继承后,先调用父类的构造