現在、スーパークラスと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);
タイプ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()
を実行しようとするたびに自動的にエラーを出します。
多くの分野が関係しているため、猫または犬を作成するプロセスは複雑です。 ビルダーパターン の場合に適しています。
私のアイデアは、各タイプのビルダーを作成し、それらの間の関係を整理することです。構成または継承の可能性があります。
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's a, b, c
listAnimal.add(animal);
構成を単純化し、冗長性を減らして読みやすくします。
回答
その方法の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();
}
_
クラスを不変にすることを検討してください(Effective Java 3rd Edition Item 17)。すべてのパラメーターが必要な場合は、コンストラクターまたは静的ファクトリーメソッドを使用してください(Effective Java 3rd Edition項目1)。必須およびオプションのパラメーターがある場合は、ビルダーパターンを使用します(有効なJava 3rd Editionの項目2)。
ここに私が提案したいものがあります:
_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<Animal> listAnimal
_でのListインターフェイスの使用Animal animal;
_abstract
class Animalthis
を返し、コードをよりクリーンにします。または、animal.setD(4);
animal.setE(5);
のようなコードを使用する必要がありますこのようにして、インターフェイスAnimalを利用して、共通の属性を一度設定できます。お役に立てれば。
別の方法として、あなたの犬と猫の「動物」部分を、「アニマル」インターフェースを介して利用できる別個のエンティティにすることができます。これを行うことにより、最初に共通状態を作成し、次に必要な時点で種固有のコンストラクターに提供します。
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));
}
これを行う理由は、「 継承よりもお気に入りの合成 」です。これは、提起された問題を解決するための単なる別の方法であることを表明したいと思います。構成が常に継承よりも優先されるべきという意味ではありません。問題が発生したコンテキストの正しい解決策を決定するのはエンジニアの責任です。
多くのこのトピックを読む があります。
機能/機能の動的な検索/登録を検討します:飛行/水泳。
これがあなたの用途に合っているかどうかは問題です。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; }
});
解決策は、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);
コードのリファクタリング:
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();
}
利点:
generalAttributes
でスーパークラスメソッドを呼び出し、両方のサブクラスオブジェクトの共通属性を処理します。最後に、Animal
のコンストラクターは次のようになります。
public Animal(AnimalAttribute attribute) {
this.a = attribute.getA();
this.b = attribute.getB();
this.c = attribute.getC();
}
利点:
完了のために:
AnimalAttribute
/DogAttribute
/CatAttribute
クラスにはいくつかのフィールドと、それらのフィールドのゲッターとセッターのみがあります。Animal
/Dog
/Cat
オブジェクトを構築するために必要なデータです。ここで多くの素晴らしい提案。私の個人的なお気に入りのビルダーパターンを使用します(ただし、継承フレーバーを追加します)。
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
汎用引数として。オブジェクトの現在のインスタンスを返すときに、このクラスにキャストします。