在Java的继承中,你是否有这样的疑惑?

 2022-09-19
原文地址:https://www.51cto.com/article/704460.html

一、问题

最近在写代码,有两个属性很相近的类,其中80%的属性(字段)都是一样的,剩下的才是各自不一样的,在设计的时候,采用了继承的方式,抽象除了一个父类,大概如下,

有FirstChild和SecondChild两个类,因为其中的属性name、code等是相同的,为此抽出了一个父类BaseDO,如下:

    package com.example.day01;
    
    public class BaseDO {
        private String name;
        private String code;
        private String field1;
        private String field2;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getField1() {
            return field1;
        }
    
        public void setField1(String field1) {
            this.field1 = field1;
        }
    
        public String getField2() {
            return field2;
        }
    
        public void setField2(String field2) {
            this.field2 = field2;
        }
    
        @Override
        public String toString() {
            return "BaseDO{" +
                    "name='" + name + '\'' +
                    ", code='" + code + '\'' +
                    ", field1='" + field1 + '\'' +
                    ", field2='" + field2 + '\'' +
                    '}';
        }
    }

FirstChild

    package com.example.day01;
    
    public class FirstChild extends BaseDO{
        private String myField1;
        private String myFiled2;
    
        public String getMyField1() {
            return myField1;
        }
    
        public void setMyField1(String myField1) {
            this.myField1 = myField1;
        }
    
        public String getMyFiled2() {
            return myFiled2;
        }
    
        public void setMyFiled2(String myFiled2) {
            this.myFiled2 = myFiled2;
        }
    
        @Override
        public String toString() {
            return "FirstChild{" +
                    "myField1='" + myField1 + '\'' +
                    ", myFiled2='" + myFiled2 + '\'' +
                    "} " + super.toString();
        }
    }

SecondChild

    package com.example.day01;
    
    public class SecondChild extends BaseDO{
        private String secondField1;
        private String secondField2;
    
        public String getSecondField1() {
            return secondField1;
        }
    
        public void setSecondField1(String secondField1) {
            this.secondField1 = secondField1;
        }
    
        public String getSecondField2() {
            return secondField2;
        }
    
        public void setSecondField2(String secondField2) {
            this.secondField2 = secondField2;
        }
    
        @Override
        public String toString() {
            return "SecondChild{" +
                    "secondField1='" + secondField1 + '\'' +
                    ", secondField2='" + secondField2 + '\'' +
                    "} " + super.toString();
        }
    }

从上面可以看到两个子类除了含有父类的属性外还有自己各自的属性,现在有个需求是这样的,要实例化这两个子类。

二、如何解决

2.1、分别初始化

何为分别初始化,所谓分别初始化就是各自初始化自己的,为每个子类分别实现初始化其属性的方法,如下

    FirstChild fillFirstField(FirstChild firstChild){
    
            firstChild.setName("apple");
            firstChild.setCode("apple");
            firstChild.setMyField1("first Child");
    
            return firstChild;
        }
    SecondChild fillSecondField(SecondChild secondChild){
    
            secondChild.setName("apple");
            secondChild.setCode("apple");
            secondChild.setSecondField1("second Child");
            return secondChild;
        }

这里作为演示对属性没有全部赋值,如果两个子类相同的属性比较多,那么赋值起来会比较麻烦,而且两个方法的代码重复度会很高。

2.2、抽象出一个公共的方法

既然,已经为两个子类抽象出了公共的属性,那么顺着这个思路下去,也可以抽象出一个公共的方法为这些公共的属性赋值,即为父类填充属性,

    BaseDO fillField(BaseDO baseDO){
            baseDO.setName("apple");
            baseDO.setCode("apple");
            return baseDO;
        }

好了,在进行子类初始化的时候已经有一个方法可以初始化其公共属性,那么接下来的事情,就是初始化其自己的属性即可,

下面就实例化FirstChild,然后初始化其公有属性

    FirstChild firstChild=new FirstChild();
            fillField(firstChild);
            firstChild.setMyField1("first Child");
            System.out.println(firstChild);

打印出firstChild的结果如下

202209192316536181.png

可以看到已经把公共属性name、code和特意属性myField1进行赋值,完成了上面的需求。

可能有些同学会使用下面的写法,

202209192316547482.png

可以看的该种写法存在错误,提示我们需要的类型是FirstChild,但是我们提供的BaseDO,我们知道fillField方法返回的BaseDO类型,一个父类型的实例不可赋值给子类型的引用( 相反一个子类型的实例可以赋值给父类型,典型的多态 ),这怎么办那,向下类型转换,没错,如下

202209192316557833.png

细心的小伙伴会问,那为什么fillField方法可以接受FirstChild的实例那,哈哈哈,前边红字提到了多态。

怎么样,是不是对多态又有了更深的理解。

延申一点

从fillField方法来看,我们知道该方法可以不设返回值,为什么可以不设返回值,因为引用类型,不是传值,可以理解为引用,哈哈,所以叫引用类型,在fillField方法中对其引用类型的参数进行了修改,那么在这个方法执行完了之后,引用这个参数的其他引用同样可以感知到其修改,下面的写法就是很好的佐证,

202209192316568834.png

是不是又加深了引用和引用的对象间的关系,多说一句引用在jvm的内存模型中是在哪个区,引用指向的对象那?

三、总结

本文分析了在开发过程中,遇到公有属性很多的多个实体类时的设计思路,抽出公有的父类,由父类承担公有属性。并且在进行属性填充的时候,如果公有属性的值是一样的,那么可以抽出公共的方法进行属性填充,这里又提到了多态。

  1. 抽出公有属性;
  2. 对多态的理解;
  3. 向下类型转换;
  4. 引用类型的传递;

最后,广大读者们,对于类似的需求,你们有更好的设计思路吗,欢迎踊跃讨论。