web-dev-qa-db-ja.com

パラメータが多すぎるコンストラクタのリファクタリング

私はソフトウェアエンジニアとして最初の2か月間で、これを改善できるかどうかについてアドバイスを求めていました。

RFIDからのデータをメッセージの形式で表すクラスを作成しました。

 class RFIDMessage
 {

        string _a;
        string _b;
        string _c;
        string _d;
        string _e;
        string _f;
        string _g;
        string _h;

        public RFIDMessage(string a, string b, string c, string d, string e, string f, string g, string h)
        {
            _a = a;
            _b = b;
            _c = c;
            _d = d;
            _e = e;
            _f = f;
            _g = g;
            _h = h;
        }
}

私は単一責任の原則について読んでいますが、このコンストラクターに新しいデータ型を提供することで改善できるかどうかはわかりません。このメッセージはRFIDタグから読み取られたデータから解析され、8つのパラメーターを持つことは多すぎるようです。このインスタンスではパラメータは変更されませんが、他のメッセージタイプがあります-これは単なるデフォルトです。

私の質問は:

1)コードをより保守しやすくするパラメーターの数を減らす方法はありますか?

2)このクラスのデザインを変更または改善して、より多くのタイプのメッセージに対応するにはどうすればよいですか?

15
M161

そうです、8つのパラメーター(すべての文字列)を使用すると、コンストラクターがコードレビューの対象になります。

次の点のいくつかを検討してください。

最初に重要な属性

メッセージモデルを調べ、整合性のある状態でインスタンスを初期化するために必要な属性を見つけます。引数の数をエッセンシャルに減らします。残りのセッターまたは関数を追加します。

8つの属性すべてが必須で読み取り専用の場合、実行することはそれほど多くありません。

カプセル化

相関するパラメータのカプセル化を検討してください。たとえば、A、B、およびCを一緒に新しいクラスに配置できます。同じ理由により、同時に変化するパラメーターを確認します。

それは、クラスをもう1つ(複雑さ)犠牲にして引数の数を減らします。

作成パターン を使用します

コードソースの任意の場所から直接メッセージを初期化する代わりに、 factories または builders から実行します。

配列

上記のいずれも機能しない場合は、パラメーターの配列を試してください。意味のあるパラメータの名前がないことを考えると、これがおそらく最も簡単な解決策です。

配列に関して、私は question を投稿し、その適合性について質問しました。私はそのような解決策を信頼するのをためらっていました。しかし、答えは私が無口であるのを助けたので、この解決策も嫌いならそれをチェックしてください。これはあなたの考えを変えるかもしれません。

継承

最終的には、メッセージが継承の良い候補であることに気付くでしょう。メッセージを属性でセグメント化すると、コード全体でif(message.getType() == ...)のタイプを要求するようになるので、少しオーバーヘッドが発生します。

19
Laiv

ここに異端的な答えがあります:私は本質的に何もパラメータがたくさんあることに問題はありません

多くのパラメーターを取る関数を持つことは、多くの場合、リファクタリングのターゲットですが、何かをより涼しいコードのチャンクにリファクタリングしたからといって、すべきであるとは限りませんそうしました。

8つの個別のパラメーターの代わりに配列または配列引数を使用することは、渡されるパラメーターの適切な数があることを強制するための追加のロジックが必要になることを意味します。コードとあなたはすべての愚かな小さなデータ構造のためのビルダーパターンを持っているので、私はあなたの膨大なコード行で迷子になるでしょう。 8つの引数すべてが本当に必要な場合、必要なデータがすべて含まれていないクラスのインスタンスが存在する可能性があるため、一部をsetterプロップに移動することはできません。

私自身の経験から、コードベースを継承している場合、コードはいくつかの退屈で退屈で明白なスタイル(8つのコンストラクター引数など)ではなく、クール/これまで使用したことがない複雑な新しいDIフレームワーク/パターン。

上記のクラスが単純に(できれば不変)データ構造であり、本当に有効にするために正確に8つの文字列引数を必要とする場合、8つのパラメーターを持つことはそれを達成する完全に優れた方法であると言います。

32
Graham

このクラスを不変にしたいという前提で作業します。

「真の」不変性が必要な場合は、コンストラクターがreadonlyフィールドを設定できる唯一のコードであるため、コンストラクターの引数を使用する必要があります。

ただし、パブリックゲッターとプライベートセッターを実装することもできます。オブジェクトも不変にするすべての意図と目的のために。あなたは静的なfactory(ish)メソッドでプライベートフィールドを設定します。

この例では、RFIDデータがストリームでアプリケーションに到着することを想定していますが、これを簡単に変更して、バイト配列、一連の構造体、XMLなどを操作できます。

class RFIDMessage
{
    private string _a;
    private string _b;
    private string _c;
    //etc

    private RFIDMessage() 
    {
        //Declare default constructor private so nobody else can instantiate an RFIDMessage on their own
    }

    static public RFIDMessage FromStream(Stream stream)
    {
        var reader = new RFIDStreamReader(stream);
        var newObject = new RFIDMessage();
        newObject._a = reader.ReadA();
        newObject._b = reader.ReadB();
       //etc.

       return newObject;
    }
}

void SampleUsage()
{
    var rfidStream = RFIDAPI.GetStream(); //Or whatever
    RFIDMessage message = RFIDMessage.FromStream(rfidStream);
}

このパターンの名前はわかりませんが、.NET CLRで一般的です。 FromBase64String

3
John Wu

これらのデータのすべてのビットが1つのインスタンスで必要であると仮定し、クラスの設計に疑問を投げかけないようにします。これらのメンバーのサブセットを収集する別の下位層クラスを作成した場合、クラスのメンバーが少なくなり、コード全体の量が少なくなる可能性があります。それは問題ではありません。

.netフレームワーク コンストラクターに関する設計ガイドライン を読むと、パラメーターの数が少ないほうが好ましいことがわかります。これは、デフォルトのメンバー値がコードを削減する可能性があることと、混合せずに多くのパラメーターを渡すことが難しいためです。オーダーアップ。代わりに、使用する前にプロパティを使用してインスタンスを設定します。デフォルト以外の値を必要とするプロパティを設定し、その他はそのままにします。これにより、使いやすくなります。

読み取り専用フィールドは可変フィールドよりも優先される必要があるため、可変フィールドが不要な場合はトレードオフがあります。コンストラクターを介してのみ読み取り専用フィールドを初期化できます。読み取り専用フィールドは使用していませんが、その決定を再検討したい場合があります。あなたが決断を迫られるのは環境の弱さです。

1
Frank Hileman

あなたのコードは実際の状況によっては大丈夫だと思いますが、「流暢な構文」という別の可能な解決策を示したいと思います。流暢な構文の背後にある考え方は、変更されたオブジェクトを返すオブジェクトの状態を変更するメソッドを提供することです。例えば。

public RFIDMessage SetA(string a)
{
    this._a = a;
    return this;
}

それはのようなコードを可能にします

RFIDMessage message = new RFIDMessage().SetA("Some A").SetC("Some C").SetH("Some H");

これで、インスタンスは作成後に変更できることに注意してください。これは、要件によっては望ましい場合や問題になる場合があります。それが問題である場合は、クラスが実装する「読み取り専用インターフェース」を作成して(上記のSetAメソッドはインターフェースの一部ではない)、読み取り専用インターフェースのみが知っているインスタンスのコンシューマーがそれを変更。

1
Bernhard Hiller