Как уменьшить код с помощью суперкласса?

Я хотел бы реорганизовать некоторый код, который в настоящее время состоит из суперкласса и двух подклассов.

Это мои классы:

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

Ответы

Ответ 1

Ваша идея иметь переменную типа 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);

Подсказка. Если животное всегда либо Cat, либо Dog, подумайте о создании abstract животного. Затем компилятор автоматически будет жаловаться всякий раз, когда вы пытаетесь сделать new Animal().

Ответ 2

Процесс создания кошки или собаки сложный, поскольку задействовано много полей. Это хороший пример для шаблона построителя.

Моя идея - написать строитель для каждого типа и организовать отношения между ними. Это может быть состав или наследование.

  • AnimalBuilder создает общий объект Animal и управляет полями a, b, c
  • CatBuilder берет AnimalBuilder (или расширяет его) и продолжает строить объект Cat управляющий полями f, g
  • DogBuilder берет AnimalBuilder (или расширяет его) и продолжает строить объект Dog управляющий полями d, e

Если вы не хотите создавать сборщиков, подумайте о введении статического заводского метода со значимым именем для каждого подкласса:

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

Это упростило бы построение и сделало бы его менее подробным и понятным.

Ответ 3

Ответ

Один из способов сделать это - добавить соответствующие конструкторы в свои классы. Смотри ниже:

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 in this case).




бонус

Если вы не планируете создавать экземпляры класса 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();
} 

Ответ 4

Подумайте о том, чтобы сделать ваши классы неизменными (Эффективное Java 3-е издание, пункт 17). Если требуются все параметры, используйте конструктор или статический заводский метод (Эффективный Java 3rd Edition, пункт 1). Если требуемые и необязательные параметры используют шаблон построителя (Эффективный Java 3rd Edition Item 2).

Ответ 5

Я бы рассмотрел динамический поиск/регистрацию возможностей/возможностей: Flying/Swimming.

Это вопрос, подходит ли это вашему использованию: вместо Flying & Swimming берут Птицу и Рыбу.

Это зависит от того, являются ли добавленные свойства эксклюзивными (Dog/Cat) или добавка (Flying/Swimming/Mammal/Insect/EggLaying/...). Последнее больше подходит для поиска с использованием карты.

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

Реализация выполняет безопасную динамическую трансляцию, так как регистр обеспечивает безопасное заполнение записей (ключ, значение).

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

Ответ 6

В качестве альтернативы вы можете сделать части "Животных" вашей собаки и кошки отдельной сущностью, доступной через интерфейс "Animalian". Делая это, вы сначала создаете общее состояние, а затем предоставляете его конкретному конструктору вида в той точке, в которой это необходимо.

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

Причиной этого является " поддержка композиции над наследованием ". Я хочу сказать, что это просто другой способ решения поставленной проблемы. Это не означает, что композиция должна всегда быть предпочтительной в отношении наследования. Это зависит от инженера, чтобы определить правильное решение для контекста, в котором возникает проблема.

В этой теме много читается.

Ответ 7

Вот решение, достаточно близко к slartidan, но используя setter со стилем строителя, избегая переменных dog и cat

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

Ответ 8

Вот что я хотел бы предложить:

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 в List<Animal> listAnimal объявлений List<Animal> listAnimal
  2. Использование интерфейса животного при создании объекта Animal animal;
  3. abstract класс животных
  4. Сеттеры возвращают this чтобы сделать код чище. Или вам придется использовать код типа animal.setD(4); animal.setE(5);

Таким образом, мы можем использовать интерфейс Animal и установить общие атрибуты один раз. Надеюсь это поможет.

Ответ 9

Обновите свой код, чтобы:

ArrayList<Animal> listAnimal = new ArrayList();

//Other code...

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

Преимущества с новым кодом:

  1. Скрытая сложность: вы скрыли сложность, и теперь вам придется искать способ меньшего кода, написанный почти на простом английском языке, при повторном просмотре кода.

Но теперь методы addDogTo и addCatTo должны быть написаны. Вот как они выглядели бы так:

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. Оба метода являются частными: это означает, что они могут быть вызваны только из класса. Таким образом, вы можете с уверенностью избавиться от проверки ввода на нуль и т.д., Потому что вызывающий (который находится внутри класса) должен позаботиться о том, чтобы не передавать ложные данные своим собственным членам.

Это означает, что теперь нам нужно иметь createDog и createCat. Вот как я напишу эти методы:

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. Оба метода являются частными.

Теперь для написанного выше кода вам придется писать конструкторы для Cat и Dog которые принимают общие атрибуты и конкретные атрибуты для построения объекта. Это может выглядеть так:

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 и generalAttributes общие атрибуты обоих объектов подкласса;
  2. Весь объект сохраняется: вместо вызова конструктора и передачи ему 20 тысяч параметров вы просто передаете его 2, а именно: общий объект атрибута животных и конкретный объект атрибута животных. Эти два параметра содержат остальные атрибуты внутри, и при необходимости они распаковываются внутри конструктора.

Наконец, ваш конструктор Animal будет выглядеть так:

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

Выгоды:

  1. Весь объект сохраняется;

Ради завершения:

  • AnimalAttribute/DogAttribute/CatAttribute есть только поля и геттеры и сеттеры для этих полей;
  • Эти поля являются данными, необходимыми для построения объекта Animal/Dog/Cat.

Ответ 10

Здесь много замечательных предложений. Я бы использовал свой собственный любимый шаблон строителя (но с добавленным вкусом наследования):

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 как общий аргумент. Передайте текущий экземпляр объекта этому классу, когда он вернется.