web-dev-qa-db-ja.com

ジェネリック型継承階層の設計

これで、ユーザーの要求に応じて、郵便局の実際のシナリオを模倣したクラス名のコメントに、元の質問の別の言い換えバージョンを追加しました(言い換えた例がどれほど現実世界かはわかりません)。元の質問には、抽象化/一般化されたクラス名がありました。また、両方の正しいコードをアップロードしました。


言い換えた質問

設計要件

  • Postage and PostageLot:郵便局を運営しています。したがって、class Postageinterface PostageLotがあります。
  • 必要な配達速度に基づいて送料ロットを分類します:class SpeedPostageLotを実装するclass NormalPostageLotinterface PostageLotがあります。
  • 注意することに基づいて送料ロットを分類します:
    • interface ExecutivePostageLotinterface PublicPostageLotがあります。 ExecutivePostageLotPublicPostageLotよりも細心の注意を払っています。コードでの説明のために、ExecutivePostageLotのみを考慮し、PublicPostageLotを記述しなかったため、PublicPostageLotに対応するサブクラスを記述しません
    • 幹部と公共の両方の郵便料金は、速度または通常の郵便料金にすることができます。したがって、SpeedPostageLotを実装するNormalPostageLotおよびExecutivePostageLotのサブクラスを作成しました:ExecutiveSpeedPostageLotおよびExecutiveNotmalPostageLot。 (公共郵便料金の場合も同じ:PublicSpeedPostageLotおよびPublicNormalPostageLot
    • ExecutivePostageクラスもあります
  • CommonSpeedPostageProcessorを処理するときにSpeedPostageLotExecutivePostageProcessorの両方で使用できるPublicPostageProcessorを処理するための共通ロジックを含むSpeedPostageLotもあります。 CommonSpeedPostageLotProcessorのメソッドは、ExecutiveSpeedPostageLotおよびPublicSpeedPostageLotにキャストできる型の値を返す必要があります。これには、PostageLotまたはSpeedPostageLotを使用する必要があると感じました。しかし、以下のコードに見られるように、これは機能していません。

enter image description here

コード

public class Postage {}
public interface PostageLot<T extends Postage> {}
public abstract class NormalPostageLot<T extends Postage> implements PostageLot<T>{}
public abstract class SpeedPostageLot<T extends Postage> implements PostageLot<T>{}
public interface ExecutivePostageLot extends PostageLot<ExecutivePostage> {}
public class ExecutiveSpeedPostageLot extends SpeedPostageLot<ExecutivePostage> implements ExecutivePostageLot {}
public class ExecutiveNormalPostageLot extends NormalPostageLot<ExecutivePostage> implements ExecutivePostageLot {}
public class ExecutivePostage extends Postage { }

public class CommonSpeedPostageProcessor 
{
    PostageLot<Postage> speedPostageProcessor1()
    {
        SpeedPostageLot<Postage> obj = null;
        return obj;
    }

    SpeedPostageLot<Postage> speedPostageProcessor2()
    {
        SpeedPostageLot<Postage> obj = null;
        return obj;
    }
}

public class ExecutivePostageLotHandler {

    public static void main(String[] args) {
        CommonSpeedPostageProcessor logic = new CommonSpeedPostageProcessor();
        ExecutivePostageLot obj = logic.method1EntitySetProcessor1(); //Type mismatch: cannot convert from PostageLot<Postage> to ExecutivePostageLot
        ExecutivePostageLot obj2 = logic.method1EntitySetProcessor2(); //Type mismatch: cannot convert from SpeedPostage<Postage> to ExecutivePostageLot
        ExecutiveSpeedPostageLot obj3 = logic.method1EntitySetProcessor1(); //Type mismatch: cannot convert from PostageLot<Postage> to ExecutiveSpeedPostageLot
        ExecutiveSpeedPostageLot obj4 = logic.method1EntitySetProcessor2(); //Type mismatch: cannot convert from SpeedPostage<Postage> to ExecutiveSpeedPostageLot
    }
}

(EclipseプロジェクトのZipは here にあります)

これらの型キャストは無効であると理解していますが、設計のどこでミスを犯しましたか?主に私はそれが正しく行われていると感じていますが、CommonSpeedPostageProcessorをジェネリック型パラメーターでパラメーター化する必要があるだけです。しかし、私はそれをどのように行うのか正確に推測することができません。 CommonSpeedPostageProcessorをジェネリック型パラメーター化するにはどうすればよいですか?または私は単に物事を複雑にしていますか?


元の質問

アプリケーションのクラス階層を設計しているときに行き詰まりました。この設計制約シナリオの解決策を見つけることができません。

設計要件

(クラスおよびインターフェースの名前は、ドメイン固有の名前による不必要な混乱を避けるために一般化されています)。

  • Entity and EntitySet:組織全体のEntityクラスとエンティティのセットを識別するインターフェイスEntitySetがあり、扱う特定のタイプのエンティティを処理するために必要な基本メソッドを実施します私たちの組織で。
  • メソッド固有のエンティティセット(Method1EntitySetおよびMethod2EntitySet):これで、このエンティティセットを処理する2つのメインメソッドがあります。各メソッドは、EntitySetMethod1EntitySetおよびMethod2EntitySetを実装するクラスを定義します。
  • モジュール固有のインターフェースとクラス:
    • これで、さまざまな開発者グループによって開発されたさまざまなモジュールができました。これらのモジュールはすべてMethod1EntitySetMethod2EntitySetの両方を使用する必要がありますが、これらのクラスにいくつかの共通モジュール固有のメソッドを持たせることも望んでいます。そのため、モジュール固有のメソッドを含むインターフェースを定義します。たとえば、Module1EntitySet
    • 各モジュールは、クラスModule1Method1EntitySetModule1Method2EntitySetの別のペアも定義します。どちらも対応するメソッド固有のクラス(つまり、それぞれMethod1EntitySetMethod2EntitySet)を拡張し、モジュール固有のインターフェース(つまりModule1EntitySet)。
    • また、各モジュールは独自のエンティティを定義しますModule1Entity
  • 一般的なロジック:処理の共通機能(すべてのモジュールで使用される)を含むクラス(CommonMethod1EntitySetLogic)が欲しいMethod1EntitySetMethod1EntitySetを扱うすべてのモジュールは、この共通クラスを使用します。したがって、CommonMethod1EntitySetLogicクラスのメソッドは、モソドが呼び出されるModuleXEntitySetModuleXMethod1EntitySetまたはModuleXにキャストできる型の値を返す必要があります。このタイプの戻り値は、モジュール固有ではないため、EntitySetまたはMethod1EntitySetになる可能性があると感じました。しかし、以下のコードに見られるように、これは機能していません。

注:上記のすべてのクラス名で、ジェネリック型の指定を省略して、設計要件を混乱させないようにしています。しかし、これは誤った質問につながる可能性があります。だから私は以下のコードを提供しています。

enter image description here

コード

public class Entity { }
public interface EntitySet<T extends Entity> { }
public abstract class Method1EntitySet<T extends Entity> implements EntitySet<T>{}
public abstract class Method2EntitySet<T extends Entity> implements EntitySet<T>{}
public class Module1Entity extends Entity{}
public interface Module1EntitySet extends EntitySet<Module1Entity>{}
public class Module1Method1EntitySet extends Method1EntitySet<Module1Entity> implements Module1EntitySet{}
public class Module1Method2EntitySet extends Method2EntitySet<Module1Entity> implements Module1EntitySet{}

public class CommonMethod1EntitySetLogic 
{
    EntitySet<Entity> method1EntitySetProcessor1()
    {
        Method1EntitySet<Entity> obj = null;
        return obj;
    }

    Method1EntitySet<Entity> method1EntitySetProcessor2()
    {
        Method1EntitySet<Entity> obj = null;
        return obj;
    }
}

上記のすべての主なクラスが終わったので、コメントで指定されているようにコンパイル時エラーが発生します。

public class Module1 {
    public static void main(String[] args) {
        CommonMethod1EntitySetLogic logic = new CommonMethod1EntitySetLogic();
        Module1EntitySet obj = logic.method1EntitySetProcessor1(); //Type mismatch: cannot convert from EntitySet<Entity> to Module1EntitySet
        Module1EntitySet obj2 = logic.method1EntitySetProcessor2(); //Type mismatch: cannot convert from Method1EntitySet<Entity> to Module1EntitySet
        Module1Method1EntitySet obj3 = logic.method1EntitySetProcessor1(); //Type mismatch: cannot convert from EntitySet<Entity> to Module1Method1EntitySet
        Module1Method1EntitySet obj4 = logic.method1EntitySetProcessor2(); //Type mismatch: cannot convert from Method1EntitySet<Entity> to Module1Method1EntitySet
    }
}

これらの型キャストは無効であると理解していますが、設計のどこでミスを犯しましたか?主に私はそれが正しく行われていると感じていますが、CommonMethod1EntitySetLogicをジェネリック型パラメーターでパラメーター化する必要があるだけです。しかし、私はそれをどのように行うのか正確に推測することができません。ジェネリック型のパラメーター化はどのように行うべきですかCommonMethod1EntitySetLogic?または私は単に物事を複雑にしていますか?

(EclipseプロジェクトのZipは here にあります。)

4
Maha

タイプシステムの悪用?

郵便局の例を読むと、型システムを悪用してインターフェースのプロパティ値をエンコードしようとしている可能性があることを示唆する証拠がいくつかあります。そして失敗する...

現在実装している方法、SpeedとNormalは相互に排他的ですが、クラスがExecutivePostageLotPublicPostageLotを同時に使用できない理由はありません。

ただし、説明から、速度(速度と通常)と重要度(エグゼクティブとパブリック)の2つの次元に沿って、郵便料金のロット(およびその問題については送料)を分類したいようです。

もしそうなら、それぞれの値のためのインターフェースを持つことは、あなたがそれに取り組むべき方法ではありません。

代替解釈

しかし、あなたの質問と図は、あなたがやろうとしていることを解釈する別の方法を可能にしているようです。想像しているかもしれませんが、次のことを実行したいと思います(別の例を使用しますが、ダイアグラムに1:1でマッピングする必要があります)。

一般的なメソッドを持つEntityクラスがあります。 int Id()およびDateTime Created()

複数のモジュールがあり、各モジュールは独自のEntityサブクラスを定義しています。たとえば、購入モジュール(OrderProductなどのエンティティを含む)とニュースレターモジュール(PostSubscriptionなどのエンティティを含む)があります。

さて、Entityの特定のサブクラスのさまざまな種類のセットを操作したいようです。それらのセットの一部は、エンティティの種類に固有ではない一般的な機能のみを提供する必要があります。たとえば、次のようなSortableSetがあるとします。

class SortableSet<T extends Entity> 
    implements EntitySet<T>
{
    // resorts the set (i know, this makes no sense for a set).
    void Sort(IComparer<T> cmp);

    // ... whatever methods are required by EntitySet ...
    // ... these might also be abstract, of course ...
}

ただし、各モジュールには、セットに追加する特定の機能が必要な場合もあります。ニュースレターモジュールは、新しいスーパーポストを形成するために、セット内のすべての投稿を組み合わせる必要がある場合があります。

// assuming Post has subclasses, otherwise this does not
// need to be generic, of course.
interface CombinablePostSet<T extends Post>
    implements EntitySet<T>
{
    T combineAll();
}

これで、モジュールに必要な機能と、EntitySetのような汎用のSortableSetによって提供される汎用機能を組み合わせるクラスを定義できます。

class CombinableSortablePostSet<T extends Post>
    extends SortableSet<T>
    implements CombinablePostSet<T>
{
    T combineAll() { ... }
}

何らかの理由で、SortableSetを生成するクラスを作成する場合、基本的なエンティティのセットではなく、特定のエンティティタイプのクラスを作成する必要があります。つまり、セットに含めるエンティティのタイプをメソッドに通知する必要があります。これは一般的な方法で解決できます。 (またはジェネリッククラスを使用しますが、とりあえずメソッドを使用してみましょう)。

もちろん、メソッド本体では、要素を製品や投稿としてではなく、エンティティとしてのみ扱うことができます。このメソッドは、モジュール固有の機能について何も知らないためです。

// this might be a Factory?
class CommonSortableSetLogic
{
    <T extends Entity> SortableSet<T> Processor()
    {
        SortableSet<T> set = null;
        return set;
    }
}

代わりに、メソッドがSortableSet<Entity>またはEntitySet<Entity>を返した場合、それが必要なタイプであるという理由だけで、返された結果を単純にEntitySetの任意のサブクラスにキャストすることはできません。これがエラーが発生する理由です。CommonMethod1EntitySetLogicメソッドは、正しい種類のEntitySetを提供する必要があります。

ただし、工場などはさておき、SortableSet<T>の共通ロジックはSortableSet<T>クラス自体に実装する必要があります。そのため、インターフェースではなくクラス(派生元のクラス)を使用します。

いくつかのメモ

私があなたが何をしたいのかを理解したと仮定すると、あなたが実際にこのようにすべきかどうかという疑問がまだあります。実際のアプリケーションについてもっと知る必要があります(Entityクラスの目的は何か-通常のセットとは異なるエンティティセットの何が特別なのかなど)。

ジェネリックスを適切に使用すると、通常のセット(SetSorter<T>など)で機能する操作クラスは、適切に構成されていれば、結合が少なくなり、構造がより明確になる可能性があります。とにかく、これは別の日のトピックかもしれません。

デザインに固執する場合は、BAを拡張し、YXを拡張しても、A<X>B<X>またはA<Y>にキャストできないことに注意してください。この種のことは、ジェネリックスに注意しないとあなたに噛み付く傾向があります。

2
doubleYou