面向对象编程中,有一个非常经典的规则,组合优于继承,多用组合少用继承,为什么多用组合,继承还该不该使用,两者各在什么情况下使用?这就是今天的问题

1.为什么不推荐使用继承

继承虽然是面向对象的四大特性之一,用来表示类之间的is-a关系,但是使用继承会导致代码层次过深,也会导致代码的可维护性降低,所以说有人认为继承是一种反模式,应该减少使用,甚至说不再使用,接下来我们将会举一个例子来证明继承他的局限性,我们拿鸟类来举例,我们将所有的鸟抽象出一个鸟的抽象类,AbstractBird抽象类.在其中我们定一个fly()的方法,但是很显然这个fly()方法并不符合所有的鸟类,比如说企鹅和鸵鸟它就不会飞.那么我们首先想到的解决方案是可以在鸵鸟或者企鹅这个子类当中实现这个fly()方法,然后调用fly()方法的时候抛出一个unSupportMethodException异常就可以了

public class AbstractBird {

//…省略其他属性和方法…

public void fly() { //… }

}

public class Ostrich extends AbstractBird { //鸵鸟

//…省略其他属性和方法…

public void fly() {

throw new UnSupportedMethodException(“I can’t fly.'”);

}

}

但是这样就导致了,我们可能在鸟这个词类下面有很多不会飞的鸟,我们不可能将所有不会飞的鸟都去实现这个方法,这样就增加了我们编程的代码量,也增加了我们的可维护性,其次,它也违背了我们所需要讲的最小知识原则(Least Knowledge Principle),或者叫迪米特法则.

可以使用第2种解决方案,我们可以在这个抽象类下面进行继承,然后再写两个子抽象类,分别表示是会飞的鸟类AbstractFlyableBird()和AbstractUnFlyableBird()不会飞的鸟类,让会飞的鸟去实现继承并实现会飞的鸟的抽象类,让不会飞的鸟去继承并实现不会飞的鸟的抽象类.

图片

这这样看我们的继承关系,就从原来的两层变成了三层,但是如果继续往下细分的,还要有的还会分出来,有的鸟是下蛋的,有的鸟是胎生的,还会分出来有的鸟会不会叫。那么再往下细分,可能导致我们类图就变成如下的情况

图片

那么这样如果我们需要搞清楚一个类,它具体有哪些方法.可能要阅读多层的父类,才能够完全的了解到这个类的属性和方法,就破坏了我们的代码可读性,而且子类高度依赖于父类,造成了我们的代码高度耦合,从而一旦修改了父类的代码,会影响所有的子类的实现

组合的优势在于什么

越来越多的有人提出使用组合,接口.,委托这三个技术的手段来替换掉继承

我们可以创建一个会飞的接口,让所有会飞的鸟类去实现这个会飞的接口,从而表示它会飞

类比如上方法,我们还可以去定义胎生的接口,下蛋的接口

同时为了避免会飞的鸟类都去实现飞的接口,我们可以根据这个接口去实现它的实现类,并且通过委托这种方式去消除重复代码,去让那些需要实现这个接口类去利用委托机制实现接口,并且通过组合的方式可以将多个实现类组合在一起,代码如下

public interface Flyable {

void fly();

}

public class FlyAbility implements Flyable {

@Override

public void fly() { //… }

}

//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟

private TweetAbility tweetAbility = new TweetAbility(); //组合

private EggLayAbility eggLayAbility = new EggLayAbility(); //组合

//… 省略其他属性和方法…

@Override

public void tweet() {

tweetAbility.tweet(); // 委托

}

@Override

public void layEgg() {

eggLayAbility.layEgg(); // 委托

}

}

上面就利用了实现类来避免了代码的重复书写

利用多个接口来表示它的多个特性,并且用组合这种手段来将多个特性组合起来,然后对于代码重复的问题,我们可以用委托这种技术端来消除掉代码重复,于是乎我们可以通过组合委托和接口这三个技术技术手段来替换掉几层,从而在项目中避免使用继承这种技术的,因为继承会导致我们代码的可读性和可维护性降低

怎么判断何时用组合何时用继承

虽然组合看起来更加灵活,但是使用组合的话,会增加代码的维护成本,因为组合要将继承拆解成多个接口,并且将这些多个接口提供其的实现类去方便委托,所以说我们要根据实际情况去判断我们应该使用继承还是组合去实现

如果一个类的继承关系相对于稳定,并且它不会轻易改变继承关系,继承的层次也相对于短,那么我们可以放心大胆的去使用继承,与之相反,如果一个类它的关系非常容易去改变,继承关系复杂,层次很深,那么还是去组合来去替代它

但是对于某些特殊场景,我们还是使用继承比较好,假设有一个类是不可变的,但是我们要用到它其中的某些特性,这时候我们就需要去继承这个类,然后对于我们不需要的那些特性进行重写就可以完成了.

public class FeignClient { // feighn client框架代码

//…省略其他代码…

public void encode(String url) { //… }

}

public void demofunction(FeignClient feignClient) {

//…

feignClient.encode(url);

//…

}

public class CustomizedFeignClient extends FeignClient {

@Override

public void encode(String url) { //…重写encode的实现…}

}

// 调用

FeignClient client = new CustomizedFeignClient();

demofunction(client);

总结一下来说,就是继承和组合两者各有长短,我们要根据实际情况去使用它

今天的重点为

今天总结一下为什么不建议使用继承

继承虽然可以去表达我们在面向对象编程中的多态性,但是使用继承往往可能会导致我们类之间的层次复杂,影响到了代码的可维护性

组合相比于继承,他可以解决类和类之间的耦合太高,降低类和类之间的维护性,利用接口 多态和委托这个技术手段可以去实现

对于继承和组合的选择,我们需要鼓励使用使用组合,但是如果一个类它相对于稳定,层次低,那么我们可以使用继承这种方式简单而明了的去

对于这个问题,我个人的看法是在实际开发中,我们经常使用beans,这个工具类其中的copy properties,方法去解决代码重复来重新,的重复复制问题,但是经过了这几天的学习,发现经常使用的mvc是一个面向过程的程序模型,所以我给出的解决答案是使用注册和接口组合和接口以及委托这种方式去实现它,这是我学完这节课的一个设想,但这三者之间并没有什么,直接的关系,我认为这三者没必要去解决他们的代码重复问题,他们三个本身来说就是应该是相对独立的,不然有这么多相同的代码重复,我们为什么不将其放到一个对象里面了?在整个mvc3层之间都共用的一个对象

发表评论

邮箱地址不会被公开。 必填项已用*标注