web-dev-qa-db-ja.com

適切な設計が必要:貧血モデル、継承、パターンマッチング

クエリを受け入れて結果を返すハンドラクラスがあります。ハンドラーは貧血です。入力データバッグを受け入れ、出力データバッグを返します。ハンドラーは多数になる可能性があるため、ハンドラーの共通汎用インターフェースと、クエリバッグと結果バッグの共通インターフェースを作成しました。さらに、Queryインスタンスを受け入れ、パターンマッチングを使用するHandlerManagerクラスを作成し、Handlerのどのインスタンスを使用する必要があるかを決定します。

sealed class Query {
    class QueryA(val data: Int) : Query()
    class QueryB(val data: Double) : Query()
    class QueryC(val data: String) : Query()
}


sealed class Result {
    class ResultA(val data: Int) : Result()
    class ResultB(val data: Double) : Result()
    class ResultC(val data: String) : Result()
}

interface Handler<Q : Query, R : Result> {
    fun handle(query: Q): R
}

class HandlerA : Handler<Query.QueryA, Result.ResultA> {
    override fun handle(query: Query.QueryA): Result.ResultA {
        return Result.ResultA(query.data)
    }
}

class HandlerB : Handler<Query.QueryB, Result.ResultB> {
    override fun handle(query: Query.QueryB): Result.ResultB {
        return Result.ResultB(query.data)
    }
}

class HandlerC : Handler<Query.QueryC, Result.ResultC> {
    override fun handle(query: Query.QueryC): Result.ResultC {
        return Result.ResultC(query.data)
    }
}

class HandlerManager() {
    private val handlerA = HandlerA()
    private val handlerB = HandlerB()
    private val handlerC = HandlerC()

    fun handle(query: Query): Result {
        return when (query) {
            is Query.QueryA -> handlerA.handle(query)
            is Query.QueryB -> handlerB.handle(query)
            is Query.QueryC -> handlerC.handle(query)
        }
    }
}

質問は簡単です:このモデルは適切で有効ですか?または、OOPスタイル/リッチドメインの代替はありますか?

1
Eldar Agalarov

私は「いいえ」で行きます、あなたの例にはあなたがコードのにおいをたくさん持っていると思います。

  1. HandlerManagerはクエリのタイプを検査します

    タイプをチェックする代わりに、継承を使用するのが理想的です

  2. ジェネリックの使用はうまくいきますか?つまり、すべてのハンドラーはHandlerを実装しますが、handle関数を呼び出す前に、ハンドラーのタイプを知っておく必要があります。

    マネージャーでクエリの種類を確認するので、単純にHandlerの非汎用インターフェイスを持つことができます。

あなたのコードの問題は、あなたが [〜#〜] oop [〜#〜][〜#〜] adm [〜#〜] 設計。完全なADMを実行するだけです。

public class Query
{
    string QueryString
    string Type
}

public interface IHandler
{
    Result RunQuery(Query q)
}

public class HandlerManager
{
    Dictionary<string, Handler> handlers

    public Result RunQuery(Query q)
    {
        return this.handers[q.Type].RunQuery(q)
    }
}

または完全なOOP

public class Query
{
    string QueryString
    Result RunQuery() //override in descendants
}
3
Ewan

リッチモデルと貧血モデル:モデルがリッチであるか貧弱であるかは、エンティティと集計でのメソッドの可用性によって決まります。それはハンドラーについてではありません。ハンドラーで処理する以外に多くのメソッドを用意しても意味がありません。

一緒に変更するものは一緒に残ります:すべてのクエリ(QueryA、QueryB、QueryC)をQueryクラスの内部クラスとして作成する理由。これらのクエリがすべての変更を同時に変更することは珍しいため、それらをグループ化することは最良のアイデアではありません。結果についても同様

HandlerManagerは開閉できません:クエリがシステムに追加されると、HandlerManagerを変更する必要があります。 C#で mediatR と呼ばれる Jimmy Bogard によるリフレクションを使用した美しい解決策があります。 MediatRはリフレクションを使用してハンドラーを自動的に呼び出します。 Java代替案があります。 ここ で確認できます。非常に頻繁に変更されるクエリを開閉することをお勧めします