web-dev-qa-db-ja.com

サービス層のオブジェクトを更新するために使用するビルダーパターンは適切ですか?

現在、サービスレイヤーでは、idと新しい更新された値を渡しています。

updatePersonName(Person person, Name name)

次に、対応するリポジトリ関数を呼び出します。これで、単一の値のみを更新する場合に問題なく機能します。しかし、一度に複数の値を更新したい場合は、続けてさまざまなサービスメソッドを呼び出すのをやめるか、更新するために複数の引数を取るサービスメソッドを定義します。我慢できるが、一緒に更新されるように強制される複数の値を更新する必要がある場合はさらに悪化します。つまり、組み合わせが確実に満たされるようにするサービスで(おそらく新しい)メソッドを定義する必要があります。また、いくつかの制限(おそらく、さまざまな理由により更新不可としてマークされている人物)が発生し始めた場合は、複雑さがさらに増すだけです。

最近、ビルダーパターンを使用することを考えていますが、オブジェクトの作成ではなく、更新しています。このようなもの(すべての方法は完全に任意ですが、要点はわかります):

PersonService.updater(person)
    .setName("newName")
    .addRole(newRoleObject)
    .setFlag(PersonFlag.NOT_UNDERAGE)
    .overrideBlock()
    .withEventDescription("Person changed her name on birthday!")
    .update();

ビルダーは、外部にあまり複雑さを公開することなく、内部的にロジックを解決できます。 Fluent APIは、アクセスが必要な他のすべてのサービス/コンポーネントで簡単に使用できます。どんな要件が出てきてもカバーするために大量のメソッドを作成する必要はありません。複数の更新は簡単にまとめることができます。現在許可されているものを更新したい場合は、ブロックをオーバーライドしない限り、内部でブロックする可能性があります。そして、型の安全性のために、特定のフィールド、たとえば、EventObjectを強制することができます。

さらに重要なことに、これにより、複数回ではなく、1回だけリポジトリにアクセスできるようになります。特に、そうでなければデータベースへの多くのパスを必要とする重要なアルゴリズムで、ランタイムを改善します。

このアプローチにもいくつか問題があることがわかります。かさばり、経験のない人にとっては型破りなAPIであり、誤用につながる可能性があります。これを実装するのは簡単ではありませんが、内部ロジックが一緒になっていることを確認します。しかし、私はポジティブが私の状況のネガティブを上回っていると思います。

何か不足していますか?

編集:通常、DTOの内部には保存を処理するsave()関数がありますが、既存のインフラストラクチャのため、現時点ではこれはオプションではありません。

6
Joe

まず、これは GoF Builderパターン ではありません。これは Joshua Bloch Builder Pattern の可能性があります。 名前付き引数 のない言語(Javaなど)のシミュレーションに使用されます。

名前付き引数は、引数に明確なラベルを付けることにより、長い引数リストを許容できるようにします。また、オプションの引数を有効にするため、位置引数のようにすべての可能な引数をリストに含める必要はありません。

Joshua Bloch Builderが提供するものであり、これらすべてのフィールドが設定された不変オブジェクトであるので、それがあなたがしているすべてのことなら、あなたは本当にオブジェクト構築を行っています。

それがあなたのやっていることではない場合、これは 内部または埋め込みドメイン固有言語 (DSL)です。これらには、次に何をするか、何を行わないかを制御する力があります。メソッドは、新しいメソッドを有効にするさまざまなタイプを返すため、これらはそうなります。

