superclase simple sencillo que polimorfismo herencia ejercicios ejemplos ejemplo definicion java inheritance subclass superclass

java - simple - ¿Cómo reducir el código utilizando una superclase?



super en java (10)

Aquí hay una solución, bastante parecida a la de Slartidan, pero que utiliza el setter con el estilo del constructor, evitando las variables dog y 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);

Me gustaría refactorizar un código que actualmente consiste en una superclase y dos subclases.

Estas son mis clases:

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

Este es mi código actual:

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

¿Cómo puedo refactorizar el código con respecto a los atributos comunes?

Me gustaría algo así:

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


Como alternativa, puede hacer que las partes "Animales" de su perro y gato sean una entidad independiente que esté disponible a través de la interfaz "Animalian". Al hacer esto, creará primero el estado común y luego lo proporcionará al constructor específico de la especie en el momento en que sea necesario.

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

Ahora para crear animales:

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

La razón para hacer esto es " favorecer la composición sobre la herencia ". Quiero expresar que esta es simplemente una forma diferente de resolver el problema planteado. No significa que la composición deba favorecerse sobre la herencia en todo momento. Depende del ingeniero determinar la solución correcta para el contexto en el que surge el problema.

Hay mucha lectura sobre este tema .


Consideraría una búsqueda / registro dinámico de capacidades / características: vuelo / natación.

La pregunta es si esto se ajusta a tu uso: en lugar de Flying & Swimming, toma Bird and Fish.

Depende de si las propiedades agregadas son exclusivas (Dog / Cat) o aditivas (Flying / Swimming / Mammal / Insect / EggLaying / ...). Este último es más para una búsqueda utilizando un mapa.

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 no necesita implementar ninguna interfaz, pero puedes buscar (buscar o quizás) las capacidades. Esto es extensible en el futuro, dinámico: puede cambiar en tiempo de ejecución.

Implementación como

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

La implementación realiza una conversión dinámica segura, ya que el registro garantiza el llenado seguro de las entradas (clave, valor).

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


Considere hacer que sus clases sean inmutables (Artículo 3 de la tercera edición de Java vigente). Si todos los parámetros son necesarios, utilice un constructor o un método de fábrica estático (Artículo 1 de la 3ª edición de Java vigente). Si hay parámetros obligatorios y opcionales, use el patrón de creación (Artículo 2 de la 3ª edición de Java vigente).


El proceso de construir un gato o un perro es complejo, ya que hay muchos campos involucrados. Ese es un buen caso para el patrón de constructor .

Mi idea es escribir un constructor para cada tipo y organizar las relaciones entre ellos. Podría ser composición o herencia.

  • AnimalBuilder construye un objeto Animal general y administra los campos a, b , c
  • CatBuilder toma un AnimalBuilder (o lo extiende) y continúa construyendo un objeto Cat que administra los campos f , g
  • DogBuilder toma un AnimalBuilder (o lo extiende) y continúa construyendo un objeto Dog que administra los campos d , e

Si no desea crear constructores, considere la posibilidad de introducir un método de fábrica estático con un nombre significativo para cada subclase:

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

Simplificaría la construcción y la haría menos verbosa y más legible.


Esto es lo que me gustaría proponer:

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

Algunos puntos notables:

  1. Uso de la interfaz de la Lista en la declaración List<Animal> listAnimal
  2. Uso de la interfaz animal mientras que la creación del objeto Animal animal;
  3. clase abstract animal
  4. Setters devolviendo this para hacer el código más limpio. O tendrías que usar código como animal.setD(4); animal.setE(5);

De esta manera podemos utilizar la interfaz Animal y establecer los atributos comunes una vez. Espero que esto ayude.


Muchas grandes sugerencias aquí. Usaría mi patrón de constructor favorito personal (pero con un sabor de herencia añadido):

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

Nota: el constructor Animal.Builder toma Class builderClass como argumento genérico. Convierte la instancia actual del objeto a esta clase cuando regrese.


Tu idea de tener una variable de tipo Animal es buena. Pero también debes asegurarte de usar el constructor correcto:

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

Consejo: si un animal es siempre un gato o un perro, considera hacer un animal abstract . Luego, el compilador se quejará automáticamente cada vez que intentes hacer un new Animal() .


Refactoriza tu código a:

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

Beneficios con el nuevo código:

  1. Complejidad oculta : ha ocultado la complejidad y ahora tendrá que buscar un código más pequeño, escrito casi en inglés, cuando vuelva a visitar el código más tarde.

Pero ahora, los métodos addDogTo y addCatTo deberían escribirse. Así es como se verían:

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

Beneficios:

  1. Complejidad oculta ;
  2. Ambos métodos son privados : Esto significa que solo se pueden llamar desde la clase. Por lo tanto, puede dejar de revisar la entrada en busca de valores nulos, ya que la persona que llama (que está dentro de la clase) debe tener cuidado de no pasar datos falsos a sus propios miembros.

Esto significa que ahora necesitamos tener los métodos createDog y createCat en su lugar. Así es como escribiría esos métodos:

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

Beneficios:

  1. Complejidad oculta ;
  2. Ambos métodos son privados .

Ahora, para el código escrito anteriormente, tendrá que escribir constructores para Cat y Dog que tomen los atributos comunes y los atributos específicos para la construcción del objeto. Eso puede parecer:

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

y,

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

Beneficios:

  1. El código es DRY: ambos constructores llaman al método de superclase con generalAttributes y se ocupan de los atributos comunes de ambos objetos de subclase;
  2. Se conserva todo el objeto: en lugar de llamar a un constructor y pasarle 20 mil parámetros, simplemente se le pasa 2, a saber. Objeto de atributo animal general y objeto de atributo animal específico. Estos dos parámetros contienen el resto de los atributos y se desajustan dentro del constructor cuando es necesario.

Finalmente, el constructor de tu Animal se verá como:

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

Beneficios:

  1. Todo el objeto se conserva ;

Por el bien de la finalización:

  • AnimalAttribute / DogAttribute / CatAttribute solo tienen algunos campos y los DogAttribute y CatAttribute para esos campos;
  • Estos campos son los datos necesarios para construir el objeto Animal / Dog / Cat .

Responder

Una forma de hacerlo es agregar los constructores adecuados a sus clases. Mira abajo:

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

Ahora, para crear una instancia de los objetos, puede hacer lo siguiente:

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 }

Este es el método que usaría en este caso. Mantiene el código más limpio y más legible al evitar toneladas de métodos "establecidos". Tenga en cuenta que super() es una llamada al constructor de superclase ( Animal en este caso).



Prima

Si no planea crear instancias de la clase Animal , debe declararlo como abstract . Las clases abstractas no se pueden crear instancias, pero se pueden clasificar en subclases y pueden contener métodos abstractos . Esos métodos se declaran sin un cuerpo, lo que significa que todas las clases de niños deben proporcionar su propia implementación. Aquí hay un ejemplo:

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

¡Ahora puedes llamar eat() para cualquier animal! En el ejemplo anterior, con la lista de animales, podría hacer lo siguiente:

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