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


24

私はHeadingいくつかのことを行うと呼ばれるクラスを持っていますが、現在の見出し値の反対を返すこともできるはずであり、Headingクラス自体の新しいインスタンスを作成することで最終的に使用する必要があります。

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

しかし、私の同僚の1だけでクラスを作成するために私をお勧めプロパティと呼ばれるreciprocalそのgetterメソッドを介したクラス自体の新しいインスタンスを返すことを。

私の質問は次のとおりです。クラスのプロパティがそのように動作するのはアンチパターンではありませんか?

私は特に以下の理由で直観的ではないと思います:

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

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


6
@Ewanが答えの最後の段落で述べているように、アンチパターンではHeadingなく、不変の型としてreciprocal新しいものを返すことHeadingは、「成功の落とし穴」の良い習慣です。(2つの呼び出しの警告でreciprocal「同じもの」を返す必要があります。つまり、同等性テストに合格する必要があります。)
デイビッドアルノ

20
「私のクラスは不変ではありません」。まさにそこにあなたの本当のアンチパターンがあります。;)
デビッドアルノ

3
@DavidArnoさて、特に言語がネイティブにサポートしていない場合、特定のものを不変にすることでパフォーマンスが大幅に低下することがありますが、そうでない場合は不変が多くの場合に良い習慣であることに同意します。
53777A

2
クラスの@dcorkingはC#とTypeScriptの両方で実装されますが、この言語に依存しないようにします。
53777A

2
@Kaiserludiは答えのように聞こえる(素晴らしい答え)-コメントは明快さを要求するためのもの
16年

回答:


22

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

例えば:

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

プロパティが毎回新しいオブジェクトであるという警告があればいいでしょう。

次のことも想定できます:

h2.reciprocal == h1

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


1
Copy()and Clone()はメソッドであり、プロパティではないことに注意してください。OPは新しいインスタンスを返すプロパティゲッターを指していると思います。
リン

6
知っている。しかし特性はまた、(一種の)C#での方法である
ユアン

4
その場合には、C#が提供してはるかにありますString.Substring()Array.Reverse()Int32.GetHashCode()、など
リン・

If your heading class was an immutable value type-不変オブジェクトのプロパティセッターは、常に新しいインスタンスを返すように追加する必要があると思います(少なくとも、新しい値が古い値と同じでない場合)。これが不変オブジェクトの定義だからです。:)
イヴァンコルミチェック16年

17

クラスのインターフェースは、クラスのユーザーがそれがどのように機能するかを推測するように導きます。

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

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

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


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


10

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

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

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


1
このDateTime.Nowプロパティは一般的に使用され、新しいオブジェクトを参照します。プロパティの名前は、毎回同じオブジェクトが返されるかどうかのあいまいさを取り除くのに役立つと思います。それはすべて名前と使用方法にあります。
グレッグブルクハート

3
DateTime.Nowは間違いと見なされます。stackoverflow.com/questions/5437972/を
ブラッドトーマス

@GregBurghardt新しいオブジェクトの副作用DateTimeは、値型であり、オブジェクトのアイデンティティの概念がないため、それ自体の問題ではない場合があります。私が正しく理解している場合、問題は非決定的であり、毎回新しい値を返すことです。
ゼフスピッツ

1
@GregBurghardt DateTime.Newは静的であるため、オブジェクトの状態を表すことは期待できません。
ツァヒアッシャー

5

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

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

var reciprocalHeading = myHeading.Reciprocal;

相対的なC#初心者ですが、MicrosoftのProperty Usage Guidelinesを読んでいる人として、私はReciprocalとりわけ以下を期待しています。

  1. Headingクラスの論理データメンバーである
  2. 安価に電話をかけ、値をキャッシュする必要がない
  3. 目に見える副作用がない
  4. 連続して2回呼び出された場合、同じ結果を生成します
  5. (たぶん)ReciprocalChangedイベントを提供する

これらの仮定のうち、(3)と(4)はおそらく正しい(ユアンの答えのHeadingように不変の値型であると仮定)、(1)は議論の余地があり、(2)は不明だが議論の余地があり、(5)はそうではない(どんなかかわらセマンティック意味を成している見出しをおそらく持つべきイベントを)。これは、C#APIでは「逆数の取得または計算」をプロパティとして実装すべきではないことを示唆していますが、特に計算が安価で不変である場合は、境界線の場合です。HeadingChangedHeading

(ただし、これらの懸念は、プロパティの呼び出しが新しいインスタンスを作成するかどうかとは関係がないことに注意してくださいなく、(2)でもないことに。CLRでのオブジェクトの作成自体は、かなり安価です。)

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

Heading reciprocalHeading = myHeading.getReciprocal();

私の期待は上記のものと似ています(それほど明確に設定されていない場合):呼び出しは安価で、べき等で、副作用がないことを期待しています。ただし、JavaBeansフレームワークの外では、「プロパティ」の概念はJavaでそれほど意味がありません。特に、対応するのない不変のプロパティを検討するsetReciprocal()場合、getXXX()慣習はやや古くなりました。Effective Javaから、第2版(すでに8年以上前):

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

そして、現代的でより流fluentな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 スタイルガイドでは推奨されていないさまざまなものを許可していることに注意してください。これは、Scalaに対する私の大きな悩みの1つです。)

括弧がないことは、呼び出しに副作用がないことを示しています。この名前も、呼び出しが比較的安価であるべきであることを示唆しています。それを超えて、私は気にしませんそれがどのように価値を得るのか。

