2017-09-07 6 views
6

URLを解析してデータを抽出するライブラリがあります。 URLごとに1つのクラスがあります。どのクラスがユーザーから提供されたURLを処理するかを知るために、私は以下のコードを持っています。工場で正しい実装を動的に呼び出す

public class HostExtractorFactory { 

private HostExtractorFactory() { 
} 

public static HostExtractor getHostExtractor(URL url) 
     throws URLNotSupportedException { 
    String host = url.getHost(); 

    switch (host) { 
    case HostExtractorABC.HOST_NAME: 
     return HostExtractorAbc.getInstance(); 
    case HostExtractorDEF.HOST_NAME: 
     return HostExtractorDef.getInstance(); 
    case HostExtractorGHI.HOST_NAME: 
     return HostExtractorGhi.getInstance(); 
    default: 
     throw new URLNotSupportedException(
       "The url provided does not have a corresponding HostExtractor: [" 
         + host + "]"); 
    } 
} 

}

問題は、ユーザーが自分のswitch文が成長していることを意味する、解析されるように多くのURLを要求しているです。誰かがパーサを使いこなすたびに、コードを修正する必要があります。

これを終了するには、マップを作成して公開することに決めました。そのクラスが作成されると、ホスト名と抽出者を工場に登録することで自分自身を工場に登録できます。以下は、このアイデアを実装した工場です。

public class HostExtractorFactory { 

private static final Map<String, HostExtractor> EXTRACTOR_MAPPING = new HashMap<>(); 

private HostExtractorFactory() { 
} 

public static HostExtractor getHostExtractor(URL url) 
     throws URLNotSupportedException { 
    String host = url.getHost(); 

    if(EXTRACTOR_MAPPING.containsKey(host)) { 
     return EXTRACTOR_MAPPING.get(host); 
    } else { 
     throw new URLNotSupportedException(
       "The url provided does not have a corresponding HostExtractor: [" 
         + host + "]"); 
    } 
} 

public static void register(String hostname, HostExtractor extractor) { 
    if(StringUtils.isBlank(hostname) == false && extractor != null) { 
     EXTRACTOR_MAPPING.put(hostname, extractor); 
    } 
} 

}

、ユーザーがそのようにそれを使用します。ユーザークラスを:

public class HostExtractorABC extends HostExtractor { 

public final static String HOST_NAME = "www.abc.com"; 

private static class HostPageExtractorLoader { 
    private static final HostExtractorABC INSTANCE = new HostExtractorABC(); 
} 

private HostExtractorABC() { 
    if (HostPageExtractorLoader.INSTANCE != null) { 
     throw new IllegalStateException("Already instantiated"); 
    } 

    HostExtractorFactory.register(HOST_NAME, this); 
} 

public static HostExtractorABC getInstance() { 
    return HostPageExtractorLoader.INSTANCE; 
} 
... 

}

私はこれが機能することはありません気づいたとき、私は自分の背中を撫でました。 URLを受け取ったときにロードされず、コンストラクタが実行されないことを意味するファクトリだけがロードされ、マップは常に空です。だから私はドローイングボードに戻っていますが、これを動作させるためのアイデアや、この厄介なswitch文を取り除くための別のアプローチが欲しいと思います。

S

+1

** reflection **を使用することができます。または、既知の、または「利用したい」クラスをすべてリストする必要があります。設定ファイルなど... 'org.reflections'は、クラスパスをスキャンするための素敵な軽量なlibです。 – dedek

+1

クラス名をシンプルなテキストファイルに列挙してください。シンプルなテキストファイルでは、それらを維持したり、ファクトリの構築を静的コンストラクタに移したり、すべてのエントリに対して 'Class.forname'(https://stackoverflow.com/q/8100376/13075)を使用しますファクトリの初期化メソッドでファイルの – Henrik

+1

または、クラスに適用する注釈を導入し、工場出荷時のクラスパスをスキャンします。 – Henrik

答えて

1

あなたは(私は実装を愛する)について依存性注入を学ぶために私がアドバイスします。次に、あなたのコードよりも

public interface HostExtractorHandler { 
    public String getName(); 
    public HostExtractor getInstance(); 
} 

のようなインタフェースを書くことができるようにすることは、あなたが、あなたのクラスの初期化フェーズでは、あなたのマップを構築することができるだろう、このイ​​ンタフェースを実装するすべてのクラスのために、「尋ねる」ことができます。

+0

サンプルコードを追加して、春にこのインターフェース*を実装するすべてのクラスを* "尋ねる"方法を教えてください。 – dedek

+2

ここに良い例があります:https://dzone.com/articles/load-all-implementors –

2

もう1つの方法は、Service Loaderアプローチを使用することです。あなたの実装を持つ

./resources/META-INF/services/your.package.HostExtractorで、次のようなものを追加します。

their.package1.HostExtractorABC 
their.package2.HostExtractorDEF 
their.package3.HostExtractorGHI 
... 

次に、あなたのコードでは、あなたのような何かを持つことができます。私はパーサを見つけるためにReflections libraryを使用する

HostExtractorFactory() { 
    final ServiceLoader<HostExtractor> loader 
      = ServiceLoader.load(your.package.HostExtractor.class); 

    for (final HostExtractor registeredExtractor : loader) { 
     // TODO - Perform pre-processing which is required. 
     // Add to Map? Extract some information and store? Etc. 
    } 
} 
+0

Wauは、そのようなものがプレーンなJavaで利用可能であることを知らなかった! – dedek

+0

HostExtractorが 'host'を扱うかどうかを尋ねることで、インターフェースメソッドか、単純に' name'部分を 'host'にするだけで、あなたのアプリは一つのSPIインターフェースを知る必要があります。 –

+0

しかし、すべてのクラスを 'META-INF/services/your.package.HostExtractor'ファイルにリストする必要があります... – dedek

1

を。

Reflections reflections = new Reflections("base.package");  
Set<Class<? extends HostExtractor>> extractorTypes = 
    reflections.getSubTypesOf(HostExtractor.class); 

あなたの工場内のインスタンスを作成するために、結果を使用します:

for (Class<? extends HostExtractor> c : extractorTypes) { 
    HostExtractor he = c.newInstance(); 
    EXTRACTOR_MAPPING.put(he.getHostName(), he); 
} 

私はgetHostName方法を作った彼らはすべてのHostExtractorクラスから派生するので、すべてのサブタイプを見つけるためにライブラリを使用するように見えますしかし、基本クラスHostExtractorに追加するのは簡単です。

関連する問題