web-dev-qa-db-ja.com

Javaでのファクトリメソッドの作成

現在、与えられた文字列に基づいてファクトリとして機能するメソッドがあります。例えば:

public Animal createAnimal(String action)
{
    if (action.equals("Meow"))
    {
        return new Cat();
    }
    else if (action.equals("Woof"))
    {
        return new Dog();
    }

    ...
    etc.
}

私がやりたいのは、クラスのリストが大きくなったときにif-elseの問題全体を回避することです。クラスに文字列を登録するメソッドと、アクションの文字列に基づいてクラスを返すメソッドの2つが必要だと思います。

これをJavaで行うための良い方法は何ですか?

40
Brad

あなたがしたことはおそらく、ストリングのスイッチが利用できるようになるまで、それを回避する最良の方法です。 (2019を編集:文字列のスイッチが利用可能です-それを使用してください。)

ファクトリオブジェクトと文字列からこれらへのマップを作成できます。しかし、これは現在のJavaでは少し冗長になります。

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,AnimalFactory>() {{
        put("Meow", new AnimalFactory() { public Animal create() { return new Cat(); }});
        put("Woof", new AnimalFactory() { public Animal create() { return new Dog(); }});
    }});

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw new EhException();
    }
    return factory.create();
}

この回答が最初に作成された時点では、JDK7向けの機能により、コードは次のようになります。結局のところ、ラムダはJava SE 8に登場しました。私が知る限り、マップリテラルの計画はありません。(Edited 2016

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap = {
    "Meow" : { -> new Cat() },
    "Woof" : { -> new Dog() },
};

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.create();
}

2019を編集:現在、これは次のようになります。

import Java.util.function.*;
import static Java.util.Map.entry;

private static final Map<String,Supplier<Animal>> factoryMap = Map.of(
    "Meow", Cat::new, // Alternatively: () -> new Cat()
    "Woof", Dog::new // Note: No extra comma like arrays.
);

// For more than 10, use Map.ofEntries and Map.entry.
private static final Map<String,Supplier<Animal>> factoryMap2 = Map.ofEntries(
    entry("Meow", Cat::new),
    ...
    entry("Woof", Dog::new) // Note: No extra comma.
);

public Animal createAnimal(String action) {
    Supplier<Animal> factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.get();
}

パラメータを追加する場合は、SupplierFactoryに切り替える必要があります(また、getapplyになり、コンテキストでも意味がありません。 )。 2つのパラメータBiFunctionの場合。 3つ以上のパラメーターを使用すると、再度読み取り可能にしようとすることに戻ります。

53

このソリューションではマップは必要ありません。基本的に、マップは基本的に、if/elseステートメントを実行するための別の方法です。少しリフレクションを利用してください。すべてに対して機能するのは数行のコードだけです。

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}

引数を "Woof"と "Meow"から "Cat"と "Dog"に変更する必要がありますが、これは簡単に実行できるはずです。これにより、一部のマップでクラス名を持つ文字列の「登録」が回避され、追加する今後のアニマルでコードが再利用可能になります。

13
bluedevil2k

haveを使用して文字列を使用しない場合は、アクションに列挙型を使用して、抽象ファクトリメソッドを定義できます。

...
public enum Action {
    MEOW {
        @Override
        public Animal getAnimal() {
            return new Cat();
        }
    },

    WOOF {
        @Override
        public Animal getAnimal() {
            return new Dog();
        }
    };

    public abstract Animal getAnimal();
}

その後、次のようなことができます:

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...

ちょっとファンキーですが、うまくいきます。このようにして、すべてのアクションに対してgetAnimal()を定義しない場合、コンパイラーは、存在しないアクションを渡すことはできません。

12
Jeff

Scannotationsを使用してください!

ステップ1。以下のような注釈を作成します。

package animal;

import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}

RetentionPolicyはランタイムであり、リフレクションを介してこれにアクセスすることに注意してください。

ステップ2。(オプション)共通のスーパークラスを作成します。

package animal;

public abstract class Animal {

    public abstract String greet();

}

ステップ3。新しいアノテーションを使用してサブクラスを作成します。

package animal;

@AniMake(action="Meow")
public class Cat extends Animal {

    @Override
    public String greet() {
        return "=^meow^=";
    }

}
////////////////////////////////////////////
package animal;

@AniMake(action="Woof")
public class Dog extends Animal {

    @Override
    public String greet() {
        return "*WOOF!*";
    }

}

ステップ4。ファクトリを作成します。

package animal;

import Java.util.Set;

import org.reflections.Reflections;

public class AnimalFactory {

    public Animal createAnimal(String action) throws InstantiationException, IllegalAccessException {
        Animal animal = null;
        Reflections reflections = new Reflections("animal");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(AniMake.class);

        for (Class<?> clazz : annotated) {
            AniMake annoMake = clazz.getAnnotation(AniMake.class);
            if (action.equals(annoMake.action())) {
                animal = (Animal) clazz.newInstance();
            }
        }

        return animal;
    }

