关于java:如何通过使用超类来减少代码?

How to reduce code by using a superclass?

我想重构一些目前由超类和两个子类组成的代码。

这些是我的课程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Animal {
    int a;
    int b;
    int c;
}

public class Dog extends Animal {
    int d;
    int e;
}

public class Cat extends Animal {
    int f;
    int g;
}

这是我目前的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ArrayList<Animal> listAnimal = new ArrayList<>();

if (condition) {
    Dog dog = new Dog();
    dog.setA(..);
    dog.setB(..);
    dog.setC(..);
    dog.setD(..);
    dog.setE(..);  
    listAnimal.add(dog);

} else {
    Cat cat = new Cat();
    cat.setA(..);
    cat.setB(..);
    cat.setC(..);
    cat.setF(..);
    cat.setG(..);
    listAnimal.add(cat);
}

如何重构有关公共属性的代码?

我想要这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Animal animal = new Animal();
animal.setA(..);
animal.setB(..);
animal.setC(..);

if (condition) {
    Dog anim = (Dog) animal; //I know it doesn't work
    anim.setD(..);
    anim.setE(..);  
} else {
    Cat anim = (Cat) animal; //I know it doesn't work
    anim.setF(..);
    anim.setG(..);
}

listAnimal.add(anim);

你想要一个Animal类型的变量是个好主意。但是你还必须确保使用正确的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Animal animal; // define a variable for whatever animal we will create

if (condition) {
    Dog dog = new Dog(); // create a new Dog using the Dog constructor
    dog.setD(..);
    dog.setE(..);  
    animal = dog; // let both variables, animal and dog point to the new dog
} else {
    Cat cat = new Cat();
    cat.setF(..);
    cat.setG(..);
    animal = cat;
}

animal.setA(..); // modify either cat or dog using the animal methods
animal.setB(..);
animal.setC(..);

listAnimal.add(animal);

提示:如果动物总是猫或狗,请考虑制作动物abstract。然后,只要您尝试执行new Animal(),编译器就会自动发出警告。


构建猫或狗的过程很复杂,因为涉及很多领域。对于构建器模式来说,这是一个很好的例子。

我的想法是为每种类型编写一个构建器并组织它们之间的关系。它可以是组合或继承。

  • AnimalBuilder构造一般的Animal对象并管理abc字段
  • CatBuilder获取AnimalBuilder(或扩展它)并继续构建管理fg字段的Cat对象
  • DogBuilder获取AnimalBuilder(或扩展它)并继续构建管理de字段的Dog对象

如果您不想创建构建器,请考虑为每个子类引入一个具有有意义名称的静态工厂方法:

1
2
3
Animal animal = condition ? Dog.withDE(4, 5) : Cat.withFG(6, 7);
// populate animal's a, b, c
listAnimal.add(animal);

它将简化构造并使其更简洁,更易读。


回答

一种方法是在类中添加适当的构造函数。往下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Animal {
   int a, b, c;

   public Animal(int a, int b, int c) {
      this.a = a;
      this.b = b;
      this.c = c;
   }
}

public class Dog extends Animal {
   int d, e;

   public Dog(int a, int b, int c, int d, int e) {
      super(a, b, c);
      this.d = d;
      this.e = e;
   }
}

public class Cat extends Animal {
   int f, g;

   public Cat(int a, int b, int c, int f, int g) {
      super(a, b, c);
      this.f = f;
      this.g = g;
   }
}

现在,要实例化对象,您可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ArrayList<Animal> listAnimal = new ArrayList();

//sample values
int a = 10;
int b = 5;
int c = 20;

if(condition) {
   listAnimal.add(new Dog(a, b, c, 9, 11));
   //created and added a dog with e = 9 and f = 11
}
else {
   listAnimal.add(new Cat(a, b, c, 2, 6));
   //created and added a cat with f = 2 and g = 6
}

这是我在这种情况下使用的方法。它通过避免大量的"设置"方法使代码更清晰,更可读。请注意,super()是对超类'(在本例中为Animal)构造函数的调用。




奖金

