multiple - superclass java




Come ridurre il codice usando una superclasse? (7)

Ecco cosa vorrei proporre:

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

Alcuni punti degni di nota:

  1. Uso dell'interfaccia Elenco nella List<Animal> listAnimal dichiarazione List<Animal> listAnimal
  2. Uso dell'interfaccia animale durante la creazione dell'oggetto Animal animal;
  3. classe abstract Animale
  4. Setter che restituiscono this per rendere il codice più pulito. O dovresti usare codice come animal.setD(4); animal.setE(5);

In questo modo possiamo utilizzare l'interfaccia Animal e impostare gli attributi comuni una volta. Spero che questo ti aiuti.

Mi piacerebbe refactoring un codice che attualmente consiste in una superclasse e due sottoclassi.

Queste sono le mie classi:

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

Questo è il mio codice attuale:

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

Come posso refactoring il codice riguardante gli attributi comuni?

Vorrei qualcosa del genere:

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

Ecco una soluzione, abbastanza vicina a quella di slartidan ma che usa setter con lo stile del costruttore, evitando le variabili dog e 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);

In alternativa, puoi rendere le parti "Animali" del tuo cane e gatto un'entità separata disponibile tramite l'interfaccia "Animale". In questo modo, prima crei lo stato comune e successivamente lo fornisci al costruttore specifico della specie nel punto in cui è necessario.

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

Ora per creare animali:

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 ragione per fare questo è " favorire la composizione sull'ereditarietà ". Voglio esprimere che questo è solo un modo diverso di risolvere il problema posto. Ciò non significa che la composizione debba essere favorita sull'eredità in ogni momento. Spetta all'ingegnere determinare la soluzione corretta per il contesto in cui si pone il problema.

C'è molta lettura su questo argomento .


La tua idea di avere una variabile di tipo Animal è buona. Ma devi anche assicurarti di usare il costruttore giusto:

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

Suggerimento: se un animale è sempre un gatto o un cane, considera la possibilità di rendere l'animale abstract . Quindi il compilatore si lamenterà automaticamente ogni volta che proverai a fare un new Animal() .


Prenderò in considerazione una ricerca / registrazione dinamica di funzionalità / funzionalità: Flying / Swimming.

È la domanda se questo si adatta al tuo utilizzo: invece di Flying & Swimming prendi Bird and Fish.

Dipende se le proprietà aggiunte sono esclusive (Cane / Gatto) o additivo (Volare / Nuotare / Mammifero / Insetti / EggLaying / ...). Quest'ultimo è più per una ricerca utilizzando una mappa.

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 non devono implementare alcuna interfaccia, ma puoi cercare (cercare o forse come) capacità. Questo è estensibile in futuro, dinamico: può cambiare in fase di esecuzione.

Implementazione come

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'implementazione esegue una trasmissione dinamica sicura, poiché il registro garantisce il riempimento sicuro delle voci (chiave, valore).

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

Prendi in considerazione l'opportunità di rendere immutabili le tue lezioni (efficace articolo 3a edizione di Java 17). Se tutti i parametri sono obbligatori, utilizzare un costruttore o un metodo factory statico (Effective Java 3rd Edition Item 1). Se sono presenti parametri obbligatori e facoltativi, utilizzare il modello di builder (Effective Java 3rd Edition Item 2).


Risposta

Un modo per farlo è aggiungere i costruttori appropriati alle tue classi. Guarda sotto:

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

Ora, per istanziare gli oggetti, puoi fare come segue:

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
} 

Questo è il metodo che userei in questo caso. Mantiene il codice più pulito e più leggibile evitando quei tonnellate di metodi "set". Si noti che super() è una chiamata al costruttore della superclasse ( Animal in questo caso).



indennità

Se non hai intenzione di creare istanze della classe Animal , dovresti dichiararlo abstract . Le classi astratte non possono essere istanziate, ma possono essere sottoclasse e possono contenere metodi astratti . Questi metodi sono dichiarati senza un corpo, il che significa che tutte le classi di bambini devono fornire la propria implementazione di esso. Ecco un esempio:

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

Ora puoi chiamare eat() per ogni animale! Nell'esempio precedente, con l'elenco degli animali, potresti fare come di seguito:

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






superclass