つまり、使用している言語を理解し、他のプログラマーがAPIにどのような期待を寄せているかを把握してください。それ以外はすべて実装の詳細です。


1
お気に入りの回答の1つを+1してください。これを書いてくれてありがとう。
53777A

2

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

命名は、言語の命名規則と連動する必要があります。

たとえば、Javaではおそらく呼び出されると期待しています getReciprocal();

そうは言っても、可変オブジェクトと不変オブジェクトを検討します。

不変なもの、物事はとても簡単で、あなたが同じオブジェクトを返すかどうかを傷つけることはありません。

変更可能なもの、これは非常に恐ろしい得ることができます。

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

bは何を指しているのでしょうか?aの元の値の逆数?または、変更されたものの逆ですか?これは例では短すぎるかもしれませんが、ポイントを得ることができます。

そのような場合、何が起こるかを伝えるより良い命名法を探してくださいcreateReciprocal()。例えば、より良い選択かもしれません。

しかし、実際にはコンテキストにも依存します。


あなたは可変性を意味しましたか?
カーステンS

@CarstenSはい、もちろんありがとう。頭がどこにあるのかわかりません。:)
エイコ

これはgetReciprocal()、「通常の」JavaBeansスタイルのゲッターのように「聞こえる」ので、やや反対です。示すか、他の何かの異なるが起こっていることをプログラマへのヒント「て、createXXX()」または多分「calculateXXX()」を好む
user949300

@ user949300私はあなたの懸念にいくらか同意します-そして、さらに下の名前を作成...に言及しました。ただし、欠点もあります。create ...は、常にそうであるとは限らない新しいインスタンスを意味します(いくつかの特別なケースでは、単一の専用オブジェクトを考える)、calculate ...リークの実装の詳細の並べ替え-値札に関する誤った仮定を促す可能性があります。
エイコ

1

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


1
名前を明確にするために+1が、他のソリューションのSRPの何が問題になっていますか?
53777A

3
ファクトリメソッドよりもファクトリクラスを推奨するのはなぜですか?(私の意見では、オブジェクトの有用な責任は、iterator.nextなど、それ自体のようなオブジェクトを
発行する

2
@dcorkingファクトリクラスとメソッド(私の意見では)は通常、「オブジェクトは比較可能か、コンパレータを作成する必要があるか?」と同じタイプの質問です。コンパレータを作成することは常に問題ありませんが、それらを比較する非常に普遍的な方法である場合にのみ、それらを比較可能にすることができます。(たとえば、大きい数字は小さい数字よりも大きい、これは比較に適していますが、すべての偶数がすべてのオッズよりも大きいと言うことができます、私はそれをコンパレータにします)-だから、このために、私は 1つしかない思いますヘッダーを逆にする方法なので、余分なクラスを回避するメソッドを作成することに傾倒します。
キャプテンマン

0

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

しかし、たとえば、文字列クラスには、プロパティ「firstWord」、「lastWord」、またはUnicode文字列「firstLetter」、「lastLetter」を処理できます。


0

他の回答はプロパティの部分をカバーしているので、私はあなたの#2について以下について説明しreciprocalます。

以外は使用しないでくださいreciprocal。これは、あなたが説明しているものに対する唯一の正しい用語です(そして正式な用語です)。開発者が作業しているドメインを学習するのを防ぐために、不適切なソフトウェアを作成しないでください。ナビゲーションでは、用語は非常に具体的な意味を持ち、特定のコンテキストで混乱を招く、reverseまたは一見無害に見えるものを使用しますopposite


0

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

逆数は見出しの純粋な関数であり、負の数はその数の純粋な関数です。

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

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


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

  • BigIntegerComplex構造があります。しかし、それらは構造体であり、静的関数のみを持ち、すべてのプロパティは異なるタイプです。そのため、Headingクラスではあまりデザインは役に立ちません。
  • Math.NET Numericsには、プロパティがほとんどないVectorclassがあり、にかなり近いようHeadingです。これで行けば、プロパティではなく、相互の機能があります。

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


-1

とても興味深い質問です。あなたが提案しているものにSOLID + DRY + KISS違反はありませんが、それでも悪臭がします。

クラスのインスタンスを返すメソッドはコンストラクタと呼ばれますよね?そのため、コンストラクター以外のメソッドから新しいインスタンスを作成しています。それは世界の終わりではありませんが、クライアントとして、私はそれを期待していません。これは通常、特定の状況で行われます:工場または場合によっては、同じクラスの静的メソッド(シングルトンや...それほどオブジェクト指向ではないプログラマーに役立ちますか?)

さらにgetReciprocal()、返されたオブジェクトを呼び出すとどうなりますか?クラスの別のインスタンスを取得します。これは、最初のインスタンスの非常に正確なコピーになる可能性があります。そして、あなたが呼び出す場合getReciprocal()と?誰もがObjects延しているオブジェクト?

繰り返しますが、呼び出し側が相互の(スカラー、つまり)を必要とする場合はどうなりますか?getReciprocal()->getValue()?少しの利益でデメテルの法則に違反しています。


反抗者からの反論を喜んで受け入れます、ありがとう!
ジャンルイジ・ザネ・ザネッティニ博士

1
私はダウン投票しませんでしたが、その理由は、それが実際に残りの答えにあまり多くを追加しないという事実かもしれないと思いますが、私は間違っているかもしれません。
53777A

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