web-dev-qa-db-ja.com

メソッドチェーンの長所と短所、およびすべてのvoid戻りパラメーターをオブジェクト自体で置き換える可能性

私は主にJavaに興味がありますが、それは一般的な質問だと思います。最近、私は多くのメソッドチェーンを使用するArquillianフレームワーク(ShrinkWrap)を使用しています。メソッドチェーンの他の例は、StringBuilderStringBufferのメソッドです。このアプローチを使用することには明らかな利点があります。冗長性の低下はその1つです。

void returnパラメーターを持つすべてのメソッドがチェーン可能として実装されていないのはなぜですか?連鎖には明らかで客観的な欠点があるはずです。すべてのメソッドが連鎖可能である場合でも、使用しないことを選択できるためです。

私はJavaの既存のコードを変更するように求めていません。これはどこかで何かを壊す可能性がありますが、なぜそれが使用されなかったのかについての説明もいいでしょう。私は将来のフレームワーク(Javaで書かれた)の設計の観点からもっと質問しています。


私は同様の質問を見つけましたが、元の質問者は実際になぜそれがIS良い習慣と見なされたのか疑問に思っています: メソッドチェーン-なぜそれが良い習慣かどうか? ==


いくつかの答えがありますが、チェーンのすべての利点と欠点、およびすべてのvoidメソッドをチェーン可能にすることが有用であると見なされるかどうかはまだわかりません。

22
MartinTeeVarga

欠点

  • 主に署名を混乱させます。何かが新しいインスタンスを返す場合、それがミューテーターメソッドでもあるとは思いません。たとえば、ベクトルにscaleメソッドがある場合、戻り値がある場合は、入力によってスケーリングされた新しいベクトルが返されると想定します。そうでない場合は、内部でスケーリングされると予想します。
  • さらに、もちろん、クラスが拡張されると問題が発生します。クラスが拡張されると、チェーンの途中でオブジェクトがスーパータイプにキャストされます。これは、チェーンメソッドが親クラスで宣言されているが、子クラスのインスタンスで使用されている場合に発生します。

メリット

  • これにより、数学方程式スタイルのコードを、複数の中間オブジェクトを必要とせずに(不要なオーバーヘッドにつながる)、たとえばメソッドチェーンを使用せずに完全な方程式として記述できます。ベクトル外積(ランダムな例として)は、次のように記述する必要があります。

    _MyVector3d tripleCrossProduct=(vector1.multiply(vector2)).multiply(vector3);
    _

    これには、作成してガベージコレクションする必要がある中間オブジェクトを作成するという欠点があります。

    _MyVector3d tripleCrossProduct=vector1;
    tripleCrossProduct.multiplyLocal(vec2);
    tripleCrossProduct.multiplyLocal(vec3);
    _

    これにより、中間オブジェクトの作成が回避されますbutは非常に不明確ですが、変数名tripleCrossProductは実際には3行目まで嘘です。ただし、メソッドチェーンがある場合は、これを簡潔に記述できます。不要な中間オブジェクトを作成しない通常の数学的方法。

    _MyVector3d tripleCrossProduct=vector1.multiplyLocal(vector2).multiplyLocal(vector3);
    _

    これはすべて、vector1が犠牲であり、二度と使用する必要がないことを前提としています。

  • そしてもちろん、明らかなメリット。簡潔。上記の例のマナーで操作がリンクされていない場合でも、オブジェクトへの不要な参照を回避できます

    _SomeObject someObject=new SomeObject();
    someObject
      .someOperation()
      .someOtherOperation();
    _

注意:_MyVector3d_はJavaの実際のクラスとして使用されていませんが、.multiply()メソッドが呼び出されたときに外積を実行すると想定されています。 .cross()は使用されないため、ベクトル計算に精通していない人にとっては「意図」が明確になります。
NB Amitのソリューションは、マルチラインメソッドチェーンを使用する最初の答えでした。完全を期すために、4番目の箇条書きの一部として含めています。

24
Richard Tingle

メソッドチェーンは、プログラミング言語に関係なく、流暢なインターフェイスを実装する方法です。それの主な利点(読み取り可能なコード)は、それをいつ使用するかを正確に教えてくれます。読み取り可能なコードが特に必要ない場合は、メソッド呼び出しの結果としてコンテキスト/オブジェクトを返すようにAPIが自然に設計されていない限り、コードの使用を避けてください。