如果您不打算创建类Animal的实例,则应将其声明为abstract。抽象类不能实例化,但可以是子类,可以包含抽象方法。声明这些方法没有正文,这意味着所有子类必须提供它们自己的实现。这是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Animal {
   //...  

   //all animals must eat, but each animal has its own eating behaviour
   public abstract void eat();
}

public class Dog {
   //...

   @Override
   public void eat() {
     //describe the eating behaviour for dogs
   }
}

现在你可以为任何动物打电话eat()!在前面的示例中,使用动物列表,您可以执行以下操作:

1
2
3
for(Animal animal: listAnimal) {
   animal.eat();
}


以下是我想提出的建议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.util.ArrayList;
import java.util.List;

class Animal {
    int a;
    int b;
    int c;

    public Animal setA(int a) {
        this.a = a;
        return this;
    }

    public Animal setB(int b) {
        this.b = b;
        return this;
    }

    public Animal setC(int c) {
        this.c = c;
        return this;
    }
}

class Dog extends Animal {
    int d;
    int e;

    public Dog setD(int d) {
        this.d = d;
        return this;
    }

    public Dog setE(int e) {
        this.e = e;
        return this;
    }
}

class Cat extends Animal {
    int f;
    int g;

    public Cat setF(int f) {
        this.f = f;
        return this;
    }

    public Cat setG(int g) {
        this.g = g;
        return this;
    }
}

class Scratch {
    public static void main(String[] args) {
        List<Animal> listAnimal = new ArrayList();
        boolean condition = true;
        Animal animal;
        if (condition) {
            animal = new Dog()
                    .setD(4)
                    .setE(5);

        } else {
            animal = new Cat()
                    .setF(14)
                    .setG(15);
        }
        animal.setA(1)
                .setB(2)
                .setC(3);
        listAnimal.add(animal);

        System.out.println(listAnimal);
    }
}

