注: これは重複 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 つの構成セットを互いに独立させておくことが理にかなっているからですが、実装の観点からはまったく同じです。ResourceDescriptorA
ResourceDescriptorB
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
新しいインターフェイスを定義しました。これを使用して、構成の実際の説明を取得できます。列挙型は ではなく を実装するようになりました。IConfigResourceDescriptorProvider
IConfigResourceDescriptor
IConfigResourceDescriptorProvider
IConfigResourceDescriptor
実際の実装を含む新しいヘルパー クラス:
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
インスタンス化する があり、より多くの機能を提供できるようになります。AdvancedConfigResourceDescriptor
ConfigResourceDescriptor
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;
}
...
}