ステップ1:流暢なインターフェースとコマンドクエリAPI

流暢なインターフェースは、コマンドクエリAPIに対して考慮する必要があります。それをよりよく理解するために、以下にコマンドクエリAPIの箇条書きの定義を書いてみましょう。簡単に言えば、これは単なる標準的なオブジェクト指向コーディングアプローチです。

  • データを変更するメソッドはCommandと呼ばれます。コマンドは値を返しません。
  • 値を返すメソッドはQueryと呼ばれます。クエリはデータを変更しません。

コマンドクエリAPIに従うと、次のような利点があります。

  • オブジェクト指向のコードを見ると、何が起こっているのかがわかります。
  • 各呼び出しは別々に行われるため、コードのデバッグは簡単です。

ステップ2:コマンドクエリAPIに基づく流暢なインターフェース

しかし、コマンドクエリAPIは何らかの理由で存在し、実際、読みやすくなっています。では、流暢なインターフェイスとコマンドクエリAPIの両方のメリットをどのように得るのでしょうか。

回答:流暢なインターフェイスは、コマンドクエリAPIの上に実装する必要があります(コマンドクエリAPIを置き換えるのではなくby流暢なインターフェイス)。流暢なインターフェースは、コマンドクエリAPIのファサードと考えてください。そして結局のところ、それは標準の(コマンドクエリ)APIよりも流暢な "interface" --a 読み取り可能またはコンビニエンスインターフェイスと呼ばれます。

通常、コマンドクエリAPIの準備ができたら(作成、おそらく単体テスト、簡単にデバッグできるように洗練された)、その上に流暢なインターフェイスソフトウェアレイヤーを作成できます。言い換えると、流暢なインターフェイスは、コマンドクエリAPIを利用してその機能を実行します。次に、利便性と読みやすさを必要とする場所で、流暢なインターフェイス(メソッドチェーンを使用)を使用します。ただし、実際に何が起こっているのかを理解したい場合(たとえば、例外をデバッグする場合)、いつでもコマンドクエリAPI(古き良きオブジェクト指向コード)を掘り下げることができます。

20
Tengiz

メソッドチェーンを使用することの欠点は、NullPointerExceptionまたはその他のExceptionが発生したときにコードをデバッグすることです。次のコードがあるとします。

String test = "TestMethodChain"; test.substring(0,10).charAt(11); //This is just an example

次に、文字列インデックスが範囲外になります:上記のコードを実行すると例外が発生します。リアルタイムの状況に陥り、そのようなことが起こった場合、どの回線エラーが発生したかはわかりますが、連鎖メソッドのどの部分が原因であるかはわかりません。したがって、データが常に来ることがわかっている場合、またはエラーが適切に処理される場合は、慎重に使用する必要があります。

複数行のコードを記述したり、複数の変数を使用したりする必要がないなどの利点もあります。

多くのフレームワーク/ツールはこれをDozerのように使用しており、コードのデバッグに使用したときは、チェーンの各部分を調べてエラーの原因を見つける必要がありました。

お役に立てれば。

7
Harish Kumar

Martin Fowlerによる Fluent Interface についてお読みください。

要約すると

  • 主にコマンドクエリ責任分離(CQRS)の設計原則に違反するため、メソッドをチェーンしないでください。
  • ビジネスがこれらの操作について話す方法に近づけることでAPI設計を改善し、それらを内部DSLと見なします
  • aPIを汚染し、コードのクライアント/メンテナに意図を明らかにできない可能性があるため、独立したメソッドを連鎖させないようにしてください
5
Nitin Tripathi

このパターンは、同じオブジェクトに対して一連の更新を実行する必要があり、更新操作で更新のステータスを返す必要がない場合に役立ちます。たとえば、データベースレイヤー用に作成したAPIでこのパターンを使用しました。多くの基準に基づいていくつかの行をフェッチするには、where句に多くの条件を追加する必要があります。このパターンを使用して、基準を次のように追加できます。

CriteriaCollection().instance()
    .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_1, value1))
    .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_2, value2))
    .addSelect(Criteria.isIn(XyzCriteria.COLUMN_3, values3))
    .addOrder(OrderCriteria.desc(XyzCriteria.Order.COLUMN_1));

最終的には、コードの可読性が向上します。

