Как уменьшить код с помощью суперкласса? - PullRequest
0 голосов
/ 26 сентября 2018

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

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

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

Ответы [ 10 ]

0 голосов
/ 06 октября 2018

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

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

0 голосов
/ 03 октября 2018

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

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

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

0 голосов
/ 04 октября 2018

Измените код на:

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. Код СУХОЙ: Оба конструктора вызывают метод суперкласса с 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 объект.
0 голосов
/ 27 сентября 2018

В качестве альтернативы, вы можете сделать части "Animal" вашей собаки и кошки отдельной сущностью, доступной через интерфейс "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));
}

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

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

0 голосов
/ 01 октября 2018

Вот решение, довольно близкое к решению Слартидана, но использующее 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);
0 голосов
/ 26 сентября 2018

Ответ

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

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();
} 
0 голосов
/ 26 сентября 2018

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

Вопрос в том, подходит ли это для вашего использования: вместо Полета и Плавания берите Bird and Fish.

Зависит от того, являются ли добавленные свойства исключительными (Собака / Кошка) или добавочными (Полет / Плавание / Млекопитающее / Насекомое / Яйцо / ...).Последнее больше подходит для поиска с использованием карты.

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; }
});
0 голосов
/ 26 сентября 2018

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

0 голосов
/ 26 сентября 2018

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

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

  • 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's a, b, c
listAnimal.add(animal);

Это упростит конструкцию, сделает ее менее многословной и более удобочитаемой.

0 голосов
/ 26 сентября 2018

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

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

...