メソッドチェーン は、別のメソッドで結果を呼び出すためにオブジェクト自体を返すオブジェクトメソッドの実践です。このような:
participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()
これは読みやすいコードまたは「流fluentなインターフェース」を生成するため、良い習慣と考えられます。しかし、私にとっては、代わりにオブジェクトの向き自体によって暗示されるオブジェクト呼び出し表記を壊しているようです-結果のコードは、前のメソッドのresultに対するアクションの実行を表していません。一般にオブジェクト指向コードがどのように機能すると予想されるか:
participant.getSchedule('monday').saveTo('monnday.file')
この違いは、「結果のオブジェクトを呼び出す」というドット表記に対して2つの異なる意味を作成することに成功します。チェーンのコンテキストでは、上記の例はparticipantオブジェクトを保存すると読みます。この例は、実際にはgetScheduleが受け取ったスケジュールオブジェクトを保存することを目的としていますが。
ここでの違いは、呼び出されたメソッドが何かを返すことを期待されるかどうか(その場合、呼び出されたオブジェクト自体をチェーンのために返すかどうか)であることを理解しています。ただし、これらの2つのケースは表記法自体と区別することはできず、呼び出されるメソッドのセマンティクスからのみ区別できます。メソッドチェーンが使用されていない場合、メソッド呼び出しは、以前の呼び出しのresultに関連する何かで動作することを常に知ることができます-チェーンを使用すると、この仮定が破れ、チェーン全体を意味的に処理して、呼び出されている実際のオブジェクトが実際に何であるかを理解します。例えば:
participant.attend(event).setNotifications('silent').getSocialStream('Twitter').postStatus('Joining '+event.name).follow(event.getSocialId('Twitter'))
そこで最後の2つのメソッド呼び出しはgetSocialStreamの結果を参照しますが、前のメソッド呼び出しは参加者を参照します。たぶん、コンテキストが変化するチェーンを実際に書くのは悪い習慣ですが(そうですか?)、その場合でも、同じように見えるドットチェーンが実際に同じコンテキスト内に保持されているか、結果に対してのみ機能するかどうかを常に確認する必要があります。
私にとっては、メソッドチェーンは表面的には読み取り可能なコードを生成しますが、ドット表記の意味をオーバーロードすると混乱が増えるだけです。私は自分自身をプログラミングの第一人者とは考えていないので、私は自分のせいだと思っています。 そう:私は何が欠けていますか?メソッドチェーンを何らかの形で間違って理解していますか?メソッドの連鎖が特に良い場合と、特に悪い場合がありますか?
補足:この質問は、質問として隠された意見書として読むことができると理解しています。しかし、そうではありません-チェーンがグッドプラクティスと見なされる理由を本当に理解したいと思います。そして、それが本来のオブジェクト指向の表記法を壊すと思うのはどこが間違っていますか。
これは主観的であることに同意します。ほとんどの場合、メソッドチェーンを回避しますが、最近、それがちょうど正しいことであるケースも発見しました。少ない。オーバーライドにより、これは非常に面倒で非常に高速になりました。代わりに、チェーンアプローチを選択しました。
MyObject.Start()
.SpecifySomeParameter(asdasd)
.SpecifySomeOtherParameter(asdasd)
.Execute();
これは工場パターンのようなものです。メソッドチェーンアプローチはオプションでしたが、コードの記述を簡単にしました(特にIntelliSenseを使用)。ただし、これは1つの孤立したケースであり、私のコードでは一般的な慣行ではないことに注意してください。
重要なのは、99%の場合、おそらくメソッドチェーンなしでも同様に、またはそれ以上にできることです。しかし、これが最善のアプローチである1%があります。
ちょうど私の2セント。
メソッドチェーンにより、デバッグが難しくなります。-ブレークポイントを簡潔なポイントに置くことができないため、プログラムを目的の場所に正確に一時停止できます-これらのメソッドのいずれかが例外をスローし、行番号を取得した場合「チェーン」のどのメソッドが問題を引き起こしました。
常に非常に短く簡潔な行を書くことは一般的に良い習慣だと思います。すべての行で1つのメソッド呼び出しを行う必要があります。長い行よりも多くの行を優先します。
編集:コメントは、メソッドの連鎖と改行が別々であることを述べています。それは本当です。ただし、デバッガーによっては、ステートメントの途中にブレークポイントを配置できる場合とできない場合があります。可能であっても、中間変数を含む個別の行を使用すると、はるかに柔軟性が高まり、デバッグプロセスに役立つ[ウォッチ]ウィンドウで調べることができる値の束全体が得られます。
個人的には、元のオブジェクトにのみ作用するメソッドを連鎖することを好みます。複数のプロパティを設定するか、ユーティリティタイプのメソッドを呼び出します。
_foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();
_
私の例では、1つ以上のチェーンメソッドがfoo以外のオブジェクトを返す場合は使用しません。チェーン内のオブジェクトに適切なAPIを使用している限り、構文的には何でもチェーンできますが、オブジェクトを変更すると、IMHOが読みにくくなり、異なるオブジェクトのAPIに類似性がある場合、本当に混乱する可能性があります。最後に本当に一般的なメソッド呼び出し(.toString()
、.print()
、何でも)を行うと、最終的にどのオブジェクトに作用しますか?偶然にコードを読んでいる人は、元の参照ではなく、チェーン内の暗黙的に返されるオブジェクトであることに気付かないかもしれません。
異なるオブジェクトをチェーンすると、予期しないnullエラーが発生する可能性もあります。私の例では、fooが有効であると仮定すると、すべてのメソッド呼び出しは「安全」です(たとえば、fooに対して有効)。 OPの例:
_participant.getSchedule('monday').saveTo('monnday.file')
_
...(コードを見る外部開発者として)getScheduleが実際に有効な非nullスケジュールオブジェクトを返すという保証はありません。また、このスタイルのコードのデバッグは、多くのIDEがデバッグ時にメソッド呼び出しを検査可能なオブジェクトとして評価しないため、はるかに困難になることがよくあります。 IMO、デバッグの目的で検査するオブジェクトが必要になる場合はいつでも、明示的な変数に含めることを好みます。
Martin Fowlerはここで良い議論をしています:
いつ使うか
メソッドチェーンは、内部DSLの可読性を大幅に向上させることができ、その結果、一部の心では内部DSLのほぼ同義語になりました。ただし、メソッドチェーンは、他の関数の組み合わせと組み合わせて使用する場合に最適です。
メソッドチェーンは、parent :: =(this | that)*のような文法で特に効果的です。さまざまなメソッドを使用することで、次にどの引数が来るかを判読できる方法で確認できます。同様に、オプションの引数はメソッドチェーンで簡単にスキップできます。 parent :: = first secondなどの必須句のリストは、基本的なフォームではあまり機能しませんが、プログレッシブインターフェイスを使用することで適切にサポートできます。ほとんどの場合、その場合はネストされた関数を使用します。
メソッドチェーンの最大の問題は、仕上げの問題です。回避策はありますが、通常これに遭遇した場合は、ネストされた関数を使用する方が良いでしょう。ネストされた関数は、コンテキスト変数で混乱する場合にも適しています。
私の意見では、メソッドの連鎖はちょっとした目新しいものです。確かに、見た目はかっこいいですが、実際の利点は見当たりません。
方法は:
someList.addObject("str1").addObject("str2").addObject("str3")
より良い:
someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")
例外は、addObject()が新しいオブジェクトを返す場合です。この場合、チェーン解除されたコードは次のように少し面倒になる場合があります。
someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")
呼び出しが別のクラスのインスタンスを返すように、予想よりも多くのオブジェクトに依存している可能性があるため、危険です。
例を挙げます。
foodStoreは、所有する多くの食料品店で構成されるオブジェクトです。 foodstore.getLocalStore()は、パラメーターに最も近いストアに関する情報を保持するオブジェクトを返します。 getPriceforProduct(anything)は、そのオブジェクトのメソッドです。
したがって、foodStore.getLocalStore(parameters).getPriceforProduct(anything)を呼び出すとき
ただし、FoodStoreだけでなくLocalStoreにも依存しています。
GetPriceforProduct(anything)が変更された場合、FoodStoreだけでなく、連鎖メソッドを呼び出したクラスも変更する必要があります。
クラス間の疎結合を常に目指してください。
そうは言っても、個人的にはRubyをプログラミングするときにそれらを連鎖させたいと思っています。
メソッドチェーンにより、高度な DSL in Javaを直接設計できます。本質的に、少なくともこれらのタイプのDSLルールをモデル化できます。
1. SINGLE-Word
2. PARAMETERISED-Word parameter
3. Word1 [ OPTIONAL-Word]
4. Word2 { Word-CHOICE-A | Word-CHOICE-B }
5. Word3 [ , Word3 ... ]
これらのルールは、これらのインターフェイスを使用して実装できます
// Initial interface, entry point of the DSL
interface Start {
End singleWord();
End parameterisedWord(String parameter);
Intermediate1 Word1();
Intermediate2 Word2();
Intermediate3 Word3();
}
// Terminating interface, might also contain methods like execute();
interface End {}
// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
End optionalWord();
}
// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
End wordChoiceA();
End wordChoiceB();
}
// Intermediate interface returning itself on Word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
Intermediate3 Word3();
}
これらの単純なルールを使用すると、SQLのような複雑なDSLをJavaで直接実装できます。これは、私が作成したライブラリ jOOQ によって行われます。 my blog から取ったかなり複雑なSQLの例を参照してください:
create().select(
r1.ROUTINE_NAME,
r1.SPECIFIC_NAME,
decode()
.when(exists(create()
.selectOne()
.from(PARAMETERS)
.where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
.and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
.and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
val("void"))
.otherwise(r1.DATA_TYPE).as("data_type"),
r1.NUMERIC_PRECISION,
r1.NUMERIC_SCALE,
r1.TYPE_UDT_NAME,
decode().when(
exists(
create().selectOne()
.from(r2)
.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
.and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
create().select(count())
.from(r2)
.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
.and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
.as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()
もう1つの素敵な例は、 jRTF です。これは、Javaで直接ドキュメントをRTFドキュメントをセレーションするために設計された小さなDSLです。例:
rtf()
.header(
color( 0xff, 0, 0 ).at( 0 ),
color( 0, 0xff, 0 ).at( 1 ),
color( 0, 0, 0xff ).at( 2 ),
font( "Calibri" ).at( 0 ) )
.section(
p( font( 1, "Second paragraph" ) ),
p( color( 1, "green" ) )
)
).out( out );
連鎖の利点
ie、私はそれを使用したい
言及しなかったチェーンの利点の1つは、変数の初期化中、またはメソッドに新しいオブジェクトを渡すときに、これを使用する機能でした。これが悪い習慣かどうかはわかりません。
私はこれが不自然な例であることを知っていますが、次のクラスがあると言います
Public Class Location
Private _x As Integer = 15
Private _y As Integer = 421513
Public Function X() As Integer
Return _x
End Function
Public Function X(ByVal value As Integer) As Location
_x = value
Return Me
End Function
Public Function Y() As Integer
Return _y
End Function
Public Function Y(ByVal value As Integer) As Location
_y = value
Return Me
End Function
Public Overrides Function toString() As String
Return String.Format("{0},{1}", _x, _y)
End Function
End Class
Public Class HomeLocation
Inherits Location
Public Overrides Function toString() As String
Return String.Format("Home Is at: {0},{1}", X(), Y())
End Function
End Class
そして、基本クラスにアクセスできない、または時間に基づいてデフォルト値が動的であるなどと言います。はい、インスタンス化してから値を変更できますが、特に通過する場合は面倒になりますメソッドへの値:
Dim loc As New HomeLocation()
loc.X(1337)
PrintLocation(loc)
しかし、これは読みやすいだけではありません:
PrintLocation(New HomeLocation().X(1337))
または、クラスのメンバーはどうですか?
Public Class Dummy
Private _locA As New Location()
Public Sub New()
_locA.X(1337)
End Sub
End Class
対
Public Class Dummy
Private _locC As Location = New Location().X(1337)
End Class
これが私がチェーンを使用している方法であり、通常、私のメソッドは構成のためだけなので、2行の長さで、値を設定してからReturn Me
。私たちにとっては、コードを読み理解するのが非常に難しい巨大な行を、文章のように読む1行に整理しました。何かのようなもの
New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats
対何か
New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
, Dealer.CarPicker.Models.WRX
, Dealer.CarPicker.Transmissions.SixSpeed
, Dealer.CarPicker.Engine.Options.TurboCharged
, Dealer.CarPicker.Exterior.Color.Blue
, Dealer.CarPicker.Interior.Color.Gray
, Dealer.CarPicker.Interior.Options.Leather
, Dealer.CarPicker.Interior.Seats.Heated)
連鎖の悪影響
ie、使用したくない場所
主に行が長くなるため、ルーチンに渡すパラメーターがたくさんある場合、チェーンを使用しません.OPが述べたように、ルーチンの1つに渡すために他のクラスにルーチンを呼び出すと混乱する可能性があります連鎖方法。
また、ルーチンが無効なデータを返すという懸念もあります。したがって、これまでは、呼び出されている同じインスタンスを返すときにのみチェーンを使用していました。指摘したように、クラス間をチェーンすると、デバッグが難しくなり(どれがnullを返しましたか?)、クラス間のカップリング依存関係を増やすことができます。
結論
人生のあらゆるものやプログラミングと同様に、連鎖は良いことでも悪いことでもありません。悪いことを避けることができれば、連鎖は大きなメリットになります。
私はこれらのルールに従うようにします。
多くの場合、読みやすさを念頭に置くのではなく、メソッドチェーンを便利な方法として使用しています。メソッドチェーンは、同じオブジェクトで同じアクションを実行する場合に受け入れられます。ただし、コードの記述を減らすためだけでなく、実際に読みやすさを向上させる場合に限ります。
残念ながら、多くは質問で与えられた例に従ってメソッド連鎖を使用しています。 canはまだ読み取り可能ですが、残念ながら複数のクラス間で高い結合を引き起こしているため、望ましくありません。
これはちょっと主観的なようです。
メソッドチェーンは、本質的に悪いまたは良いものではありません。
読みやすさが最も重要です。
(また、多数のメソッドをチェーン化すると、何かが変更された場合に非常に脆弱になることを考慮してください)
主に誤解は、これが一般にオブジェクト指向のアプローチであると考えていることだと思いますが、実際には他の何よりも機能的なプログラミングのアプローチです。
私がそれを使用する主な理由は、読みやすさと、コードが変数であふれないようにするためです。
他の人がそれが読みやすさを損なうと言っているときに何を話しているのか、私は本当に理解していません。これは、私が使用したプログラミングの中で最も簡潔でまとまりのある形式の1つです。
これも:
convertTextToVoice.LoadText( "source.txt")。ConvertToVoice( "destination.wav");
通常どのように使用するかです。これを使用してx個のパラメーターをチェーンすることは、通常の使用方法ではありません。メソッド呼び出しにx個のパラメーターを入れたい場合、params構文を使用します。
public void foo(params object [] items)
タイプに基づいてオブジェクトをキャストするか、ユースケースに応じてデータ型の配列またはコレクションを使用します。
メソッドチェーンは、ほとんどの場合、単に目新しいものかもしれませんが、その場所があると思います。 CodeIgniterのActive Recordの使用 に1つの例があります。
$this->db->select('something')->from('table')->where('id', $id);
それはかなりきれいに見えます(私の意見では、より理にかなっています):
$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);
それは本当に主観的です。誰もが自分の意見を持っています。
そのため、流、なインターフェイスをライブラリに実装する方法を変更しました。
前:
_collection.orderBy("column").limit(10);
_
後:
_collection = collection.orderBy("column").limit(10);
_
「前の」実装では、関数はオブジェクトを変更し、_return this
_で終了しました。実装を同じタイプの新しいオブジェクトを返すに変更しました。
この変更の理由:
戻り値は関数とは関係がなく、チェーン部分をサポートするために純粋に存在していました。OOPによるとvoid関数であるはずです。
システムライブラリのメソッドチェーンもそのように実装します(linqやstringなど)。
_myText = myText.trim().toUpperCase();
_
元のオブジェクトはそのまま残り、APIユーザーはそれをどう処理するかを決定できます。以下が可能です。
_page1 = collection.limit(10);
page2 = collection.offset(10).limit(10);
_
copy implementationは、オブジェクトの構築にも使用できます。
_painting = canvas.withBackground('white').withPenSize(10);
_
setBackground(color)
関数はインスタンスを変更し、何も返さない(想定されるように)。
関数の動作はより予測可能です(ポイント1および2を参照)。
短い変数名を使用すると、モデルにAPIを強制することなく、コードの混乱を減らすこともできます。
_var p = participant; // create a reference
p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
_
結論:
私の意見では、_return this
_実装を使用する流れるようなインターフェイスは間違っています。
ここで完全に見逃された点は、メソッドの連鎖により[〜#〜] dry [〜#〜]。 「with」の効果的な代役です(一部の言語では実装が不十分です)。
A.method1().method2().method3(); // one A
A.method1();
A.method2();
A.method3(); // repeating A 3 times
これは同じ理由で重要ですDRYは常に重要です。Aがエラーであり、これらの操作をBで実行する必要がある場合、3ではなく1箇所で更新する必要があります。
実際には、この場合の利点はわずかです。それでも、少しタイピングが少なく、少し堅牢(DRY)なので、それを取り上げます。
私はそれが読みやすさを悪化させると思うので、私は一般的にメソッドチェーンを嫌います。コンパクトさは読みやすさと混同されることがよくありますが、それらは同じ用語ではありません。単一のステートメントですべてを実行する場合、それはコンパクトですが、ほとんどの場合、複数のステートメントで実行するよりも読みにくくなります(従うのが難しくなります)。使用したメソッドの戻り値が同じであることを保証できない場合を除き、気づいたように、メソッドチェーンは混乱の原因になります。
1.)
participant
.addSchedule(events[1])
.addSchedule(events[2])
.setStatus('attending')
.save();
対
participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()
2.)
participant
.getSchedule('monday')
.saveTo('monnday.file');
対
mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');
3.)
participant
.attend(event)
.setNotifications('silent')
.getSocialStream('Twitter')
.postStatus('Joining '+event.name)
.follow(event.getSocialId('Twitter'));
対
participant.attend(event);
participant.setNotifications('silent')
Twitter = participant.getSocialStream('Twitter')
Twitter.postStatus('Joining '+event.name)
Twitter.follow(event.getSocialId('Twitter'));
単一のステートメントに改行を追加して読みやすくし、インデントを追加して、異なるオブジェクトについて話していることを明確にする必要があるため、ほとんど何も勝てないことがわかります。 IDベースの言語を使用する場合は、これを行う代わりにPythonを学習します。ほとんどのIDEがコードを自動フォーマットしてインデントを削除することは言うまでもありません。
この種のチェーンが役立つ唯一の場所は、CLIでストリームをパイプするか、SQLで複数のクエリを結合することだけだと思います。どちらも複数のステートメントの価格があります。しかし、複雑な問題を解決したい場合は、価格を支払って変数を使用して複数のステートメントにコードを記述したり、bashスクリプトやストアドプロシージャやビューを記述したりすることになります。
DRY解釈:「知識の繰り返し(テキストの繰り返しではない)を避けてください。」および「入力を減らし、テキストを繰り返してはいけません。」本当に意味しますが、多くの人は「すべての知識がシステム内に単一の明確な権威ある表現を持たなければならない」のような複雑すぎるでたらめを理解できないため、2番目はよくある誤解です。このシナリオでは疎結合がより重要であるため、バインドされたコンテキスト間でコードをコピーすると、DDDによって最初の解釈が中断されます。
チェーンの最大の欠点は、読者が各メソッドが元のオブジェクトにどのように影響するか、それが影響する場合、すべてのメソッドが返すタイプを理解することが難しい場合があることです。
いくつかの質問:
デバッグは、ほとんどの言語で、チェーンを使用すると実際に難しくなります。チェーン内の各ステップが独自の行(チェーンの目的に反している場合)であっても、特に非変更メソッドの場合、各ステップ後に返される値を検査することは困難です。
式の解決ははるかに複雑になる可能性があるため、言語とコンパイラによってはコンパイル時間が遅くなる場合があります。
すべての場合と同様に、チェーンはいくつかのシナリオで便利な優れたソリューションだと思います。慎重に使用し、その意味を理解し、チェーン要素の数を少数に制限する必要があります。
いいもの:
悪い人:
全般:
良いアプローチは、状況が発生するか、特定のモジュールが特に適しているまで、一般的にチェーンを使用しないことです。
連鎖は、特にポイント1および2で計量する場合、場合によっては非常に読みやすさを損なう可能性があります。
非難では、別のアプローチ(たとえば、配列を渡す)や奇妙な方法でメソッドを混合する(parent.setSomething()。getChild()。setSomething()。getParent()。setSomething())などの代わりに、誤用される可能性があります。