web-dev-qa-db-ja.com

クラスのプロパティがクラスの新しいインスタンスを作成して返す場合、それはアンチパターンですか?

いくつかのことを行うHeadingというクラスがありますが、現在の見出し値の反対を返すこともできます。これは、Headingクラス自体。

reciprocalという単純なプロパティを使用して現在の値の反対の見出しを返し、Headingクラスの新しいインスタンスを手動で作成するか、createReciprocalHeading()のようなメソッドを自動的に作成してHeadingクラスの新しいインスタンスを作成し、それをユーザーに返します。

しかし、私の同僚の1人は、reciprocalというクラスpropertyを作成して、そのゲッターメソッドを介してクラス自体の新しいインスタンスを返すように勧めました。

私の質問は、クラスのプロパティがそのように振る舞うのはアンチパターンではないですか?

私はこれが直感的でないことに特に気づきました。

  1. 私の考えでは、クラスのプロパティはクラスの新しいインスタンスを返すべきではありません。
  2. プロパティの名前であるreciprocalは、IDEからの助けを得たり、ゲッターの署名を確認したりしなければ、開発者がその動作を完全に理解するのに役立ちません。

クラスプロパティが何をすべきかについて私は厳格すぎるのでしょうか、それとも正当な懸念事項ですか?私は常にフィールドとプロパティを介してクラスの状態を管理し、メソッドを介してその動作を管理しようとしましたが、これがクラスプロパティの定義にどのように適合しているかはわかりません。

24
53777A

Copy()やClone()のようなものがあることは不明ではありませんが、はい、これについて心配するのは正しいと思います。

例えば:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

プロパティが毎回新しいオブジェクトであることを警告するのはいいことです。

また、次のことも想定します。

h2.reciprocal == h1

しかしながら。見出しクラスが不変の値型である場合、これらの関係を実装でき、相互関係が操作の適切な名前になる場合があります

22
Ewan

クラスのインターフェースは、クラスのユーザーがそれがどのように機能するかについての仮定を立てるように導きます。

これらの仮定の多くが正しく、いくつかが間違っている場合、インターフェースは良好です。

これらの仮定の多くが間違っており、いくつかが正しい場合、インターフェースはごみです。

プロパティに関する一般的な仮定は、get関数の呼び出しが安価であることです。プロパティに関するもう1つの一般的な仮定は、get関数を続けて2回呼び出すと同じ結果が返されるということです。


期待を変更するために、一貫性を使用してこれを回避できます。たとえば、VectorRayMatrixなどが必要な小さな3Dライブラリの場合、Vector.normalMatrix.inverseなどのゲッターがほとんど常に高価になるようにすることができます。残念ながら、インターフェースが一貫して高価なプロパティを使用している場合でも、インターフェースはVector.create_normal()およびMatrix.create_inverse()を使用するインターフェースと同じくらい直観的です-しかし、私はそれを行うことができる強力な引数がないことを知っていますプロパティを使用すると、期待を変えた後でも、より直感的なインターフェイスが作成されます。

17
Peter

プロパティは非常に迅速に返され、繰り返しの呼び出しで同じ値を返す必要があり、その値を取得しても副作用はありません。ここで説明する内容は、プロパティとして実装する必要があります。

あなたの直感は広く正しいです。ファクトリメソッドは、呼び出しを繰り返しても同じ値を返しません。毎回新しいインスタンスを返します。これは、それをプロパティとしてほとんど失格にします。後の開発でインスタンスの作成に重みが加わった場合も、高速ではありません。ネットワークの依存関係。

プロパティは通常、クラスの現在の状態の一部を取得/設定する非常に単純な操作である必要があります。工場のようなオペレーションはこの基準を満たしていません。

10
Brad Thomas

「プロパティ」を構成するものは言語固有の質問であり、「プロパティ」の呼び出し側が期待するのは言語固有の質問でもあるので、これには言語に依存しない答えがあるとは思いません。これについて考える最も実り多い方法は、発信者の観点からそれがどのように見えるかを考えることだと思います。

