Factory Method パターンとリフレクションの合わせ技【Java】

Java
スポンサーリンク

Factory Method パターンとリフレクションを使ってシステム設計する機会があったので、その際に得た知見などを実装例を交えて解説いたします。

そもそも1 Factory Method パターンとは

Wikipediaいわく

Factory Method パターン(ファクトリメソッド・パターン)[1]とは、GoF (Gang of Four; 四人組)によって定義されたデザインパターンの1つである。 Factory Method パターンは、他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする。

Factory Method パターン 『ウィキペディア フリー百科事典』
(https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8)
最終更新 2014年2月19日 (水) 15:49
アクセス日時 2022年2月26日(土) 21:15

とのことです。

うーん、難しい。
ググって色々なサイト見ても分からない。

こればかりは作ってみないと分からないということで、理解もそこそこに とりあえず設計・実装していきます。

そもそも2 リフレクションとは

Javaリフレクションでインスタンスを文字列変数で設定したClass名から生成したりすることができます。もちろん他にも色々できることはあります。

Factory Method とリフレクションを設計に組み込んだ理由

フロントからリクエストを受けて処理をするバックエンドシステムを開発する必要がありました。どのようなリクエストでも基本的な処理の流れは同じなのですが、リクエストデータに含まれる “ID” ごとに固有の処理を考える必要がありました。

“ID”については今後も数が増えていくことが予想されたので、単体のバックエンドクラスで処理を捌くには限界がありました(保守的な意味で)。そのため “IDごと” のクラスを作り共通バックエンドクラスでIDごとのクラスインスタンスを生成する設計としました。

次にクラス単位の概要図で説明いたします。

クラス単位の設計

上記がクラスごとの関係を示した概要図になります。MainではフロントのリクエストデータからIDを取得し、IDごとに定義されたUniqueProcessのクラス名を取得します。

取得したクラス名をFactoryに渡し、FactoryでUniqueProcessインスタンスを生成します。

FactoryとUniqueProcessはそれぞれInterfaceをimplementsすることで、抽象・具象関係を明らかにしています。このような設計にすることで、今後IDが増えてもUniqueProcessクラスを作成し、定義ファイルを修正するだけで対応できるようになります。

次にサンプルソースを紹介します。

スポンサーリンク

サンプルソース

サンプルソースにつき例外処理は考慮していません。ご了承ください。

DefinitionFile.json (定義ファイル)

{
    "00001":{
        "uniqueProcessName":"factoryMethodSample.UniqueProcessA"
    },
    "00002":{
        "uniqueProcessName":"factoryMethodSample.UniqueProcessB"
    }
}

Main.java

フロントからリクエストデータを受けて処理をするメインの部分になります。今回はフロントから “ID 00001” を渡された体でコーディングしています。jsonファイルの読み込みには “jackson” を使用しています。

package factoryMethodSample;

import java.io.IOException;
import java.nio.file.Paths;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) {
		
        try {

            ObjectMapper objectMapper = new ObjectMapper();
            // 定義ファイルを取得
            JsonNode json = objectMapper.readTree(Paths.get("ファイルパス\\uniqueProcessName.json").toFile());
            // 定義ファイルから ID 00001 の子階層にあるuniqueProcessNameを取得
            String uniqueProcessName = (json.get("00001").get("uniqueProcessName").toString()).replace("\"", "");
			
            // Factory処理
            FactoryInterface factoryInstance  = new Factory();
            // 定義ファイルから取得したuniqueProcessNameからインスタンスの生成
            UniqueProcessInterface uniqueProcessInstance = factoryInstance.factoryMethod(uniqueProcessName);
			
            // 固有処理
            uniqueProcessInstance.process1();
            uniqueProcessInstance.process2();
			
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FactoryInterface.java

Factryクラスのインターフェイスです。

package factoryMethodSample;

public interface FactoryInterface {

    public UniqueProcessInterface factoryMethod(String uniqueProcessClass);


}

Factory.java

リフレクションを使って、引数で受け取った “uniqueProcessName” からインスタンスを生成しています。

package factoryMethodSample;

import java.lang.reflect.InvocationTargetException;

public class Factory implements FactoryInterface {

    public UniqueProcessInterface factoryMethod(String uniqueProcessName){

        Object uniqieProcessInstance = null;

        try {
            uniqieProcessInstance = Class.forName(uniqueProcessName).getDeclaredConstructor().newInstance();
        
        } catch (ClassNotFoundException| InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException  ex) {
        }
        
        return (UniqueProcessInterface) uniqieProcessInstance;
    }
    
}

UniqueProcessInterface.java

IDごとに用意するクラスのインターフェイスです。

package factoryMethodSample;

public interface UniqueProcessInterface {
    
    public void process1(); 

    public void process2();

}

UniqueProcessA.java & UniqueProcessB.java

IDごとの固有処理を実行するクラスです。AとBを用意しました。

package factoryMethodSample;

public class UniqueProcessA implements UniqueProcessInterface{
    
    public void process1(){
        System.out.println("UniqueProcessA Process1");
    }

    public void process2(){
        System.out.println("UniqueProcessA Process2");        
    }
}
package factoryMethodSample;

public class UniqueProcessB implements UniqueProcessInterface{
    
    public void process1(){
        System.out.println("UniqueProcessB Process1");
    }

    public void process2(){
        System.out.println("UniqueProcessB Process2");
    }
}

サンプルソースの実行結果

Main.javaを実行してみます。

UniqueProcessA Process1
UniqueProcessA Process2

定義ファイルで ID:”00001″ のUniqueProcessAのメソッドが実行されました。
試しにMain.javaの json.get(“00001”) の部分を json.get(“00002”) に書き換えてみると。

UniqueProcessB Process1
UniqueProcessB Process2

ID:”00002″ のUniqueProcessBが実行されました。

まとめ

Factory Method パターンとリフレクションの合わせ技で、拡張性の高いシステム設計をしましたがもちろん弱点もあります。

今回はjsonで作った定義ファイルのクラス名からインスタンスを生成していますので、定義ファイルが正しいことが大前提となります。なので開発保守の際に定義ファイルの修正を忘れずにする必要があります。また定義ファイルの読み込みに加えリフレクション処理も増えるので、処理時間がどうしても増えてしまいます。定義ファイルについては外部ファイルの参照をやめるか、サーバーのグローバル定数として定義するなどして対応できます。リフレクションについてはよほど厳しい要件でない限り、処理時間の遅延は許容範囲かと思います。

リフレクションの処理時間について、検証されている方の記事です。

最後まで読んでいただきありがとうございました!

タイトルとURLをコピーしました