一些值得注意的要点:

  • 在声明List listAnimal中使用List接口
  • 在创建对象时使用界面动物Animal animal;
  • abstract类动物
  • Setters返回this以使代码更清晰。或者你必须使用像animal.setD(4); animal.setE(5);这样的代码
  • 这样我们就可以使用Animal接口并设置一次公共属性。希望这可以帮助。


    这是一个解决方案,非常接近slartidan的一个,但使用setter和构建器的样式,避免使用DogCat变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public class Dog extends Animal
    {
        // stuff

        Dog setD(...)
        {
            //...
            return this;
        }

        Dog setE(...)
        {
            //...
            return this;
        }
    }

    public class Cat extends Animal
    {
        // stuff

        Cat setF(...)
        {
            //...
            return this;
        }

        Cat setG(...)
        {
            //...
            return this;
        }
    }

    Animal animal = condition ?
        new Dog().setD(..).setE(..) :
        new Cat().setF(..).setG(..);

    animal.setA(..);
    animal.setB(..);
    animal.setC(..);

    listAnimal.add(animal);


    作为替代方案,您可以将您的狗和猫的"动物"部分设置为通过"动物"界面提供的单独实体。通过这样做,您首先创建公共状态,然后在需要时将其提供给特定于物种的构造函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Animal {
        int a;
        int b;
        int c;
    }

    public interface Animalian {
        Animal getAnimal();
    }

    public class Dog implements Animalian {
        int d;
        int e;
        Animal animal;
        public Dog(Animal animal, int d, int e) {
            this.animal = animal;
            this.d = d;
            this.e = e;
        }
        public Animal getAnimal() {return animal};
    }

    public class Cat implements Animalian {
        int f;
        int g;
        Animal animal;
        public Cat(Animal animal, int f, int g) {
            this.animal = animal;
            this.f = f;
            this.g = g;
        }
        public Animal getAnimal() {return animal};
    }

    现在创造动物:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Animal animal = new Animal();
    animal.setA(..);
    animal.setB(..);
    animal.setC(..);

    if (condition) {
        listAnimalian.add(new Dog(animal, d, e));
    } else {
        listAnimalian.add(new Cat(animal, f, g));
    }

    这样做的原因是"赞成合成而非继承"。我想表达,这只是解决问题的另一种方式。这并不意味着组合在任何时候都应该优于继承。由工程师决定出现问题的背景的正确解决方案。

    关于这个主题有很多阅读。


    我会考虑动态查找/注册功能/功能:飞行/游泳。

    这是否适合您的使用问题:而不是飞行&amp;游泳带鸟和鱼。

    这取决于添加的属性是否是独家(狗/猫)或添加剂(飞行/游泳/哺乳动物/昆虫/ EggLaying / ...)。后者更适合使用地图进行查找。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Fish { boolean inSaltyWater(); }
    interface Bird { int wingSpan(); setWingSpan(int span); }

    Animal animal = ...

    Optional<Fish> fish = animal.as(Fish.class);
    fish.ifPresent(f -> System.out.println(f.inSaltyWater()?"seafish" :"riverfish"));

    Optional<Bird> bird = animal.as(Bird.class);
    bird.ifPresent(b-> b.setWingSpan(6));

    Animal不需要实现任何接口,但您可以查找(查找或可能)功能。这在未来是可扩展的,动态的:可以在运行时更改。

    实施为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private Map<Class< ? >, ?> map = new HashMap<>();

    public < T > Optional< T > as(Class< T > type) {
         return Optional.ofNullable(type.cast(map.get(type)));
    }

     void register(Class type, S instance) {
        map.put(type, instance);
    }

    该实现执行安全的动态转换,因为寄存器确保安全填充(键,值)条目。

    1
    2
    3
    4
    5
    Animal flipper = new Animal();
    flipper.register(new Fish() {
        @Override
        public boolean inSaltyWater() { return true; }
    });


    考虑使您的类不可变(Effective Java 3rd Edition Item 17)。如果需要所有参数,请使用构造函数或静态工厂方法(Effective Java 3rd Edition Item 1)。如果有必需参数和可选参数,请使用构建器模式(Effective Java 3rd Edition Item 2)。


    将您的代码重构为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ArrayList<Animal> listAnimal = new ArrayList();

    //Other code...

    if (animalIsDog) {
        addDogTo(listAnimal, commonAttribute, dogSpecificAttribute);
    } else {
        addCatTo(listAnimal, commonAttribute, catSpecificAttribute);
    }

    新代码的好处:

  • 隐藏的复杂性:你已经隐藏了复杂性,你现在必须看一下较小的代码,几乎用简单的英文编写,稍后重新访问你的代码。
  • 但是现在,必须编写方法addDogToaddCatTo。这是他们的样子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private void addDogTo(ArrayList<Animal> listAnimal,
        AnimalAttribute generalAttribute,
        DogAttribute specificAttribute) {
        var dog = createDog(commonAttribute, specificAttribute);
        listAnimal.add(dog);
    }

    private void addCatTo(ArrayList<Animal> listAnimal,
        AnimalAttribute generalAttribute,
        CatAttribute specificAttribute) {
        var cat = createCat(commonAttribute, specificAttribute);
        listAnimal.add(cat);
    }

    优点:

  • 隐藏的复杂性;
  • 这两种方法都是私有的:这意味着它们只能在类中调用。因此,您可以放心地检查输入是否为null等,因为调用者(在类中)必须注意不要将虚假数据传递给自己的成员。
  • 这意味着现在我们需要有createDogcreateCat方法。这就是我写这些方法的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private Dog createDog(AnimalAttribute generalAttribute,
        DogAttribute specificAttribute) {
        var dog = new Dog(generalAttribute, specificAttribute);
        return dog;
    }

    private Cat createCat(AnimalAttribute generalAttribute,
        CatAttribute specificAttribute) {
        var cat = new Cat(generalAttribute, specificAttribute);
        return cat;
    }

    优点:

  • 隐藏的复杂性;
  • 两种方法都是私有的。
  • 现在,对于上面编写的代码,您将必须编写CatDog的构造函数,这些构造函数接受公共属性和对象构造的特定属性。这可能看起来像:

    1
    2
    3
    4
    5
    6
    public Dog(AnimalAttribute generalAttribute,
        DogAttribute specificAttribute)
            : base (generalAttribute) {
        this.d = specificAttribute.getD();
        this.e = specificAttribute.getE();
    }

    和,

    1
    2
    3
    4
    5
    6
    public Cat(AnimalAttribute generalAttribute,
        CatAttribute specificAttribute)
            : base (generalAttribute) {
        this.f = specificAttribute.getF();
        this.g = specificAttribute.getG();
    }

    优点:

  • 代码是DRY:两个构造函数都使用generalAttributes调用超类方法,并处理两个子类对象的公共属性;
  • 保留整个对象:不是调用构造函数并传递2万个参数,而是传递它2,即。一般动物属性对象和特定动物属性对象。这两个参数保存其余属性,并在需要时在构造函数内取消装箱。
  • 最后,你的Animal的构造函数将如下所示:

    1
    2
    3
    4
    5
    public Animal(AnimalAttribute attribute) {
        this.a = attribute.getA();
        this.b = attribute.getB();
        this.c = attribute.getC();
    }

    优点:

  • 保留整个对象;
  • 为了完成:

    • AnimalAttribute / DogAttribute / CatAttribute类只有一些字段以及这些字段的getter和setter;
    • 这些字段是构造Animal / Dog / Cat对象所需的数据。

    这里有很多好建议。我会使用我个人喜欢的构建器模式(但添加了继承风味):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    public class Animal {

        int a;
        int b;
        int c;

        public Animal() {
        }

        private < T > Animal(Builder< T > builder) {
            this.a = builder.a;
            this.b = builder.b;
            this.c = builder.c;
        }

        public static class Builder< T > {
            Class< T > builderClass;
            int a;
            int b;
            int c;

            public Builder(Class< T > builderClass) {
                this.builderClass = builderClass;
            }

            public T a(int a) {
                this.a = a;
                return builderClass.cast(this);
            }

            public T b(int b) {
                this.b = b;
                return builderClass.cast(this);
            }

            public T c(int c) {
                this.c = c;
                return builderClass.cast(this);
            }

            public Animal build() {
                return new Animal(this);
            }
        }
        // getters and setters goes here

    }

    public class Dog extends Animal {

        int d;
        int e;

        private Dog(DogBuilder builder) {
            this.d = builder.d;
            this.e = builder.e;
        }

        public static class DogBuilder extends Builder<DogBuilder> {
            int d;
            int e;

            public DogBuilder() {
                super(DogBuilder.class);
            }

            public DogBuilder d(int d) {
                this.d = d;
                return this;
            }

            public DogBuilder e(int e) {
                this.e = e;
                return this;
            }

            public Dog build() {
                return new Dog(this);
            }
        }
        // getters and setters goes here
    }

    public class Cat extends Animal {

        int f;
        int g;

        private Cat(CatBuilder builder) {
            this.f = builder.f;
            this.g = builder.g;
        }

        public static class CatBuilder extends Builder<CatBuilder> {
            int f;
            int g;

            public CatBuilder() {
                super(CatBuilder.class);
            }

            public CatBuilder f(int f) {
                this.f = f;
                return this;
            }

            public CatBuilder g(int g) {
                this.g = g;
                return this;
            }

            public Cat build() {
                return new Cat(this);
            }
        }
        // getters and setters goes here
    }

    public class TestDrive {

        public static void main(String[] args) {

            Boolean condition = true;
            ArrayList<Animal> listAnimal = new ArrayList<>();

            if (condition) {
                Dog dogA = new Dog.DogBuilder().a(1).b(2).c(3).d(4).e(5).build();
                Dog dogB = new Dog.DogBuilder().d(4).build();
                listAnimal.add(dogA);
                listAnimal.add(dogB);

            } else {
                Cat catA = new Cat.CatBuilder().b(2).f(6).g(7).build();
                Cat catB = new Cat.CatBuilder().g(7).build();
                listAnimal.add(catA);
                listAnimal.add(catB);
            }
            Dog doggo = (Dog) listAnimal.get(0);
            System.out.println(doggo.d);
        }
    }

    注意:Animal.Builder构造函数将Class builderClass作为通用参数。返回时将当前的对象实例强制转换为此类。