    /**
     * @param args
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        AnimalFactory factory = new AnimalFactory();
        Animal dog = factory.createAnimal("Woof");
        System.out.println(dog.greet());
        Animal cat = factory.createAnimal("Meow");
        System.out.println(cat.greet());
    }

}

このファクトリーは少しクリーンアップできます。厄介なチェック例外などに対処する.
このファクトリでは、 Reflections ライブラリを使用しました。
これは難しい方法で行いました。つまり、Mavenプロジェクトを作成せず、依存関係を手動で追加する必要がありました。
依存関係は次のとおりです:

ステップ2をスキップした場合は、ファクトリメソッドを変更してObjectを返す必要があります。
この時点から、サブクラスを追加し続けることができます。ただし、それらにAniMake(または適切な名前)で注釈を付け、Reflectionsコンストラクタで定義されたパッケージに配置することができます(この場合) "animal")、デフォルトの引数なしのコンストラクターを表示したままにしておくと、ファクトリー自体を変更する必要なく、クラスがインスタンス化されます。

出力は次のとおりです。

log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=
8
crowne

私はこれを試していませんが、「Meow」などをキーとしてMapを作成し、(たとえば)Cat.class値として。

インターフェースを介して静的インスタンスを生成し、次のように呼び出します

Animal classes.get("Meow").getInstance()
5
Rui Vieira

StringのEnum表現を取得し、それをオンに切り替えます。

4
rich

あなたはすでにその質問への回答を選択しましたが、それでも解決する可能性があります。

私は.NET/C#開発者ですが、これは実際には一般的なOOP問題です。同じ種類の問題で実行しており、IoCコンテナ

まだ使用していない場合は、それがおそらく開始する良い理由です。 JavaのIoCコンテナーはわかりませんが、同様の機能を持つコンテナーが存在する必要があると思います。

私が持っていたのは、IoCコンテナーへの参照を含むファクトリーで、これは(BootStrapperの)コンテナー自体によって解決されます

...
public AnimalFactory(IContainer container) 
{ 
    _container = container; 
}

次に、IoCコンテナーをセットアップして、キー(例ではサウンド)に基づいて正しいタイプを解決します。ファクトリが返す必要がある具象クラスを完全に抽象化します。

最後に、あなたのファクトリーメソッドはこれに縮小されます:

...
public Createable CreateAnimal(string action) 
{ 
    return _container.Resolve<Createable>(action); 
} 

このstackoverflowの質問 は、実世界の要素に関する同じ種類の問題を示し、検証された回答は私のソリューションのドラフト(疑似コード)を示しています。私は後で 実際のコードの断片を含むブログ投稿 を書いたところ、より明確になりました。

これがお役に立てば幸いです。しかし、それは単純なケースではやり過ぎかもしれません。 3つのレベルの依存関係を解決する必要があり、IoCコンテナーがすべてのコンポーネントを既に組み立てているため、これを使用しました。

2
Stéphane

そして、人々はClass.newInstance()をトム・ホーティンの答えの中で使用することについてどう思いますか?これにより、不要な匿名クラスをメモリに保存することが回避されますか?プラスコードはよりクリーンになります。

次のようになります。

private static final Map<String,Class> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,Class>() {{
        put("Meow", Cat.class);
        put("Woof", Dog.class);
}});

public Animal createAnimal(String action) {
    return (Animal) factoryMap.get(action).newInstance();
}
1
goRGon

私の考えは、どういうわけか文字列を関数にマッピングすることでしょう。これにより、Meowをマップに渡し、すでにマップされているコンストラクター関数を返すことができます。 Javaでこれを行う方法はわかりませんが、クイック検索で this SO thread が返されました。他の誰かがより良いアイデアを持っている可能性があります。

1
Dave McClelland

これで、Java 8つのコンストラクター参照と関数インターフェースを使用できます。

import Java.util.HashMap;
import Java.util.Map;
import Java.util.function.Supplier;

public class AnimalFactory {
    static final Map<String, Supplier<Animal>> constructorRefMap = new HashMap<>();

    public static void main(String[] args) {
        register("Meow", Cat::new);
        register("Woof", Dog::new);

        Animal a = createAnimal("Meow");
        System.out.println(a.whatAmI());
    }

    public static void register(String action, Supplier<Animal> constructorRef) {
        constructorRefMap.put(action, constructorRef);
    }

    public static Animal createAnimal(String action) {
        return constructorRefMap.get(action).get();
    }
}

interface Animal {
    public String whatAmI();
}

class Dog implements Animal {
    @Override
    public String whatAmI() {
        return "I'm a dog";
    }
}

class Cat implements Animal {
    @Override
    public String whatAmI() {
        return "I'm a cat";
    }
}
0
Ron McLeod