web-dev-qa-db-ja.com

単一の責任の原則違反ですか?

私は最近、以下のクラスに関して別の開発者との議論に入りました:

public class GroupBillingPayment
{
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;
        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        UpdateGroupBilling([Parameters])
    }
}

UpdateGroupBillingは単一責任の原則に違反するため、saveメソッド内で呼び出すべきではないと私は信じています。しかし、支払いが行われるたびに、請求は更新されるべきであると彼は言います。したがって、これは正しいアプローチです。

私の質問SRPはここで違反されていますか?違反している場合、どのようにすれば、両方の基準を満たすようにリファクタリングできますか?

8
gvk

私はそれをこのように見ます:

メソッドは、他のメソッドを(同じオブジェクトまたは他のオブジェクト内で)呼び出して構成済みメソッドにするか、「プリミティブ計算」(同じレベルの抽象化)を実行する必要があります。

構成されたメソッドの責任は「他のメソッドを呼び出す」ことです。

したがって、Saveメソッド自体が「プリミティブ計算」自体を行う場合(たとえば、戻り値をチェックする場合)、SPRに違反する可能性があります。この「プリミティブ計算」がSaveメソッドによって呼び出された別のメソッドに存在する場合、SRPに違反していません。


Saveメソッドの更新バージョンはsingle abstraction layerの原則に従っていません。これはより重要な問題なので、それを新しいメソッドに抽出する必要があります。

これにより、Save構成されたメソッドに変換されます。書かれているように、構成されたメソッドの責任は「他のメソッドを呼び出す」ことです。したがって、ここでUpdateGroupBilling([Parameters])を呼び出すことは、SRPの違反ではなく、ビジネスケースの決定です。

6
Timothy Truckle

単一の責任は、関数/クラスが変更する理由は1つだけであると解釈できます。

したがって、現在のSaveメソッドはその原則に違反します。これは、複数の理由で変更する必要があるためです。
1。変更された保存ロジック
2。請求グループの更新に加えて、何か他のものを更新する場合

保存のみを担当するSaveModelメソッドを導入することで、リファクタリングできます。また、要件に基づいて必要なすべての操作を組み合わせる別の方法を紹介します。だからあなたは2つの方法で終わる

public void SaveModel(IGroupBillingPayment model)
{
    // only saves model
}

public void Save(IGroupBillingPayment model)
{
    SaveModel(model);
    UpdateGroupBilling([Parameters]);
}

SaveModelメソッドは、モデルをデータベースに保存する責任があり、変更する1つの理由-「ロジック」の保存が変更される場合。

Saveメソッドは、完全な「保存」プロセスに必要なすべてのメソッドを呼び出す責任があり、変更する理由が1つあります-必要なメソッドの量が変わる場合。

検証をSaveModelの外に移動することもできると思います。

2
Fabio

単一の責任は私見であり、概念を明確にすることは容易ではありません。

簡単な経験則は次のとおりです。

私が説明しなければならないとき、メソッド/クラスが何をするか、そしてWordを使用する必要があるとき"そして"、それは何か臭いが起こっているかもしれないことを示す指標です

