web-dev-qa-db-fra.com

Comment réduire le code en utilisant une superclasse?

Je voudrais reformuler un code qui consiste actuellement en une super-classe et deux sous-classes.

Ce sont mes cours:

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

Ceci est mon code actuel:

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

Comment puis-je refactoriser le code concernant les attributs communs? 

Je voudrais quelque chose comme ça:

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

Votre idée d'avoir une variable de type Animal est bonne. Mais vous devez également vous assurer d'utiliser le bon constructeur:

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

Astuce: Si un animal est toujours un chat ou un chien, envisagez de rendre Animal abstract. Ensuite, le compilateur se plaindra automatiquement chaque fois que vous essayez de faire new Animal().

37
slartidan

Le processus de construction d'un chat ou d'un chien est complexe car de nombreux domaines sont impliqués. C'est un bon cas pour le modèle de constructeur .

Mon idée est d'écrire un constructeur pour chaque type et d'organiser les relations entre eux. Ce pourrait être une composition ou un héritage.

  • AnimalBuilder construit un objet Animal général et gère les champs a, b, c
  • CatBuilder prend une AnimalBuilder (ou la prolonge) et continue de construire un objet Cat gérant les champs f, g
  • DogBuilder prend une AnimalBuilder (ou l'étend) et continue à construire un objet Dog gérant les champs d, e

Si vous ne souhaitez pas créer de générateurs, envisagez d'introduire une méthode de fabrique statique avec un nom explicite pour chaque sous-classe:

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

Cela simplifierait la construction et la rendrait moins verbeuse et plus lisible.

15
Andrew Tobilko

Réponse

Une façon de le faire est d'ajouter les constructeurs appropriés à vos classes. Regardez ci-dessous:

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

Maintenant, pour instancier les objets, vous pouvez faire comme suit:

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
} 

C'est la méthode que j'utiliserais dans ce cas. Il garde le code plus propre et plus lisible en évitant des tonnes de méthodes "définies". Notez que super() est un appel au constructeur de la superclasse '(Animal dans ce cas).




Prime

Si vous ne prévoyez pas de créer des instances de la classe Animal, vous devez la déclarer comme étant abstract . Classes abstraites ne peuvent pas être instanciées, mais peuvent être sous-classées et peuvent contenir méthodes abstraites. Ces méthodes sont déclarées sans corps, ce qui signifie que toutes les classes d'enfants doivent fournir leur propre implémentation. Voici un exemple:

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

Maintenant, vous pouvez appeler eat() pour tout animal! Dans l'exemple précédent, avec la liste des animaux, vous pourriez faire comme ci-dessous:

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

Envisagez de rendre vos classes immuables (Effective Java 3rd Edition Item 17). Si tous les paramètres sont requis, utilisez un constructeur ou une méthode fabrique statique (Effective Java 3rd Edition Item 1). S'il existe des paramètres obligatoires et facultatifs, utilisez le modèle de générateur (Effective Java 3rd Edition Item 2).

4
Diego Marin

Voici ce que je voudrais proposer:

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

Quelques points remarquables:

  1. Utilisation de l'interface de liste dans la déclaration List<Animal> listAnimal
  2. Utilisation de l'animal d'interface lors de la création d'objet Animal animal;
  3. abstract classe Animal
  4. Les Setters retournant this pour rendre le code plus propre. Ou vous devriez utiliser un code comme animal.setD(4);animal.setE(5);

De cette façon, nous pouvons utiliser l'interface Animal et définir les attributs communs une fois. J'espère que cela t'aides.

4
mayurmadnani

Au lieu de cela, vous pouvez transformer les parties "Animaux" de votre chien et de votre chat en une entité distincte disponible via l'interface "Animalian". Ce faisant, vous créez d'abord l'état commun, puis vous le transmettez au constructeur spécifique à l'espèce au moment où il est nécessaire.

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

