it-swarm.com.ru

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

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

Это мои занятия:

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);
38
Jax Teller

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

37
slartidan

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

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

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

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

15
Andrew Tobilko

Ответ

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

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();
} 
11
Talendar

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

4
Diego Marin

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

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

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

4
mayurmadnani

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

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

Существует много из читая эту тему .

4
justin.hughey

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

Вопрос в том, подходит ли вам это использование: вместо Flying & Swim возьмите 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; }
});
4
Joop Eggen

Вот решение, довольно близкое к решению 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);
4
ToYonos

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

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. Code is DRY: Оба конструктора вызывают метод суперкласса с помощью 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.
1
displayName

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

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
nabster