一方ではインジケータにすぎず、他方では逆に機能します。2つのことが起こっている場合、Word"と"の使用を避けることはできません。また、2つのことを行っているため、[〜#〜] srp [〜#〜]

しかし、それは一方で、複数のことを行うと違反することになります[〜#〜] srp [〜#〜] ?いいえ。そうでなければ、解決するのは簡単なコードと簡単な問題に限定されていたからです。解釈が厳しすぎると、自分を傷つけるでしょう。

[〜#〜] srp [〜#〜]の別の見方は次のとおりです:1レベルの抽象化。 1レベルの抽象化を扱っている限り、ほとんど問題ありません。

あなたの質問にとってそれはすべて何を意味します:

UpdateGroupBillingは単一責任の原則に違反するため、saveメソッド内で呼び出すべきではないと私は思います。しかし、支払いが行われるたびに、請求は更新されるべきであると彼は言います。したがって、これは正しいアプローチです。

これが[〜#〜] srp [〜#〜]の違反かどうかを判断するには、何が起こっているのかを知る必要があります。 save()-メソッド。メソッドが-名前として-モデルをデータベースに永続化する責任があることを示唆している場合、UpdateGroupBillingへの呼び出しis IMHO違反[ 〜#〜] srp [〜#〜]、「支払いの保存」のコンテキストを拡張しているためです。 「私は支払いを保存し、グループ請求を更新します」と言い換えることができます。これは、前述のとおり、[〜#〜] srp [の違反の(可能性のある)インジケータです。 〜#〜]

一方、メソッドが「支払いのレシピ」を記述している場合-つまりプロセスでどのステップを実行して決定するか:支払いが完了するたびに保存する(別の場所で処理する)必要があり、次にグループの請求を更新する(別の場所で処理する)必要があります。 「レシピ」の抽象化を離れないので、SRPの違反はありません。何をすべきか(レシピ内)と、どこで行われるか(対応するクラス/メソッド/モジュール内)を区別します。しかし、それがsave()- methodが行うこと(どのステップを実行するかを説明する)の場合、名前を変更する必要があります。

これ以上のコンテキストがないと、具体的なことを言うのは困難です。

編集:最初の投稿を更新しました。このメソッドは[〜#〜] srp [〜#〜]に違反しているため、リファクタリングする必要があります。データのfetchingは除外する必要があります(このメソッドのパラメーターにする必要があります)。データの追加(updateBy/On)は他の場所で行う必要があります。 savingは他の場所で行う必要があります。次に、UpdateGroupBilling([Parameters])をそのままにしておいてもかまいません。

0
Thomas Junk

完全なコンテキストがないと確信が持てませんが、あなたの直感は正しいと思います。私の個人的なお気に入りのSRPインジケーターは、どこに行くべきかを正確に把握し、何か月後に機能を変更するために何かを変更するかどうかです。

支払いタイプを定義するクラスは、誰が支払いを行うか、どのように支払いが行われるかを再定義することを期待する場所ではありません。アプリの他の部分がトランザクションを開始、実行、またはロールバック/キャンセルするために使用する重要な詳細を提供し、統一されたインターフェースを介してそれらの詳細を収集する多くの支払いタイプの1つであると思います。

これは、より一般的なDRY原則違反のレシピのようにも見えます。複数のタイプがあり、それぞれが同じメソッドを多数使用して独自のトランザクションを分類している場合です。SOLIDは、主にJavaScript開発者に常に100%の関連があるとは限りません(ただし、説明の多くが不適切に説明されていると思います)DRYは、一般的なプログラミング方法IMOのゴールドスタンダードです。 DRYの延長のようなアイデアに出会ったときは、それを考慮します。SRPはその1つです。誰もがトピックに留まっていると、自分自身を繰り返したくなる可能性が低くなります。

0
Erik Reppen

概念的にはUpdateGroupBilling(...)への1つの方法しかなく、それがその場所でのみ発生する場合、それがどこにあるかはおそらく問題ありません。しかし、問題は時間的な状況ではなく、概念に関連しています。

どちらでもない場合、リファクタリングする1つの方法は、発行/購読を使用して、支払いが保存されるたびに通知し、そのイベントに請求を購読させ、関連するエントリを更新することです。これは、更新が必要な複数のタイプの請求が必要な場合に特に便利です。別の方法は、課金を戦略パターンとして考えることです。つまり、1つを選択してそれを使用するか、それをパラメーターと同様に受け入れます。または、課金が常に発生するとは限らない場合は、デコレータなどとして追加できます。ただし、これは概念モデルとそれに対する考え方に依存するため、把握する必要があります。 。

ただし、考慮すべき重要な点の1つはエラー処理です。課金が失敗した場合、以前の操作はどうなりますか?

0
Yam Marcovic

この設計はSRPに違反していると思いますが、を修正するのは本当に簡単で、他のすべての必要なことを実行できます。

メッセージについて、そしてこのメ​​ソッドで何が起こっているかについて考えてください。 GroupBillingPaymentを保存する必要がありますが、保存されたことをGroupBillingPaymentがクラスに通知しても問題はありません。特に、その動作を明示的に公開するパターンを実装している場合。

Observerパターンを使用できます。

これがあなたのケースでどのように機能するかの例です:

public interface Subject {

    public void register(Observer observer);
    public void unregister(Observer observer);

    public void notifyObservers();      
}

public class GroupBillingPayment implements Subject {

    private List<Observer> observers;
    public void Save(IGroupBillingPayment model)
    {
        if (model == null || UserInfo.UserID == 0)
        {
            throw new Exception("GroupBillingPayment object or Current User Id is NULL , Please Contact Administrator.");
        }

        Data.GroupBillingPayment groupBillingPayment = RepositoryManager.GroupBillingPaymentRepository.GetById(model.GroupBillingPaymentID);
        Mapper.Map(model, groupBillingPayment);
        ServiceManager.GroupBilling.IsBillAlreadyCancelled(groupBillingPayment.GroupBillingID, THROW_ERROR);
        groupBillingPayment.UpdatedBy = UserInfo.UserID;
        groupBillingPayment.UpdatedOn = DateTime.Now;

        RepositoryManager.GroupBillingPaymentRepository.Update(groupBillingPayment, false);
        //UpdateGroupBilling([Parameters]) The Observer will have this responsability instead now
        notifyObservers();
    }

    public void notifyObservers() {
        for (Observer obj : observers) {
            obj.update();
        }
    }
}

public interface Observer {     
    public void update();

    public void setSubject(Subject sub);
}

あとは、Observersにバインドされる1つ以上のGroupBillingPaymentを作成するだけで、保存されるたびに通知されます。独自の依存関係を保持し、通知内容がわからないため、これらにまったく依存しません。

0

Xを達成したいとします。Xが簡単なものでない限り、Xを達成するためにすべてを実行するメソッドを記述し、メソッド「achieveX」を呼び出し、そのメソッドを呼び出します。 「achieveX」の単一の責任は、Xを達成するためにすべてを行うことです。「achieveX」は他のことをしてはなりません。

Xが複雑な場合、Xを実現するために多くのアクションが必要になる可能性があります。必要なアクションのセットは時間の経過とともに変化する可能性があるため、その場合はメソッドserveXを変更します。

あなたの例では、メソッド「保存」の責任は請求書を保存してグループの請求書を更新することではなく(2つの責任)、その責任は請求書を永続的に記録するために必要なすべてのアクションを実行することです。一つの責任。多分あなたはそれをひどく名付けた。

0
gnasher729