C#では、プロパティは(従来のように)大文字(メソッドのように)になっていますが、括弧(パブリックインスタンス変数のように)がないのが特徴です。次のコードがあり、ドキュメントがない場合、何を期待しますか?

_var reciprocalHeading = myHeading.Reciprocal;
_

相対的なC#の初心者ですが、Microsoftの プロパティ使用ガイドライン を読んでいる人として、特にReciprocalに期待しています:

  1. Headingクラスの論理データメンバーであること
  2. 値段をキャッシュする必要がないように、呼び出すのに費用がかからない
  3. 観察可能な副作用がない
  4. 連続して2回呼び出されても同じ結果を生成します
  5. (たぶん)ReciprocalChangedイベントを提供する

これらの仮定のうち、(3)と(4)はおそらく正しいです(Headingは不変の値型であると想定します Ewanの答え のように)、(1)は議論の余地があります、(2)不明ですが、議論の余地があります。(5)は意味的に意味をなさそうにありません(ただし、has見出しにはおそらくHeadingChangedイベントが必要です)。これは、C#APIでは「逆数を取得または計算」する必要があることを示唆していますnotはプロパティとして実装されていますが、特に計算が安価でHeadingが不変である場合は、ボーダーラインケース。

(ただし、これらの懸念のどれも、プロパティを呼び出すかどうかとは関係ありません新しいインスタンスを作成します、(2)でもありません。CLRでのオブジェクトの作成自体は、かなり簡単です。安いです。)

Javaでは、プロパティはメソッドの命名規則です。見たら

_Heading reciprocalHeading = myHeading.getReciprocal();
_

私の期待は上記と同様です(明確に示されていない場合):通話は安く、べき等であり、副作用がないと思います。ただし、JavaBeansフレームワークの外では、「プロパティ」の概念はJavaでそれほど意味がありません。特に、対応するsetReciprocal()のない不変のプロパティを検討する場合、getXXX()規約はやや古風。 Effective Java 、第2版(すでに8年以上経過している)から:

呼び出されたオブジェクトのboolean以外の関数または属性を返すメソッドには、通常、名詞、名詞句、または動詞get…で始まる動詞句が付けられます。 3番目の形式(getで始まる)のみが受け入れ可能であると主張する発言条件がありますが、この主張の根拠はほとんどありません。通常、最初の2つの形式を使用すると、コードが読みやすくなります…(p。239)

現代のより流暢なAPIでは、

_Heading reciprocalHeading = myHeading.reciprocal();
_

-呼び出しは安く、べき等であり、副作用がないことを再び示唆しますが、新しい計算が実行されるか、新しいオブジェクトが作成されるかについては何も言いません。これは問題ありません。良いAPIでは、気にする必要はありません。

Rubyには、プロパティなどというものはありません。 「属性」がありますが、

_reciprocalHeading = my_heading.reciprocal
_

_@reciprocal_または単純なアクセサメソッドを介してインスタンス変数_attr_reader_にアクセスしているかどうか、または高価な計算を実行するメソッドを呼び出しているかどうかをすぐに知る方法はありません。ただし、メソッド名がcalcReciprocalではなく単純な名詞であるという事実は、呼び出しが少なくとも安く、おそらく副作用がないことを示唆しています。

Scalaでは、命名規則は、副作用のあるメソッドは括弧を取り、括弧のないメソッドは括弧を取りませんが、

_val reciprocal = heading.reciprocal
_

次のいずれかになります。

_// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …
_

(Scala allowsそれでもdisiscouragedstyle guide によるさまざまなものであることに注意してください。これはScalaに対する私の大きな不満。)

括弧がないことから、この呼び出しには副作用がないことがわかります。この名前も、通話が比較的安価であることを示しています。それを超えて、私は気にしないそれが私に価値をもたらす方法。

要するに:使用している言語を理解し、他のプログラマーがAPIにもたらす期待を理解します。その他はすべて実装の詳細です。

