微服务 继承与构成:如何选择

通过优锐课的java架构学习中,了解了关于比较两种联系类的基本方法,然后练习在Java继承中调试ClassCastExceptions

继承和组合是开发人员用来在类和对象之间建立关系的两种编程技术。 继承是从另一类继承一个类,而composition将一个类定义为其部分的总和。
通过继承创建的类和对象紧密耦合,因为在继承关系中更改父类或超类可能会破坏你的代码。 通过合成创建的类和对象是松散耦合的,这意味着你可以更轻松地更改组件而不破坏代码。

因为松散耦合的代码提供了更大的灵活性,所以许多开发人员已经了解到,组合是比继承更好的技术,但事实更复杂。 选择编程工具类似于选择正确的厨房工具:你不会使用黄油刀切蔬菜,并且同样地,你不应该为每种编程方案选择成分。

在此Java Challenger中,你将学习继承和组合之间的区别以及如何确定哪种程序最适合你。 接下来,我将向你介绍Java继承的几个重要但具有挑战性的方面:方法覆盖,超级关键字和类型转换。 最后,你将通过逐行处理一个继承示例来确定所输出的内容,以测试所学的内容。

何时在Java中使用继承
在面向对象的编程中,当我们知道孩子与其父类之间存在“是”关系时,就可以使用继承。 一些示例是:

一个人是一个人。
猫是一种动物。
汽车就是车辆。

在每种情况下,子类或子类都是父类或超类的特殊版本。 从超类继承是代码重用的一个示例。 为了更好地理解这种关系,请花点时间研究继承自车辆的Car类:

class Vehicle {

    String brand;
    String color;
    double weight;
    double speed;

    void move() {
        System.out.println("The vehicle is moving");
    }
}
public class Car extends Vehicle {

    String licensePlateNumber;
    String owner;
    String bodyStyle;

    public static void main(String... inheritanceExample) {
        System.out.println(new Vehicle().brand);
        System.out.println(new Car().brand);
        new Car().move();
    }
}

在考虑使用继承时,请问问自己,子类是否确实是超类的更专门的版本。 在这种情况下,汽车是车辆的一种,因此继承关系很有意义。

何时在Java中使用合成
在面向对象的编程中,我们可以在一个对象“具有”另一个对象(或属于另一个对象)的情况下使用组合。 一些示例是:

汽车具有电池(电池是汽车的一部分)。
一个人有心脏(心脏是一个人的一部分)。
房屋有起居室(客厅是房屋的一部分)。
为了更好地理解这种类型的关系,请考虑房屋的组成:

public class CompositionExample {

    public static void main(String... houseComposition) {
        new House(new Bedroom(), new LivingRoom());
        // The house now is composed with a Bedroom and a LivingRoom
    }

    static class House {

        Bedroom bedroom;
        LivingRoom livingRoom;

        House(Bedroom bedroom, LivingRoom livingRoom) {
            this.bedroom = bedroom;
            this.livingRoom = livingRoom;
        }

    }

    static class Bedroom { }

    static class LivingRoom { }
}

在这种情况下,我们知道一间房子有一个客厅和一间卧室,因此我们可以在房子的组成中使用卧室''和客厅''对象。

获取代码
获取此Java Challenger中示例的源代码。 在遵循示例的同时,你可以运行自己的测试。

继承与组成:两个例子
考虑下面的代码。 这是继承的好例子吗?

import java.util.HashSet;
public class CharacterBadExampleInheritance extends HashSet<Object> {

    public static void main(String... badExampleOfInheritance) {
        BadExampleInheritance badExampleInheritance = new BadExampleInheritance();
        badExampleInheritance.add("Homer");
        badExampleInheritance.forEach(System.out::println);

    }

在这种情况下,答案是否定的。 子类继承了许多它永远不会使用的方法,导致紧密耦合的代码既混乱又难以维护。 如果仔细观察,很显然此代码未通过“是”测试。
现在,让我们尝试使用组合的相同示例:

import java.util.HashSet;import java.util.Set;
public class CharacterCompositionExample {

    static Set<String> set = new HashSet<>();

    public static void main(String... goodExampleOfComposition) {
        set.add("Homer");
        set.forEach(System.out::println);
    }

在这种情况下使用组合可以使CharacterCompositionExample类仅使用HashSet的两个方法,而无需继承所有方法。 这样可以简化代码,减少耦合,从而更易于理解和维护。

JDK中的继承示例

Java Development Kit充满了很好的继承示例:
class IndexOutOfBoundsException extends RuntimeException {...}
class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...}
class FileWriter extends OutputStreamWriter {...}
class OutputStreamWriter extends Writer {...}
interface Stream<T> extends BaseStream<T, Stream<T>> {...}

注意,在每个示例中,子类都是其父类的专门版本; 例如,IndexOutOfBoundsException是RuntimeException的一种。

用Java继承重写方法
继承使我们可以在新类中重用一个类的方法和其他属性,这非常方便。 但是要使继承真正起作用,我们还需要能够在新的子类中更改某些继承的行为。 例如,我们可能想专门制作猫的声音:

class Animal {

