web-dev-qa-db-ja.com

Spring 3アノテーションを使用して簡単なファクトリパターンを実装する

Spring 3アノテーションを使用して単純なファクトリパターンを実装する方法を知りたいと思いました。ドキュメントでは、ファクトリクラスを呼び出してファクトリメソッドを実行するBeanを作成できることを見ました。注釈のみを使用してこれが可能かどうか疑問に思っていました。

現在呼び出しているコントローラーがあります

MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();

MyServiceは、checkStatus()という1つのメソッドを持つインターフェースです。

私のファクトリクラスは次のようになります。

@Component
public class MyServiceFactory {

    public static MyService getMyService(String service) {
        MyService myService;

        service = service.toLowerCase();

        if (service.equals("one")) {
            myService = new MyServiceOne();
        } else if (service.equals("two")) {
            myService = new MyServiceTwo();
        } else if (service.equals("three")) {
            myService = new MyServiceThree();
        } else {
            myService = new MyServiceDefault();
        }

        return myService;
    }
}

MyServiceOneクラスは次のようになります。

@Autowired
private LocationService locationService;

public boolean checkStatus() {
      //do stuff
}

このコードを実行すると、locationService変数は常にヌルになります。これは、工場内でオブジェクトを自分で作成しており、自動配線が行われていないためです。これを正しく機能させるために注釈を追加する方法はありますか?

ありがとう

42
blong824

オブジェクトを手動で作成することにより、Springに自動配線を実行させません。 Springによるサービスの管理も検討してください。

_@Component
public class MyServiceFactory {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

    public static MyService getMyService(String service) {
        service = service.toLowerCase();

        if (service.equals("one")) {
            return myServiceOne;
        } else if (service.equals("two")) {
            return myServiceTwo;
        } else if (service.equals("three")) {
            return myServiceThree;
        } else {
            return myServiceDefault;
        }
    }
}
_

しかし、全体的な設計はかなり貧弱だと思います。一般的なMyService実装を1つ用意し、one/two/three文字列をcheckStatus()の追加パラメータとして渡す方が良いとは思いませんか?何を達成したいですか?

_@Component
public class MyServiceAdapter implements MyService {

    @Autowired
    private MyServiceOne myServiceOne;

    @Autowired
    private MyServiceTwo myServiceTwo;

    @Autowired
    private MyServiceThree myServiceThree;

    @Autowired
    private MyServiceDefault myServiceDefault;

    public boolean checkStatus(String service) {
        service = service.toLowerCase();

        if (service.equals("one")) {
            return myServiceOne.checkStatus();
        } else if (service.equals("two")) {
            return myServiceTwo.checkStatus();
        } else if (service.equals("three")) {
            return myServiceThree.checkStatus();
        } else {
            return myServiceDefault.checkStatus();
        }
    }
}
_

新しいMyService実装を追加するにはMyServiceAdapterの変更も必要になるため(SRP違反)、これはstill設計が不十分です。しかし、これは実際には良い出発点です(ヒント:マップと戦略パターン)。

32

次は私のために働いた:

インターフェイスは、ロジックメソッドと追加のIDメソッドで構成されます。

public interface MyService {
    String getType();
    void checkStatus();
}

いくつかの実装:

@Component
public class MyServiceOne implements MyService {
    @Override
    public String getType() {
        return "one";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceTwo implements MyService {
    @Override
    public String getType() {
        return "two";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

@Component
public class MyServiceThree implements MyService {
    @Override
    public String getType() {
        return "three";
    }

    @Override
    public void checkStatus() {
      // Your code
    }
}

また、工場自体は次のとおりです。

@Service
public class MyServiceFactory {

    @Autowired
    private List<MyService> services;

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @PostConstruct
    public void initMyServiceCache() {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}

そのような実装は、より簡単で、よりクリーンで、はるかに拡張性が高いことがわかりました。新しいMyServiceの追加は、他の場所で変更を加えることなく、同じインターフェースを実装する別のSpring Beanを作成するのと同じくらい簡単です。

63
DruidKuma

ファクトリーBeanをMyServiceFactoryに追加して(Springにファクトリーであることを伝えるために)、register(String service、MyService instance)を追加してから、各サービスを呼び出してください。

@Autowired
MyServiceFactory serviceFactory;

@PostConstruct
public void postConstruct() {
    serviceFactory.register(myName, this);
}

このようにして、必要に応じて各サービスプロバイダーをモジュールに分離できます。Springは、デプロイ済みで利用可能なサービスプロバイダーを自動的に選択します。

9
Jeff Wang

Springに手動で自動配線するように依頼できます。

ファクトリーにApplicationContextAwareを実装してください。次に、工場で次の実装を提供します。

@Override
public void setApplicationContext(final ApplicationContext applicationContext {
    this.applicationContext = applicationContext;
}

そして、Beanを作成した後、以下を実行します。

YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.

これにより、LocationServiceが完全に自動配線されます。

8
rogermenezes

また、Factoryクラスとして機能するタイプ ServiceLocatorFactoryBean のBeanを宣言的に定義することもできます。 Spring 3でサポートされています。

署名付きの1つ以上のメソッド(通常はMyService getService()またはMyService getService(String id))が必要なインターフェースを取り、そのインターフェースを実装する動的プロキシを作成するFactoryBean実装

Springを使用してFactoryパターンを実装する例

もう1つの明確な例

6
kyiu

DruidKuma の回答に従います

リッテは自動配線コンストラクターを使用して工場をリファクタリングします。

@Service
public class MyServiceFactory {

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

    @Autowired
    public MyServiceFactory(List<MyService> services) {
        for(MyService service : services) {
            myServiceCache.put(service.getType(), service);
        }
    }

    public static MyService getService(String type) {
        MyService service = myServiceCache.get(type);
        if(service == null) throw new RuntimeException("Unknown service type: " + type);
        return service;
    }
}
3
Pavel Černý

Org.springframework.beans.factory.config.ServiceLocatorFactoryBeanを使用すると仮定します。コードが大幅に簡素化されます。 MyServiceAdapterを除くuは、メソッドMyService getMyServiceと、クラスを登録するためのアリーを使用して、インターフェースMyServiceAdapterのみを作成できます。

コード

bean id="printStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
        <property name="YourInterface" value="factory.MyServiceAdapter" />
    </bean>

    <alias name="myServiceOne" alias="one" />
    <alias name="myServiceTwo" alias="two" />
2
Mikhail

すべてのサービスクラスをパラメーターとして渡すことにより、「AnnotationConfigApplicationContext」をインスタンス化できます。

@Component
public class MyServiceFactory {

    private ApplicationContext applicationContext;

    public MyServiceFactory() {
        applicationContext = new AnnotationConfigApplicationContext(
                MyServiceOne.class,
                MyServiceTwo.class,
                MyServiceThree.class,
                MyServiceDefault.class,
                LocationService.class 
        );
        /* I have added LocationService.class because this component is also autowired */
    }

    public MyService getMyService(String service) {

        if ("one".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceOne.class);
        } 

        if ("two".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceTwo.class);
        } 

        if ("three".equalsIgnoreCase(service)) {
            return applicationContext.getBean(MyServiceThree.class);
        } 

        return applicationContext.getBean(MyServiceDefault.class);
    }
}
1
OlivierTerrien

これを試して:

public interface MyService {
 //Code
}

@Component("One")
public class MyServiceOne implements MyService {
 //Code
}

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
}
0
Viren Dholakia

PavelČerný here のソリューションに基づいて、このパターンの汎用型付き実装を作成できます。それには、NamedServiceインターフェースを導入する必要があります。

    public interface NamedService {
       String name();
    }

抽象クラスを追加します。

public abstract class AbstractFactory<T extends NamedService> {

    private final Map<String, T> map;

    protected AbstractFactory(List<T> list) {
        this.map = list
                .stream()
                .collect(Collectors.toMap(NamedService::name, Function.identity()));
    }

    /**
     * Factory method for getting an appropriate implementation of a service
     * @param name name of service impl.
     * @return concrete service impl.

     */
    public T getInstance(@NonNull final String name) {
        T t = map.get(name);
        if(t == null)
            throw new RuntimeException("Unknown service name: " + name);
        return t;
    }
}

次に、MyServiceのような特定のオブジェクトの具体的なファクトリを作成します。

 public interface MyService extends NamedService {
           String name();
           void doJob();
 }

@Component
public class MyServiceFactory extends AbstractFactory<MyService> {

    @Autowired
    protected MyServiceFactory(List<MyService> list) {
        super(list);
    }
}

whereコンパイル時にMyServiceインターフェースの実装のリストをリストします。

名前によってオブジェクトを生成するアプリ全体に複数の同様のファクトリがある場合、このアプローチは正常に機能します(名前によってオブジェクトを生成する場合はもちろんビジネスロジックで十分です)。ここで、mapはStringをキーとして適切に機能し、サービスの既存の実装をすべて保持します。

オブジェクトを生成するための異なるロジックがある場合、この追加のロジックを別の場所に移動し、これらのファクトリー(名前でオブジェクトを取得する)と組み合わせて動作できます。

0
Andreas Gelever