4
Amit

不変性および関数型プログラミングを好む場合、voidを返すことはありません。

戻り値のない関数は、その副作用に対してのみ呼び出されます。

もちろん、これが当てはまらない状況もありますが、voidを返す関数は、別の方法で試すためのヒントと見なすことができます。

2
Beryllium

それはかなりの量の仕事です。特に継承が関係している場合。見てください ビルダーパターンに関するこの素晴らしい記事

クライアントが同じインスタンスで複数のメソッドを順番に呼び出すことが予想される場合は、これを実装することは非常に理にかなっています。ビルダーパターン、不変オブジェクトなど。しかし、私の意見では、ほとんどの場合、余分な努力をする価値はありません。

1
buritos

個人的にはとても便利なパターンだと思いますが、どこでも使うべきではありません。 copyTo(T other)メソッドがある状況を考えてみましょう。通常は何も返さないことを期待しますが、同じタイプのオブジェクトを返す場合、どのオブジェクトを期待しますか?この種の問題はドキュメントで解決できますが、メソッドの重要性についてはまだあいまいです。

public class MyObject {

    // ... my data

    public MyObject copyTo(MyObject other) {

        //... copy data


        // what should I return?
        return this;
    }
}
1
Craig

オブジェクトには属性とメソッドがあります。各メソッドは、オブジェクトの全体的な目的の一部を果たします。コンストラクター、ゲッター、セッターなどの一部のタイプのメソッドは、属性とオブジェクト自体のライフサイクル管理を実行しています。他のメソッドは、オブジェクトとその属性の状態を返します。これらのメソッドは通常、無効ではありません。

ボイドメソッドには2つの種類があります。1。オブジェクトまたは属性のライフサイクル管理に関するもの。 2.メソッド内で完全に処理され、他の場所で変更の状態を引き起こしてはならない出力を持つ。

ad1。それらはオブジェクトの内部動作に属します。 ad 2.パラメータの情報は、メソッド内でいくつかの作業を実行するために使用されます。メソッドが完了した後、オブジェクト自体にも、いずれかのパラメーターの内部ステータスにも変更はありません。

しかし、なぜvoidを返すメソッドを作成する必要があり、たとえばブール値(成功かどうか)をしないのはなぜですか?メソッドが(セッターのように)voidを返す動機は、メソッドが副作用を持たないように意図されていることです。 trueまたはfalseを返すと、副作用が発生する可能性があります。 Voidは、メソッドが設計どおりに実行されたときにメソッドに副作用がないことを意味します。例外はメソッドの通常の処理の副作用ではないため、例外を返すvoidメソッドは適切です。 。

Voidを返すメソッドは、定義上、分離された作業メソッドであることを意味します。 voidメソッドのチェーンは、疎結合メソッドのチェーンです。これは、他のメソッドが前のメソッドの結果に依存できないためです。どのメソッドにも結果がないためです。これには、Chain ofResponsibilityデザインパターンというデザインパターンがあります。異なるロガーをチェーンすることは、従来の例であり、サーブレットAPI内の後続のフィルターへの呼び出しです。

Voidメソッドを意味のある方法でチェーンするということは、それらのメソッドが機能する共有オブジェクトが、オブジェクトを操作するvoidメソッドが理解できる状態の各ステップの後にあることを意味します。後続のすべての呼び出しは、前の呼び出しの結果に依存したり、自身の呼び出し後の呼び出しの動作に影響を与えたりすることはできません。これを確実にする最も簡単な方法は、オブジェクトの内部状態を変更させないこと(ロガーの例のように)、または各メソッドにオブジェクトの内部状態の別の部分を変更させないことです。

連鎖するボイドメソッドは、私の意見では、フレーバー2を持ち、ある種の処理を共有するメソッドのみです。各メソッドはオブジェクトの状態全体の異なる部分の変更であるため、クラスのライフサイクルに関するvoidメソッドを連鎖させません。これらのメソッドの存在は、クラスの設計が変更されると時間の経過とともに変化する可能性があるため、これらのメソッドをチェーンすることはお勧めしません。さらに、最初のタイプのvoidメソッドによってスローされるすべての例外は互いに独立していますが、あるタイプの処理を共有するフレーバー2の連鎖voidメソッドによってスローされる例外には共通点があると予想される場合があります。

0
Loek Bergman