    void emitSound() {
        System.out.println("The animal emitted a sound");
    }
}
class Cat extends Animal {

    @Override
    void emitSound() {
        System.out.println("Meow");
    }}
class Dog extends Animal {}
public class Main {

    public static void main(String... doYourBest) {
        Animal cat = new Cat(); // Meow
        Animal dog = new Dog(); // The animal emitted a sound
        Animal animal = new Animal(); // The animal emitted a sound

        cat.emitSound();
        dog.emitSound();
        animal.emitSound();
    }
}

这是带有方法覆盖的Java继承的示例。 首先,我们扩展动物类以创建一个新的猫类。 接下来,我们重写动物类的emitSound()方法来获取猫发出的特定声音。 即使我们将类类型声明为Animal,当将其实例化为Cat时,我们也会得到猫的叫声。

方法重载是多态
你可能记得我上一篇文章中提到的方法重载是多态或虚拟方法调用的示例。

Java是否具有多重继承?
与某些语言(例如C ++)不同,Java不允许对类进行多重继承。 但是,你可以对接口使用多重继承。 在这种情况下,类和接口之间的区别在于接口不保持状态。
如果你尝试像我在下面这样进行多重继承,则代码将无法编译:

class Animal {}class Mammal {}class Dog extends Animal, Mammal {}

使用类的解决方案将是逐一继承:

class Animal {}class Mammal extends Animal {}class Dog extends Mammal {}

另一种解决方案是用接口替换类:

interface Animal {}interface Mammal {}class Dog implements Animal, Mammal {}

使用“ super”访问父类方法
当两个类通过继承相关联时,子类必须能够访问其父类的每个可访问字段,方法或构造函数。 在Java中,我们使用保留字super来确保子类仍然可以访问其父类的重写方法:

public class SuperWordExample {

    class Character {

        Character() {
            System.out.println("A Character has been created");
        }

        void move() {
            System.out.println("Character walking...");
        }

    }

    class Moe extends Character {

        Moe() {
            super();
        }

        void giveBeer() {
            super.move();
            System.out.println("Give beer");
        }
    }
}

在此示例中,字符是Moe的父类。 使用super,我们可以访问Character的move()方法来给Moe啤酒。
将构造函数与继承一起使用

当一个类继承自另一个类时,在加载其子类之前,始终会先加载超类的构造函数。 在大多数情况下,保留字super将自动添加到构造函数中。 但是,如果超类在其构造函数中有一个参数,我们将必须故意调用super构造函数,如下所示:

public class ConstructorSuper {

    class Character {

        Character() {
            System.out.println("The super constructor was invoked");
        }

    }

    class Barney extends Character {

        // No need to declare the constructor or to invoke the super constructor
        // The JVM will to that

    }
}

如果父类的构造函数至少具有一个参数,则必须在子类中声明该构造函数,并使用super显式调用父构造函数。 超级保留字将不会自动添加,没有它,代码将无法编译。 例如:

public class CustomizedConstructorSuper {

    class Character {

        Character(String name) {
            System.out.println(name + "was invoked");
        }

    }

    class Barney extends Character {

        // We will have compilation error if we don't invoke the constructor explicitly
        // We need to add it
        Barney() {
            super("Barney Gumble");
        }

    }
}

类型转换和ClassCastException
强制转换是一种向编译器明确传达你确实打算转换给定类型的方式。 就像说,“嘿,JVM,我知道我在做什么,所以请使用这种类型转换此类。” 如果你强制转换的类与声明的类类型不兼容,则将收到ClassCastException。

在继承中,我们可以在不强制转换的情况下将子类分配给父类,但是在不使用强制转换的情况下不能将父类分配给子类。
考虑以下示例:

public class CastingExample {

