1

注: これは重複 Enum コードを排除するという質問と非常によく似ていることを認識していますが、拡張機能 (サブクラス化) やジェネリックなどの問題についても言及しているため、これについては個別に議論することが役立つと思います。これが冗長に思われる場合は、お詫び申し上げます。

少し前に書いたプログラムにいくつかの最適化を実装しています。現在のものは、Java での抽象列挙型のサポートの欠如の結果として発生したコードの重複を排除しようとしています (...そして最適化されていない設計であることは認めます)。

これは、現在の問題の単純化されたバージョンです。IConfigResourceDescriptor一部の XML を特定のクラス ( を実装するIResourceRoot)にロードするために必要な情報を提供するインターフェイスがあります。

public interface IConfigResourceDescriptor {
    String getResourceName();
    String getResourceLocation();
    <T extends IResourceRoot> Class<T> getRootClass();
    IConfigType getConfigType();
    ...
}

必要なものに応じて、さまざまなアプリケーションまたは同じアプリケーションのさまざまな部分で定義できるさまざまな構成のセットがあります。たとえば、ここでは と の 2 つのセットを示しResourceDescriptorAています。1 つはビジネス ロジック ( ) を、もう 1 つはユーザー インターフェイス ( )ResourceDescriptorBを重視しています。ご覧のとおり、それらのコードは同一です。これらが分離されている唯一の理由は、これら 2 つの構成セットを互いに独立させておくことが理にかなっているからですが、実装の観点からはまったく同じです。ResourceDescriptorAResourceDescriptorB

public enum ResourceDescriptorA implements IConfigResourceDescriptor {
    MODEL("model.xml", "config/", Model.class, ConfigTypeA.MODEL),
    RULES("rules.xml", "config/validation/", Rules.class, ConfigTypeA.RULES),
    HELP("help.xml", "config/", Help.class, ConfigTypeA.HELP);

    private String resourceName;
    private String resourceLocation;
    private Class<? extends IResourceRoot> rootClass;
    private IConfigType configType;

    private <T extends IResourceRoot> ResourceDescriptorA(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        this.resourceName = resourceName;
        this.resourceLocation = resourceLocation;
        this.rootClass = rootClass;
        this.configType = configType;
    }

    public String getResourceName() {
        return resourceName;
    }
    public String getResourceLocation() {
        return resourceLocation;
    }
    public <T extends IResourceRoot> Class<T> getRootClass() {
        return rootClass;
    }
    public IConfigType getConfigType() {
        return configType;
    }
    ...
}
public enum ResourceDescriptorB implements IConfigResourceDescriptor {
    DIALOGS("dialogs.xml", "config/", Dialogs.class, ConfigTypeB.DIALOGS),
    FORMS("forms.xml", "config/", Forms.class, ConfigTypeB.FORMS),
    MENUS("menus.xml", "config/", Menus.class, ConfigTypeB.MENUS);

    private String resourceName;
    private String resourceLocation;
    private Class<? extends IResourceRoot> rootClass;
    private IConfigType configType;

    private <T extends IResourceRoot> ResourceDescriptorB(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        this.resourceName = resourceName;
        this.resourceLocation = resourceLocation;
        this.rootClass = rootClass;
        this.configType = configType;
    }

    public String getResourceName() {
        return resourceName;
    }
    public String getResourceLocation() {
        return resourceLocation;
    }
    public <T extends IResourceRoot> Class<T> getRootClass() {
        return rootClass;
    }
    public IConfigType getConfigType() {
        return configType;
    }
    ...
}  


解決

私の考えは、コードをヘルパー クラスに移動し、それを列挙型から参照することです。また、呼び出しを実際のクラス (ヘルパー クラス) にラップするだけのメソッドが多くなるのを避けるために、単純に を返すIConfigResourceDescriptor新しいインターフェイスを定義しました。これを使用して、構成の実際の説明を取得できます。列挙型は ではなく を実装するようになりました。IConfigResourceDescriptorProviderIConfigResourceDescriptorIConfigResourceDescriptorProviderIConfigResourceDescriptor

実際の実装を含む新しいヘルパー クラス:

public class ConfigResourceDescriptor implements IConfigResourceDescriptor {

    private String resourceName;
    private String resourceLocation;
    private Class<? extends IResourceRoot> rootClass;
    private IConfigType configType;

    public <T extends IResourceRoot> ConfigResourceDescriptor(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        this.resourceName = resourceName;
        this.resourceLocation = resourceLocation;
        this.rootClass = rootClass;
        this.configType = configType;
    }

    public String getResourceName() {
        return resourceName;
    }
    public String getResourceLocation() {
        return resourceLocation;
    }
    public <T extends IResourceRoot> Class<T> getRootClass() {
        return rootClass;
    }
    public IConfigType getConfigType() {
        return configType;
    }
    ...
}

列挙型によって実装された新しいインターフェイス。実際の記述子を返すだけです。

public interface IConfigResourceDescriptorProvider {
    IConfigResourceDescriptor getResourceDescriptor();
}

列挙型は単純化されています。コンストラクターはConfigResourceDescriptor、パラメーターの値を使用して (ヘルパー クラス) を作成します。はConfigResourceDescriptor実際の記述子です。

