web-dev-qa-db-ja.com

スーパークラスを使用してコードを削減する方法は?

現在、スーパークラスと2つのサブクラスで構成されているコードをリファクタリングしたいと思います。

これらは私のクラスです:

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);
39
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オブジェクトを構築し、abcフィールドを管理します
  • CatBuilderAnimalBuilder(または拡張)を取り、Catfフィールドを管理するgオブジェクトの構築を続けます
  • DogBuilderAnimalBuilder(またはそれを拡張)を取り、Dogdフィールドを管理するeオブジェクトの構築を続けます

ビルダーを作成したくない場合は、各サブクラスに意味のある名前を持つ静的ファクトリーメソッドを導入することを検討してください。

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

構成を単純化し、冗長性を減らして読みやすくします。

15
Andrew Tobilko

回答

その方法の1つは、適切なコンストラクターをクラスに追加することです。以下を見てください:

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

これは、この場合に使用する方法です。大量の「set」メソッドを回避することで、コードをよりクリーンで読みやすくします。 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)。すべてのパラメーターが必要な場合は、コンストラクターまたは静的ファクトリーメソッドを使用してください(Effective Java 3rd Edition項目1)。必須およびオプションのパラメーターがある場合は、ビルダーパターンを使用します(有効なJava 3rd Editionの項目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<Animal> listAnimal_でのListインターフェイスの使用
  2. オブジェクト作成中のインターフェイス動物の使用_Animal animal;_
  3. abstract class Animal
  4. セッターはthisを返し、コードをよりクリーンにします。または、animal.setD(4);animal.setE(5);のようなコードを使用する必要があります

このようにして、インターフェイスAnimalを利用して、共通の属性を一度設定できます。お役に立てれば。

4
mayurmadnani

別の方法として、あなたの犬と猫の「動物」部分を、「アニマル」インターフェースを介して利用できる別個のエンティティにすることができます。これを行うことにより、最初に共通状態を作成し、次に必要な時点で種固有のコンストラクターに提供します。

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&Swimmingの代わりに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. 隠された複雑さ:複雑さを隠しているので、後でコードを再検討するときに、ほとんど平易な英語で書かれた、より小さなコードを見る必要があります。

しかし、今ではメソッドaddDogToaddCatToを書く必要があります。これは彼らがどのように見えるかです:

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. 両方のメソッドはプライベートです:これは、クラス内からのみ呼び出すことができることを意味します。そのため、呼び出し側(クラス内)は偽のデータを自身のメンバーに渡さないように注意する必要があるため、nullなどの入力のチェックを安全に行うことができます。

これは、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. 両方のメソッドはプライベートです

さて、上記のコードでは、オブジェクト構築のための共通の属性と特定の属性を取り込むCatDogのコンストラクターを作成する必要があります。次のようになります。

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. コードはDRY:両方のコンストラクターは、generalAttributesでスーパークラスメソッドを呼び出し、両方のサブクラスオブジェクトの共通属性を処理します。
  2. オブジェクト全体が保存されます:コンストラクターを呼び出して2万個のパラメーターを渡す代わりに、2を渡すだけです。一般的な動物属性オブジェクトと特定の動物属性オブジェクト。これらの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