5
David Moles

他の人が言ったように、同じクラスのインスタンスを返すのはかなり一般的なパターンです。

命名は、言語の命名規則に対応している必要があります。

たとえば、Javaの場合、おそらくgetReciprocal();と呼ばれると思います。

そうは言っても、私はmutable vs immutableオブジェクトを検討します。

immutable onesを使用すると、物事は非常に簡単になり、同じオブジェクトを返すかどうかに関係なく問題はありません。

mutable onesの場合、これは非常に恐ろしいことになります。

_b = a.reciprocal
a += 1
b = what?
_

では、bは何を指しているのでしょうか。 aの元の値の逆数?または、変更されたものの逆ですか?これは例としては短すぎるかもしれませんが、要点はわかります。

それらのケースでは、何が起こるかを伝えるより良いnamingを探します。例えば、createReciprocal()がより良い選択かもしれません。

しかし、それは実際に状況にも依存します。

2
Eiko

単一の応答性と明快さのために、オブジェクトを受け取り、その逆を返すReverseHeadingFactoryを用意します。これにより、新しいオブジェクトが返されていることが明確になり、その逆を生成するコードが他のコードからカプセル化されました。

1
matt freake

場合によります。私は通常、プロパティがインスタンスの一部であるものを返すことを期待しているため、同じクラスの別のインスタンスを返すことは少し珍しいでしょう。

しかし、たとえば、文字列クラスは、 "firstWord"、 "lastWord"、またはUnicode "firstLetter"、 "lastLetter"を処理する場合、完全な文字列オブジェクト(通常はより小さなもの)を持つことができます。

0
gnasher729

他の回答はプロパティ部分をカバーしているので、reciprocalについて#2に対処します。

reciprocal以外は使用しないでください。これは、あなたが説明しているものに対する唯一の正しい用語です(そしてそれは正式な用語です)。開発者が作業しているドメインを学習しないように、誤ったソフトウェアを作成しないでください。ナビゲーションでは、用語には非常に具体的な意味があり、reverseまたはoppositeのように無害に見えるものを使用すると、特定の状況での混乱。

0
Shaz

論理的には、いいえ、それは見出しのプロパティではありません。もしそうなら、負の数値はその数値のプロパティであり、率直に言って「プロパティ」がすべての意味を失うようになると言うこともできます。ほとんどすべてがプロパティです。

逆数は、見出しの純粋な関数です。数字の負数は、その数字の純粋な関数と同じです。

経験則として、設定しても意味がない場合は、おそらくプロパティであってはなりません。もちろん、それを設定することはまだ許可されていませんが、セッターを追加することは理論的に可能で意味のあるものでなければなりません。

さて、一部の言語では、その言語のメカニズムに起因するいくつかの利点をもたらす場合は、とにかくその言語のコンテキストで指定されているプロパティとしてそれを持っていることは意味があるかもしれません。たとえば、データベースに保存する場合、ORMフレームワークによる自動処理が可能であれば、プロパティとして作成することをお勧めします。あるいは、プロパティとパラメーターなしのメンバー関数の間に区別がない言語かもしれません(私はそのような言語を知りませんが、いくつかあると確信しています)。次に、関数とプロパティを区別するのは、APIデザイナーとドキュメント作成者の責任です。


編集:特にC#の場合は、既存のクラス、特に標準ライブラリのクラスを調べます。

  • BigInteger および Complex 構造があります。ただし、これらは構造体であり、静的関数のみがあり、すべてのプロパティの型が異なります。ですから、Headingクラスの設計にはあまり役立ちません。
  • Math.NET Numericsには Vector class があり、プロパティはほとんどなく、Headingにかなり近いようです。これを行えば、プロパティではなく、逆数の関数ができます。

コードで実際に使用されているライブラリ、または既存の同様のクラスを確認したい場合があります。一貫性を保つようにしてください。1つの方法が明らかに正しくなく、別の方法が間違っている場合は、通常、それが最善です。

0
hyde