Как уменьшить код с помощью суперкласса?
Я хотел бы реорганизовать некоторый код, который в настоящее время состоит из суперкласса и двух подклассов.
Это мои классы:
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);
}
}
Некоторые примечательные моменты:
- Использование интерфейса List в
List<Animal> listAnimal
объявлений List<Animal> listAnimal
- Использование интерфейса животного при создании объекта
Animal animal;
-
abstract
класс животных - Сеттеры возвращают
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);
}
Преимущества с новым кодом:
- Скрытая сложность: вы скрыли сложность, и теперь вам придется искать способ меньшего кода, написанный почти на простом английском языке, при повторном просмотре кода.
Но теперь методы 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);
}
Выгоды:
- Скрытая сложность;
- Оба метода являются частными: это означает, что они могут быть вызваны только из класса. Таким образом, вы можете с уверенностью избавиться от проверки ввода на нуль и т.д., Потому что вызывающий (который находится внутри класса) должен позаботиться о том, чтобы не передавать ложные данные своим собственным членам.
Это означает, что теперь нам нужно иметь 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;
}
Выгоды:
- Скрытая сложность;
- Оба метода являются частными.
Теперь для написанного выше кода вам придется писать конструкторы для 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();
}
Выгоды:
- Код DRY: оба конструктора называют метод суперкласса с
generalAttributes
и generalAttributes
общие атрибуты обоих объектов подкласса; - Весь объект сохраняется: вместо вызова конструктора и передачи ему 20 тысяч параметров вы просто передаете его 2, а именно: общий объект атрибута животных и конкретный объект атрибута животных. Эти два параметра содержат остальные атрибуты внутри, и при необходимости они распаковываются внутри конструктора.
Наконец, ваш конструктор Animal
будет выглядеть так:
public Animal(AnimalAttribute attribute) {
this.a = attribute.getA();
this.b = attribute.getB();
this.c = attribute.getC();
}
Выгоды:
- Весь объект сохраняется;
Ради завершения:
-
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
как общий аргумент. Передайте текущий экземпляр объекта этому классу, когда он вернется.