    public static void main(String... castingExample) {
        Animal animal = new Animal();
        Dog dogAnimal = (Dog) animal; // We will get ClassCastException
        Dog dog = new Dog();
        Animal dogWithAnimalType = new Dog();
        Dog specificDog = (Dog) dogWithAnimalType;
        specificDog.bark();
        Animal anotherDog = dog; // It's fine here, no need for casting
        System.out.println(((Dog)anotherDog)); // This is another way to cast the object
    }
}
class Animal { }class Dog extends Animal { void bark() { System.out.println("Au au"); } }

当我们尝试将动物实例投射到狗上时,我们会得到一个例外。 这是因为动物对它的孩子一无所知。 它可能是猫,鸟,蜥蜴等。没有关于特定动物的信息。
这种情况下的问题是我们像这样实例化了Animal:
Animal animal = new Animal();

然后尝试将其投射如下:
Dog dogAnimal = (Dog) animal;

由于我们没有Dog实例,因此无法为Dog分配动物。 如果尝试的话,将会得到ClassCastException。

为了避免异常,我们应该像这样实例化Dog:
Dog dog = new Dog();

然后将其分配给动物:
Animal anotherDog = dog;

在这种情况下,因为我们扩展了Animal类,所以甚至不需要强制使用Dog实例; 动物父类类型仅接受分配。

用超类型进行转换

可以用超类动物声明一个狗,但是如果我们想从狗中调用一个特定的方法,我们将需要对其进行强制转换。 例如,如果我们想调用bark()方法怎么办? 动物超类型无法确切知道我们正在调用哪个动物实例,因此我们必须先强制转换Dogman才能调用bark()方法:


Animal dogWithAnimalType = new Dog();Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();

你也可以在不将对象分配给类类型的情况下使用强制转换。 当你不想声明另一个变量时,此方法很方便:

System.out.println(((Dog)anotherDog)); // This is another way to cast the object

接受Java继承挑战!

你已经了解了继承的一些重要概念,因此现在是时候尝试继承挑战了。 首先,请研究以下代码:

public class InheritanceCompositionChallenge {
    private static int result;
    public static void main(String... doYourBest) {
        Character homer = new Homer();
        homer.drink();
        new Character().drink();
        ((Homer)homer).strangleBart();
        Character character = new Character();
        System.out.println(result);
        ((Homer)character).strangleBart();
    }

    static class Character {
        Character() {
            result++;
        }
        void drink() {
            System.out.println("Drink");
        }
    }

    static class Homer extends Character {
        Lung lung = new Lung();

        void strangleBart() {
            System.out.println("Why you little!");
        }
        void drink() {
            System.out.println("Drink beer");
            lung.damageLungs();
        }
    }

    static class Lung {
        void damageLungs() {
            System.out.println("Soon you will need a transplant");
        }
    }
}

运行main方法后输出是什么?
一种)
DrinkDrinkWhy you little!2Exception in thread "main" java.lang.ClassCastException:....
B)
Drink beerSoon you will need a transplantDrinkWhy you little!Exception in thread "main" java.lang.ClassCastException:....
C)
Drink beerSoon you will need a transplantDrinkWhy you little!3Exception in thread "main" java.lang.ClassCastException:....
D)
Drink beerSoon you will need a transplantDrinkWhy you little!2Why you little!
刚刚发生了什么? 了解Java继承
继承质询的正确输出是C:
Drink beerSoon you will need a transplantDrinkWhy you little!3Exception in thread "main" java.lang.ClassCastException:....
要了解原因,只需从顶部开始检查代码即可:
Character homer = new Homer();
homer.drink();
由于我们使用Homer实例化了对象,因此将执行Homer方法实现,并产生以下输出:
Drink beerSoon you will need a transplant
然后,我们直接从Character类调用drink()方法。
new Character().drink();
我们将得到以下输出:
Drink beer
在这一行,我们使用强制转换并正常调用strangleBart()方法:

((Homer)homer).strangleBart();
然后,我们要求输出结果:
System.out.println(result);

因为我们知道总是要调用超级构造函数,所以我们只需要计算实例化字符或Homer的次数即可。 现在我们知道输出为3。
最后,我们尝试从滥用的转换中调用方法:

((Homer) character).strangleBart();
我们尝试转换字符的类类型和实现,因此将引发ClassCastException。 (这是因为在Character类中没有Homerinformation。)

视频挑战! 调试Java继承示例
调试是完全吸收编程概念并改善代码的最简单方法之一。 在此视频中,你可以在调试和解释Java继承挑战的同时进行后续操作。

继承的常见错误
在仔细考虑之前编写继承代码。
在更好的组合方式中使用继承。
使用继承时不利用多态性。
在“是”测试未通过时使用继承。

关于Java继承的小知识
当用子类实例化超类时,将考虑子类实例化。
为了确定是否使用继承,请问自己子级是否确实是父级的特殊类型(“是”测试)。
如果你未自行声明,则超级保留字将自动添加到构造函数中。
如果具有父类类型和该类型的子类实例化,则可以将其强制转换为子类。
如果超类的构造函数至少接收一个参数,则必须在子类中调用此超级构造函数并传递所需的参数。

喜欢这篇文章的可以点个赞,欢迎大家留言评论,记得关注我,每天持续更新技术干货、职场趣事、海量面试资料等等
 > 如果你对java技术很感兴趣也可以交流学习,共同学习进步。 
不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代

文章写道这里,欢迎完善交流。最后奉上近期整理出来的一套完整的java架构思维导图,分享给大家对照知识点参考学习。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java干货

微服务 继承与构成:如何选择

猜你喜欢

转载自blog.51cto.com/14634606/2471998