これはあなたにたくさんの力とあなた自身を掛けるのに十分なロープを提供します。また、舞台裏での設定も大変です。しかし、それは良い用途を持っています。たとえば、 [〜#〜] jooq [〜#〜] および Java 8ストリーム です。 私は正常に使用しました データベースの更新にも使用され、定着するには余りにも定着していた悪夢のような神のオブジェクトの構築を形式化しました。

これの重要な点は、ビジネスルールが、作成しているミニ言語によって適用されることです。それは素晴らしいですが、それはそれらを石に設定します。 DSLの記述は簡単ではなく、変更も簡単ではありません。 LOTとして使用され、ほとんど変更されない場合に使用します。

これで、少しの依存性注入で実装をハードコーディングすることを回避できます。

happyNewName(PersionService personService, Person person, Role newRoleObject) {
    personService
        .updater(person)
        .setName("newName")
        .addRole(newRoleObject)
        .setFlag(PersonFlag.NOT_UNDERAGE)
        .overrideBlock()
        .withEventDescription("Person changed her name on birthday!")
        .update()
    ;
}

このようにして、DSLの「ソース」とその実装を個別に変更できます。

これにより、コーダーが必要となる可能性のある複雑な儀式に従うように案内するファサードを作成できます。ただし、システムを単純化するための作業を行うだけの場合は、必ずしも必要ではありません。ここでの危険は、代わりにDSLを使用する場合、これを掘り下げることになります 技術的負債 穴をさらに深くします。これが核となるオプションなので、本当により良い解決策がないことを確認してください。

この流暢なスタイルは現在トレンドですが、DSLをエンドツーエンドで設計する必要があることに注意してください。この途中でランダムなクラスを取得する卑劣なことはありません。これは巨大な デメテルの法則 違反です。このように連携して動作するように設計されたクラスのみをドットスルーします。場所についてうそをついているランダムなものではありません。

6
candied_orange

結局のところ、個人を更新するためのいくつかのユニークなユースケースがあるように思えます。このため、あなたは、ロジックをカプセル化する「ビルダー」クラスを作成しました。これらの個々の更新。個々の更新を流暢なAPIと一緒にチェーンすることにより、ユースケースを複製します。ここでの問題は、メソッドの正しいシーケンスを呼び出すために、実行しているユースケースを知る必要があることです。

これは斬新なアプローチですが、オブジェクト作成の抽象化レイヤーを提供するビルダーパターンの誤用のように感じられます。これは、「ビルダーオブジェクト」が何をするのかという人々の期待に反します。このため、これは良いパターンだとは思いません。

クリーンアーキテクチャ では、ユースケースの動作は、文字通りユースケースと呼ばれます。これは、アプリケーションの特定のユースケースのすべての操作を調整することに特化したクラスです。他のアーキテクチャで使用されている用語に似た名前を付けることの利点が得られ、単純に古い名前の方が優れています。

「ビルダー」クラスで行うチェーンメソッド呼び出しのすべての組み合わせを検討し、メソッド呼び出しのシーケンスごとに「ユースケース」クラスを作成します。ここでは新しいオブジェクトを作成していないため、不要なビルダーパターンを削除し、ユースケースクラスにリポジトリから必要なメソッドとオブジェクトの微妙な違い、およびそれらのオブジェクトの相互作用を処理させます。

var useCase = new NameChangeUseCase(repository);

useCase.Execute(person, dateOfChange, "newName");

NameChangeUseCaseのexecuteメソッドは、イベントの説明を設定し、NOT_UNDERAGEフラグはdateOfChangeに基づいており、これらの条件にも基づいて役割を割り当てます。

public class NameChangeUseCase
{
    private IPersonRepository repository;

    public NameChangeUseCase(IPersonRepository repository)
    {
        this.repository = repository;
    }

    public void Execute(Person person, DateTime dateOfChange, string newName)
    {
        if (person.BirthDate == dateOfChange)
        {
            // Add event with description "Person changed her name on birthday!"
        }

        if (person.CalculateAgeInYears(dateOfChange) >= 18)
        {
            // set NOT_UNDERAGE flag
        }

        // get new role and add to person
        person.ChangeName(newName);
        repository.Save(person);
    }
}

これは、開発者として従うのがはるかに簡単で、失敗するのがはるかに困難です。

1
Greg Burghardt