Maintenant pour créer des animaux:

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 raison de cela est de " favoriser la composition par rapport à l'héritage ". Je tiens à dire qu’il s’agit simplement d’une autre façon de résoudre le problème posé. Cela ne signifie pas que la composition doit toujours être privilégiée par rapport à l'héritage. Il appartient à l’ingénieur de déterminer la solution appropriée au contexte dans lequel le problème se pose.

Il y a beaucoup de en train de lire sur ce sujet .

4
justin.hughey

J'envisagerais une recherche/enregistrement dynamique des capacités/fonctionnalités: vol/natation.

La question est de savoir si cela correspond à votre utilisation: au lieu de Flying & Swimming, prenez Bird and Fish.

Cela dépend si les propriétés ajoutées sont exclusives (chien/chat) ou additives (vol/natation/mammifère/insecte/mise en œuf/...). Ce dernier est plus pour une recherche à l'aide d'une carte.

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 n'a besoin d'implémenter aucune interface, mais vous pouvez rechercher des fonctionnalités (de recherche ou peut-être comme). Ceci est extensible à l'avenir, dynamique: peut changer au moment de l'exécution.

Mise en œuvre en tant que

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

L'implémentation effectue une conversion dynamique en toute sécurité, car register assure le remplissage en toute sécurité des entrées (clé, valeur).

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

Voici une solution assez proche de celle de slartidan mais utilisant setter avec le style du constructeur, en évitant les variables dog et 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

Refactor votre code à:

ArrayList<Animal> listAnimal = new ArrayList();

//Other code...

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

Avantages avec le nouveau code:

  1. Complexité cachée : Vous avez caché la complexité et vous devrez maintenant regarder un code plus petit, écrit presque dans un anglais simple, lors de la prochaine consultation de votre code.

Mais maintenant, les méthodes addDogTo et addCatTo devraient être écrites. Voici à quoi ils ressembleraient:

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

Avantages:

  1. Complexité cachée ;
  2. Les deux méthodes sont privées : Cela signifie qu'elles ne peuvent être appelées qu'à partir de la classe. Ainsi, vous pouvez supprimer la vérification de l'entrée, etc. en toute sécurité, car l'appelant (qui fait partie de la classe) doit avoir pris soin de ne pas transmettre de données parasites à ses propres membres.

Cela signifie que nous devons maintenant avoir les méthodes createDog et createCat en place. Voici comment j'écrirais ces méthodes:

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

Avantages:

  1. Complexité cachée ;
  2. Les deux méthodes sont privées .

Maintenant, pour le code écrit ci-dessus, vous devrez écrire des constructeurs pour Cat et Dog qui prennent en compte les attributs communs et les attributs spécifiques pour la construction d'objet. Cela peut ressembler à:

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

et,

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

Avantages:

  1. Le code est DRY: Les deux constructeurs appellent la méthode superclass avec generalAttributes et prennent en charge les attributs communs des deux objets de sous-classe.
  2. L'intégralité de l'objet est préservée: Au lieu d'appeler un constructeur et de lui transmettre 20 000 paramètres, vous ne faites que le transmettre 2, à savoir. attribut animal général et attribut animal spécifique. Ces deux paramètres contiennent le reste des attributs à l'intérieur et ils sont déballés à l'intérieur du constructeur si nécessaire.

Enfin, le constructeur de votre Animal ressemblera à ceci:

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

Avantages:

  1. L'objet entier est conservé ;

Par souci d'achèvement:

  • AnimalAttribute/DogAttribute/CatAttribute les classes n'ont que quelques champs, ainsi que les getters et les setters pour ces champs;
  • Ces champs sont les données nécessaires à la construction de l’objet Animal/Dog/Cat.
1
displayName

Beaucoup de bonnes suggestions ici. J'utiliserais mon modèle de constructeur préféré (mais avec une saveur d'héritage ajoutée):

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

Remarque: Le constructeur Animal.Builder prend Class builderClass en tant qu'argument générique. Convertit l'instance d'objet en cours dans cette classe à son retour.

0
nabster