【问题标题】:How to reduce code by using a superclass?如何通过使用超类来减少代码?
【发布时间】:2019-03-01 23:39:09
【问题描述】:

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

这些是我的课程:

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;
}

这是我当前的代码:

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);
}

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

我想要这样的东西:

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);

【问题讨论】:

    标签: java inheritance subclass superclass


    【解决方案1】:

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

    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 作为通用参数。返回时将对象的当前实例转换为此类。

    【讨论】:

      【解决方案2】:

      将您的代码重构为:

      ArrayList<Animal> listAnimal = new ArrayList();
      
      //Other code...
      
      if (animalIsDog) {
          addDogTo(listAnimal, commonAttribute, dogSpecificAttribute); 
      } else {
          addCatTo(listAnimal, commonAttribute, catSpecificAttribute);
      }
      

      新代码的好处:

      1. 隐藏的复杂性:您已经隐藏了复杂性,现在在以后重新访问您的代码时,您将不得不查看一个更小的代码,几乎是用简单的英语编写的。

      但是现在,必须编写方法 addDogToaddCatTo。这就是它们的样子:

      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);
      }
      

      好处:

      1. 隐藏的复杂性
      2. 这两个方法都是私有的:这意味着它们只能从类中调用。因此,您可以安全地取消检查输入是否为 null 等,因为调用者(在类中)必须注意不要将虚假数据传递给它自己的成员。

      这意味着现在我们需要有createDogcreateCat 方法。这就是我编写这些方法的方式:

      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;
      }
      

      好处:

      1. 隐藏的复杂性
      2. 这两种方法都是私有的

      现在,对于上面编写的代码,您将不得不为CatDog 编写构造函数,它们接受通用属性和对象构造的特定属性。这可能看起来像:

      public Dog(AnimalAttribute generalAttribute,
          DogAttribute specificAttribute)
              : base (generalAttribute) {
          this.d = specificAttribute.getD();
          this.e = specificAttribute.getE();
      }
      

      和,

      public Cat(AnimalAttribute generalAttribute,
          CatAttribute specificAttribute)
              : base (generalAttribute) {
          this.f = specificAttribute.getF();
          this.g = specificAttribute.getG();
      }
      

      好处:

      1. 代码是 DRY: 两个构造函数都使用 generalAttributes 调用超类方法,并负责处理两个子类对象的公共属性;
      2. 整个对象都被保留了: 不是调用构造函数并传递给它 20,000 个参数,而是传递 2 个参数,即。一般动物属性对象和特定动物属性对象。这两个参数包含其余属性,并在需要时在构造函数中拆箱。

      最后,Animal 的构造函数将如下所示:

      public Animal(AnimalAttribute attribute) {
          this.a = attribute.getA();
          this.b = attribute.getB();
          this.c = attribute.getC();
      }
      

      好处:

      1. 整个对象被保留;

      为了完成:

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

      【讨论】:

        【解决方案3】:

        以下是我想提出的建议:

        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);
            }
        }
        

        一些值得注意的点:

        1. 在声明List&lt;Animal&gt; listAnimal中使用List接口
        2. 在创建对象时使用界面动物Animal animal;
        3. abstract类动物
        4. Setter 返回this 以使代码更清晰。或者你必须使用像animal.setD(4);animal.setE(5);这样的代码

        这样我们可以利用接口Animal并设置一次通用属性。希望这会有所帮助。

        【讨论】:

          【解决方案4】:

          回答

          这样做的一种方法是将适当的构造函数添加到您的类中。往下看:

          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;
             } 
          }
          

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

          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抽象类不能被实例化,但可以被子类化并且可以包含抽象方法。这些方法是在没有主体的情况下声明的,这意味着所有子类都必须提供它们自己的实现。这是一个例子:

          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()!在前面的示例中,使用动物列表,您可以执行以下操作:

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

          【讨论】:

          • Dog(int a, int b, int c, int d, int e)这样的构造函数吓到我了
          • 哈哈是的,如果你不小心,长构造函数确实会变得混乱。但是,它们在某些情况下可能很有用。喜欢你个人资料照片中的猫,顺便说一句。
          • @AndrewTobilko:#MeToo。
          【解决方案5】:

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

          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};
          }
          

          现在创建动物:

          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));
          }
          

          这样做的原因是“favor composition over inheritance”。我想表达的是,这只是解决所提出问题的另一种方式。这并不意味着在任何时候都应该优先考虑组合而不是继承。由工程师决定针对出现问题的上下文的正确解决方案。

          reading on this topic很多

          【讨论】:

            【解决方案6】:

            这是一个解决方案,与 slartidan 的解决方案非常接近,但使用 setter 和 builder 的风格,避免使用 dogcat 变量

            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);
            

            【讨论】:

            • 这是一个非常奇怪的解决方案:setter 应该设置值并且什么都不返回。此外,您的子类将具有不同的设置器类型:标准设置器类型(用于 a、b、c)和返回 this 的设置器类型。
            • A、b、c 设置器也可以返回这个
            【解决方案7】:

            拥有Animal 类型变量的想法很好。但你也必须确保使用正确的构造函数:

            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);
            

            提示:如果 Animal 始终是 Cat 或 Dog,请考虑将 Animal abstract。然后,每当您尝试执行 new Animal() 时,编译器都会自动报错。

            【讨论】:

              【解决方案8】:

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

              问题在于这是否适合您的用法:用鸟和鱼代替飞行和游泳。

              这取决于添加的属性是专有的(狗/猫)还是附加的(飞行/游泳/哺乳动物/昆虫/产卵/...)。后者更适合使用地图进行查找。

              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 不需要实现任何接口,但您可以查找(查找或可能作为)功能。这在未来是可扩展的、动态的:可以在运行时更改。

              实现为

              private Map<Class<?>, ?> map = new HashMap<>();
              
              public <T> Optional<T> as(Class<T> type) {
                   return Optional.ofNullable(type.cast(map.get(type)));
              }
              
              <S> void register(Class<S> type, S instance) {
                  map.put(type, instance);
              }
              

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

              Animal flipper = new Animal();
              flipper.register(new Fish() {
                  @Override
                  public boolean inSaltyWater() { return true; }
              });
              

              【讨论】:

              • 你的as 方法可以简化为return Optional.ofNullable(map.get(type)).map(type.cast(instance)),否则是可爱的名字和例子!
              • @Eugene 谢谢,那一刻我不确定,但type.cast(null) 很好。我将更改为安全的代码行。
              【解决方案9】:

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

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

              • AnimalBuilder 构造一个通用的Animal 对象并管理abc 字段
              • CatBuilder 接受 AnimalBuilder(或扩展它)并继续构建一个 Cat 对象来管理 fg 字段
              • DogBuilder 采用 AnimalBuilder(或扩展它)并继续构建一个 Dog 对象来管理 de 字段

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

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

              这将简化结构并使其不那么冗长且更具可读性。

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2013-06-24
                  • 2011-05-02
                  • 2011-06-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-29
                  • 1970-01-01
                  • 2022-11-25
                  相关资源
                  最近更新 更多