public enum ResourceDescriptorProviderA implements IConfigResourceDescriptorProvider {
    MODEL("model.xml", "config/", Model.class, ConfigTypeA.MODEL),
    RULES("rules.xml", "config/validation/", Rules.class, ConfigTypeA.RULES),
    HELP("help.xml", "config/", Help.class, ConfigTypeA.HELP);

    private IConfigResourceDescriptor resourceDescriptor;

    private <T extends IResourceRoot> ResourceDescriptorA(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        resourceDescriptor = new ConfigResourceDescriptor(resourceName,
                  resourceLocation, rootClass, configType);
    }

    public IConfigResourceDescriptor getResourceDescriptor() {
        return resourceDescriptor;
    }
}
public enum ResourceDescriptorProviderB implements IConfigResourceDescriptorProvider {
    DIALOGS("dialogs.xml", "config/", Dialogs.class, ConfigTypeB.DIALOGS),
    FORMS("forms.xml", "config/", Forms.class, ConfigTypeB.FORMS),
    MENUS("menus.xml", "config/", Menus.class, ConfigTypeB.MENUS);

    private IConfigResourceDescriptor resourceDescriptor;

    private <T extends IResourceRoot> ResourceDescriptorB(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        resourceDescriptor = new ConfigResourceDescriptor(resourceName,
                  resourceLocation, rootClass, configType);
    }

    public IConfigResourceDescriptor getResourceDescriptor() {
        return resourceDescriptor;
    }
}

が列挙型ではなくクラスであるという事実ConfigResourceDescriptorは、追加機能を提供するために拡張できることも意味します。おそらく、代わりにResourceDescriptorProviderCインスタンス化する があり、より多くの機能を提供できるようになります。AdvancedConfigResourceDescriptorConfigResourceDescriptor

public class AdvancedConfigResourceDescriptor extends ConfigResourceDescriptor {
    // additional methods
}

IConfigResourceDescriptor現在、列挙が実装されていないという事実はIConfigResourceDescriptorProvider、私が以前行っていた場所を意味します...

ResourceDescriptorProviderA.MODEL.getResourceName();

…今やらなければならない…

ResourceDescriptorProviderA.MODEL.getResourceDescriptor().getResourceName();

...しかし、リソース記述子の使用は私のコードで非常に集中化されており、いくつかの変更を加えるだけで済みます。インターフェイスを実装するすべての列挙型ですべてのラッパー メソッドを定義する必要があるよりも、これの方が気に入っています。私は外部契約を結んでいないので、これを変更してもクライアントを壊すことはありません。

そして質問は...

これは大丈夫ですか、それとも問題になる可能性があると私が予想していないものはありますか? このアプローチに大きな (または小さな、または中程度の) ノーノーが見られますか? より良いアプローチはありますか?

これは繰り返し出てきたものであり、正しいアプローチが何であるかはわかりません。
列挙型の使用に関する良いプラクティスと悪いプラクティスを探してみましたが、(驚くべきことに) 非常に基本的なものや、具体的すぎて役に立たない例しか見つかりませんでした。この件に関して私が読むことができる良い記事/ブログ/本をお勧めできれば、私もそれを感謝します.
ありがとうございました!


ジェネリック

列挙型の代わりにクラスを使用するもう 1 つの利点は、クラス (およびインターフェイスIConfigResourceDescriptor) をジェネリックにできることです。

public interface IConfigResourceDescriptor<T extends IResourceRoot> {
    String getResourceName();
    String getResourceLocation();
    Class<T> getRootClass();
    IConfigType getConfigType();
    ...
}
public class ConfigResourceDescriptor<T extends IResourceRoot> implements IConfigResourceDescriptor<T> {

    private String resourceName;
    private String resourceLocation;
    private Class<T> rootClass;
    private IConfigType configType;

    public ConfigResourceDescriptor(String resourceName,
       String resourceLocation, Class<T> rootClass, IConfigType configType) {
        this.resourceName = resourceName;
        this.resourceLocation = resourceLocation;
        this.rootClass = rootClass;
        this.configType = configType;
    }

    public String getResourceName() {
        return resourceName;
    }
    public String getResourceLocation() {
        return resourceLocation;
    }
    public Class<T> getRootClass() {
        return rootClass;
    }
    public IConfigType getConfigType() {
        return configType;
    }
    ...
}
4

1 に答える 1

1

あなたのアプローチに問題は見られません。将来発生する可能性のある問題が 1 つありますが、これは簡単に回避する必要があります。

に機能を追加したい場合に問題が発生しますenum。特に機能がデータに依存している場合、機能enumが にあるのに、 に添付されたデータがサロゲート オブジェクトにあるという事実に少し神経質になるかもしれません。enum

私はあなたのアプローチが好きで、自分でその使用法を調査するつもりです。

それまでの間、試してみたいテクニックが他に 2 つあります。

あなたのものに似ていますが、enumは、ジェネリック型にenum. これは、例を含む投稿です。ここでの秘訣EnumSetは、列挙型の を親コンストラクターに渡して、共通の機能を提供できるようにすることです。

2 番目の手法は、enumと別のオブジェクトのプロキシの組み合わせを実際に作成する必要があるため、あまり興味がないかもしれません。これは最近投稿された例です

于 2013-04-20T22:16:55.513 に答える