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


8

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

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それが単一責任の原則に違反するとして保存する方法の内部で呼ばれるべきではありません。しかし、支払いが行われるたびに、請求は更新されるべきであると彼は言います。したがって、これは正しいアプローチです。

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


6
これは、問題のドメインとアーキテクチャによって異なります。データとイベントは、アプリケーションをどのように流れるはずですか?関連するクラスの責任は何ですか?これを客観的に回答するには、質問に十分なコンテキストがありません。
amon、

7
SRPはコンテキストフリーのガイドラインではありません。
whatsisname

1
更新されたバージョンには、ここではトピック外のいくつかの問題があります。あなたはそれをcodereview.stackexchange.comに投稿して改善のためのいくつかの提案を得ることを検討するかもしれません...
ティモシー・

1
POAPを適用します!原則が存在する理由と、それが状況にどのように適用されるのかを理解していない場合は、まだ議論する価値はありません。あなたがいつ、やる原則の目的を理解し、答えははるかに簡単になります....あなたに。私たちには。私たちはあなたのコードベース、あなたの組織、またはあなたが仕事で得る特定の種類のナッツを知りません。...私はこれをVTCにしています。私たちはあなたに「正しい」答えを与えることはできないと思います。答えがそうでない限り:議論する前に原理を理解する必要があります。
svidgen

2
あなたはほぼ間違いなくこれを考えすぎています。ビジネス要件により、プロセスの一部として何らかのクリーンアップまたは更新を実行する必要がある場合、そのクリーンアップまたは更新は責任の一部であるため、責任は依然として単一です。
ロバートハーベイ

回答:


6

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

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

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

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


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

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


5
+1、正確に。シングル責任原則は、あなただけの今までにいずれかの操作を実行できることを意味するものではありません事を -それは不可能一緒にカップルの事になるだろうそのすべてで現在の抽象化レベルで行うべきことは1つだけです。
Kilian Foth

Saveメソッドが更新されました。これが役立つかどうか確認してください
gvk

2

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

したがって、現在のSave方法はその原則に違反します。それは、1つ以上の理由で変更する必要があるためです。
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ます。


0

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

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

説明しなければならないとき、メソッド/クラスが何をするか、そして「そして」という言葉を使わなければならないとき、それは何か臭いが起こっているかもしれないという指標です。

一方ではインジケータにすぎず、他方では逆にうまく機能します。2つのことが起こっている場合、「および」という単語の使用を避けられません。また、2つのことをしているため、SRPに違反しています。

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

SRPの別の視点は、次のとおりです。1レベルの抽象化。1レベルの抽象化を扱っている限り、ほとんど問題ありません。

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

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

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

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

これ以上の文脈がなければ、具体的なことを言うのは難しい。

編集:最初の投稿を更新しました。この方法はSRPに違反しているため、リファクタリングする必要があります。データのフェッチは除外する必要があります(このメソッドのパラメーターにする必要があります)。データ追加(updateBy / On)は別の場所で行う必要があります。省は、他の場所で行われるべきです。その後、そのままにUpdateGroupBilling([Parameters])しておいてもかまいません。


0

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

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

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


0

概念的にはへの1つの方法UpdateGroupBilling(...)しかなく、それがその場所でのみ発生する場合、それがどこにあっても問題ないでしょう。しかし、問題は時間的な状況ではなく、概念に関連しています。つまり、「当分の間、ここではそれを行うだけです」。

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

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


0

私は、このデザインは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を変更しますが、すべてがうまくいけば、そのまま呼び出すことができます。

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


-2

ポイントを理解できれば、「支払い済み」などのイベントを発生させるための支払いと、それをサブスクライブする請求を処理するクラスがあります。このようにして、支払いハンドラは請求について何も知りません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.