可変オブジェクトと不変オブジェクト


173

私は、可変オブジェクトと不変オブジェクトの両方を理解しようとしています。可変オブジェクトを使用すると、多くの悪い結果(たとえば、メソッドから文字列の配列を返すなど)が発生しますが、これによる悪影響について理解できません。可変オブジェクトの使用に関するベストプラクティスは何ですか?可能な限り回避する必要がありますか?


string少なくとも.NETでは不変であり、他の多くの現代言語でも同様だと思います。
Domenic

4
これは、文字列が実際に言語で何であるかに依存します。erlangの「文字列」は単なるintの配列であり、haskellの「文字列」は文字の配列です。
Chii

4
Ruby文字列は可変バイト配列です。あなたがそれらをその場で変えることができるというナッツを私に絶対に駆り立てます。
Daniel Spiewak 08年

6
ダニエル-なぜ?誤ってやっていると思いますか?

5
@DanielSpiewak文字列を適切な位置に変更できる、駆動されるナットのソリューションは単純です。ただ実行しないでください。弦を所定の位置に変更できないため、ナットを駆動するためのソリューションはそれほど単純ではありません。
Kaz

回答:


162

まあ、これにはいくつかの側面があります。

  1. 参照IDのない可変オブジェクトは、奇妙なときにバグを引き起こす可能性があります。たとえばPerson、値ベースのequalsメソッドを持つBean について考えてみます。

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    Personそのためのキーとして使用される場合、インスタンスはマップに「失われた」ますhashCodeと平等を変更可能な値に基づいていました。これらの値はマップの外で変更され、すべてのハッシュは廃止されました。理論家はこの点について考えたがりますが、実際にはそれがあまりにも大きな問題であるとは知りませんでした。

  2. 別の側面は、コードの論理的な「合理性」です。これは定義するのが難しい用語で、読みやすさからフローまですべてを網羅しています。一般的には、コードを見て、それが何をするのかを簡単に理解できるはずです。しかし、それよりも重要なことは、正しく機能することを納得させることができるはずです。オブジェクトが異なるコードの「ドメイン」間で独立して変更できる場合、どこに何があるのか​​を追跡することが困難になる場合があります(「距離のある不気味なアクション」)。これは例示するのが難しい概念ですが、より大きく、より複雑なアーキテクチャーでしばしば直面するものです。

  3. 最後に、変更可能なオブジェクトは、同時状況ではキラーです。別のスレッドから変更可能なオブジェクトにアクセスするときは常に、ロックを処理する必要があります。これにより、スループットが低下し、コードの保守が劇的に難しくなります。十分に複雑なシステムでは、この問題のバランスが取れていないため、維持することがほぼ不可能になります(同時実行の専門家であっても)。

不変オブジェクト(より具体的には、不変コレクション)は、これらの問題をすべて回避します。それらがどのように機能するかを理解すると、コードは読みやすく、保守が容易になり、奇妙で予測できない方法で失敗する可能性が低くなるコードに発展します。不変オブジェクトは、モック可能性が高いだけでなく、実行する傾向のあるコードパターンにより、テストがさらに容易になります。要するに、それらはすべての周りの良い習慣です!

そうは言っても、私はこの問題について熱心ではありません。すべてが不変である場合、いくつかの問題はうまくモデル化しません。しかし、もちろんこれを否定できない意見にする言語を使用していると仮定して、できるだけ多くのコードをその方向に押し出そうとするべきだと思います(C / C ++はJavaと同様にこれを非常に難しくします)。 。つまり、利点は問題によって多少異なりますが、私は不変性を好む傾向があります。


11
素晴らしい反応。ただし、小さな質問が1つあります。C++は不変性を適切にサポートしていないのですか。ではないのconst正し機能は十分?
Dimitri C.

1
@DimitriC .: C ++には、実際にはいくつかのより基本的な機能があり、特に、非共有状態をカプセル化することになっているストレージの場所と、オブジェクトIDをカプセル化するストレージの場所の区別が優れています。
スーパーキャット2013

2
Javaプログラミングの場合、Joshua Blochはこのトピックについて、著書「Effective Java(Item 15)
dMathieuD

27

不変オブジェクトと不変コレクション

ミュータブルオブジェクトとイミュータブルオブジェクトの議論におけるより細かい点の1つは、コレクションへの不変性の概念の拡張の可能性です。不変オブジェクトは、データの単一の論理構造を表すことが多いオブジェクトです(たとえば、不変文字列)。不変オブジェクトへの参照がある場合、オブジェクトの内容は変更されません。

不変コレクションは、決して変更されないコレクションです。

変更可能なコレクションで操作を実行すると、コレクションが適切に変更され、コレクションへの参照を持つすべてのエンティティに変更が反映されます。

不変のコレクションに対して操作を実行すると、変更を反映した新しいコレクションへの参照が返されます。コレクションの以前のバージョンへの参照を持つすべてのエンティティは、変更を認識しません。

巧妙な実装では、その不変性を提供するためにコレクション全体をコピー(複製)する必要はありません。最も単純な例は、単一リンクリストとして実装されたスタックとプッシュ/ポップ操作です。以前のコレクションのすべてのノードを新しいコレクションで再利用して、プッシュ用に1つのノードのみを追加し、ポップ用にノードを複製することはできません。一方、単一リンクリストのpush_tail操作は、それほど単純でも効率的でもありません。

不変変数と可変変数/参照

一部の関数型言語は、オブジェクト参照自体に不変性の概念を採用しており、単一の参照割り当てのみを許可しています。

  • Erlangでは、これはすべての「変数」に当てはまります。オブジェクトを参照に割り当てることができるのは1回だけです。コレクションを操作すると、新しいコレクションを古い参照(変数名)に再割り当てできなくなります。
  • Scalaはこれを言語に組み込み、すべての参照をvarまたはvalで宣言しますで。valsは単一の割り当てであり、機能的なスタイルを促進しますが、varsはCやJavaのようなプログラム構造を可能にします。
  • var / val宣言は必須ですが、多くの従来の言語ではjavaのfinalやCのconstなどのオプションの修飾子を使用しています。

開発の容易さ対パフォーマンス

ほとんどの場合、不変オブジェクトを使用する理由は、副作用のないプログラミングとコードに関する簡単な推論を促進するためです(特に、並行性が高い/並列環境では)。オブジェクトが不変である場合、基になるデータが別のエンティティによって変更されることを心配する必要はありません。

主な欠点はパフォーマンスです。ここに私がJavaで行った簡単なテストの記事がありますおもちゃの問題で不変オブジェクトと可変オブジェクトを比較です。

パフォーマンスの問題はすべてではありませんが、多くのアプリケーションでは問題ではありません。そのため、PythonのNumpy Arrayクラスなどの多くの大きな数値パッケージでは、大きな配列のインプレース更新が可能です。これは、大きな行列とベクトル演算を利用するアプリケーション領域にとって重要です。この大規模なデータ並列および計算集約型の問題は、適切な場所で動作することにより、大幅な高速化を実現します。


12

このブログ投稿を確認してください:http : //www.yegor256.com/2014/06/09/objects-should-be-immutable.html。不変オブジェクトが可変オブジェクトより優れている理由を説明しています。要するに:

  • 不変オブジェクトは、構築、テスト、使用が簡単です
  • 真に不変のオブジェクトは常にスレッドセーフです
  • それらは一時的な結合を回避するのに役立ちます
  • それらの使用法には副作用がありません(防御的なコピーはありません)
  • アイデンティティの可変性の問題が回避されます
  • 彼らは常に失敗の原子性を持っています
  • キャッシュがはるかに簡単です

10

不変オブジェクトは非常に強力な概念です。すべてのクライアントでオブジェクト/変数の一貫性を維持しようとする多くの負担を取り除きます。

これらは、主に値のセマンティクスで使用されるCPointクラスのような低レベルの非ポリモーフィックオブジェクトに使用できます。

または、オブジェクトセマンティクスでのみ使用される、数学関数を表すIFunctionのような、高レベルの多相インターフェースにそれらを使用できます。

最大の利点:不変性+オブジェクトのセマンティクス+スマートポインターにより、オブジェクトの所有権は問題にならず、オブジェクトのすべてのクライアントには、デフォルトで独自のプライベートコピーがあります。暗黙的に、これは並行性が存在する場合の確定的な動作も意味します。

欠点:大量のデータを含むオブジェクトで使用すると、メモリ消費が問題になる可能性があります。これに対する解決策は、オブジェクトの操作をシンボリックに保ち、遅延評価を行うことです。ただし、これにより、シンボリック計算のチェーンが発生する可能性があり、インターフェースがシンボリック操作に対応するように設計されていない場合、パフォーマンスに悪影響を及ぼす可能性があります。この場合に避けなければならないのは、メソッドから大量のメモリを返すことです。チェーンされたシンボリック操作と組み合わせると、大量のメモリ消費とパフォーマンスの低下につながる可能性があります。

したがって、不変オブジェクトは間違いなく、オブジェクト指向設計についての私の主な考え方ですが、それらは教義ではありません。それらはオブジェクトのクライアントの多くの問題を解決しますが、特に実装者にとっても多くの問題を引き起こします。


私はセクション4を最大の利点と誤解したと思います。不変性+オブジェクトのセマンティクス+スマートポインターは、オブジェクトの所有権を「スムーズ」なポイントにします。議論の余地はありますか?"moot"を誤って使用していると思います...次の文がオブジェクトであることを見ると、オブジェクトはその "moot"(議論可能な)動作からの "決定論的動作"を暗示しています。
ベンジャミン

あなたは正しい、私は「moot」を誤って使用しました。それが変更されたと考えます:)
QBziZ '10 / 10/14

6

話している言語を指定する必要があります。CやC ++などの低レベル言語では、可変オブジェクトを使用してスペースを節約し、メモリチャーンを減らします。高水準言語では、不変オブジェクトを使用すると、コード(特にマルチスレッドコード)の動作を簡単に推論できます。これは、「遠くからの不気味なアクション」がないためです。


あなたはスレッドが量子的に絡み合っていることを示唆していますか?それはかなりのストレッチです:)スレッドは実際には、あなたがそれについて考えると、かなり絡み合っています。1つのスレッドが行う変更は、他のスレッドに影響します。+1
ndrewxie 2017

4

変更可能なオブジェクトは、作成またはインスタンス化された後に変更できるオブジェクトであり、変更できない不変のオブジェクトです(件名のWikipediaページを参照)。プログラミング言語でのこの例は、Pythonのリストとタプルです。リストは変更できます(たとえば、作成後に新しい項目を追加できます)が、タプルはできません。

どの状況でもどちらが良いかについて明確な答えがあるとは思いません。彼らは両方とも自分の居場所を持っています。


1

クラス型が可変である場合、そのクラス型の変数はいくつかの異なる意味を持つことができます。たとえば、オブジェクトfooにフィールドint[] arrがありint[3]、数値{5、7、9}を保持する参照を保持しているとします。フィールドのタイプはわかっていますが、フィールドが表すことができるものは少なくとも4つあります。

  • 潜在的に共有参照、場合その保有者には値をカプセル化することだけを気の全てが、5、7、および9 fooの欲求がarr異なる値をカプセル化し、それが所望の値が含まれている別のアレイに置き換える必要があります。のコピーを作成したい場合はfooarr{1,2,3}の値を保持する参照または新しい配列のどちらか便利な方にコピーを与えることができます。

  • ユニバース内のどこでも、値5、7、9をカプセル化する唯一の参照です。現時点で値5、7、9を保持する3つのストレージロケーションのセット。あればfoo、それは値5、8、および9をカプセル化したい、それはどちらか、その配列の2番目の項目を変更したり、値5を保持する新しい配列を作成し、8、および9と古いものを放棄することができます。のコピーを作成する場合、ユニバース内の任意の場所でその配列への唯一の参照として残るためにfooは、コピーarr内で新しい配列への参照に置き換える必要があることに注意してくださいfoo.arr

  • 何らかの理由でデータを公​​開した他のオブジェクトが所有する配列への参照foo(たとえばfoo、データをそこに格納したいなど)。このシナリオでarrは、は配列の内容をカプセル化せず、むしろそのアイデンティティをカプセル化します。arr新しい配列への参照で置き換えると完全にその意味が変わるため、のコピーはfoo同じ配列への参照を保持する必要があります。

  • foo唯一の所有者であるが、何らかの理由で他のオブジェクトによって参照が保持されている配列への参照(たとえば、他のオブジェクトにデータを格納させたい場合-前のケースの裏側)。このシナリオでarrは、アレイのIDとその内容の両方をカプセル化します。arr新しい配列への参照で置き換えると、その意味が完全に変わりますが、クローンのarr参照foo.arrがあると、それfooが唯一の所有者であるという仮定に違反します。したがって、コピーする方法はありませんfoo

理論的にint[]は、よく定義されたシンプルなタイプである必要がありますが、4つの非常に異なる意味があります。対照的に、不変オブジェクト(例:)への参照には、String一般的に1つの意味しかありません。不変オブジェクトの「力」の多くは、その事実に由来しています。


1

可変インスタンスは参照によって渡されます。

不変のインスタンスは値で渡されます。

抽象的な例。HDDにtxtfileという名前のファイルが存在するとします。今、私にtxtfileを要求すると、2つのモードでそれを返すことができます。

  1. txtfileへのショートカットとあなたへのpasショートカットを作成する、または
  2. txtfileのコピーを取り、pasのコピーを入手してください。

最初のモードでは、ショートカットファイルを変更すると元のファイルも変更されるため、返されるtxtfileは変更可能なファイルです。このモードの利点は、返される各ショートカットに必要なメモリ(RAMまたはHDD)が少なくてすむことと、すべての人(私だけでなく所有者も)がファイルの内容を変更する権限を持っていることです。

2番目のモードでは、受信したファイルのすべての変更が元のファイルを参照しないため、返されるtxtfileは不変ファイルです。このモードの利点は、私(所有者)だけが元のファイルを変更できることです。欠点は、返される各コピーにメモリ(RAMまたはHDD)が必要であることです。


これは厳密には当てはまりません。不変のインスタンスは確かに参照によって渡すことができます。コピーは主に、オブジェクトまたはデータ構造を更新する必要があるときに行われます。
Jamon Holmgren 2016

0

配列または文字列の参照を返す場合、外界はそのオブジェクトのコンテンツを変更できるため、変更可能な(変更可能な)オブジェクトにすることができます。


0

イミュータブルは変更できないことを意味し、ミュータブルは変更できることを意味します。

オブジェクトはJavaのプリミティブとは異なります。プリミティブは組み込み型(boolean、intなど)であり、オブジェクト(クラス)はユーザー作成型です。

クラスの実装内でメンバー変数として定義されている場合、プリミティブとオブジェクトは変更可能または不変にすることができます。

多くの人は、プリミティブとその前にfinal修飾子を持つオブジェクト変数は不変であると考えていますが、これは厳密には正しくありません。したがって、最後は変数に対して不変を意味することはほとんどありません。こちらの例
http://www.siteconsortium.com/h/D0000F.phpを参照してください


0

Immutable object-作成後に変更できないオブジェクトの状態です。すべてのフィールドが不変である場合、オブジェクトは不変です

スレッドセーフ

Immutableオブジェクトの主な利点は、当然ながら並行環境に適していることです。並行性の最大の問題は、shared resourceどのスレッドでも変更できることです。しかし、オブジェクトが不変である場合、read-onlyそれはスレッドセーフな操作です。元の不変オブジェクトを変更すると、コピーが返されます

副作用なし

開発者は、不変オブジェクトの状態がどこからでも変更できないことを完全に確信しています(故意かどうかにかかわらず)

コンパイル最適化

性能を上げる

不利益:

オブジェクトのコピーは、変更可能なオブジェクトを変更するよりも操作が重いため、パフォーマンスのフットプリントがあります。

immutableオブジェクトを作成するには、以下を使用する必要があります。

  1. 言語レベル。各言語には、それを支援するツールが含まれています。たとえば、Javaにはとがfinalありprimitives、Swiftにはletstruct[About]があります。言語は変数のタイプを定義します。たとえば、Javaには型primitivereference型があり、Swiftには型valuereference[About]があります。不変オブジェクトの場合、より便利なのはprimitivesvalueデフォルトでコピーを作成するタイプです。用としてreferenceのタイプはより困難(あなたはそれのうち、オブジェクトの状態を変更することができますので)が、可能です。たとえばclone、開発者レベルでパターンを使用できます

  2. 開発者レベル。開発者として、状態を変更するためのインターフェースを提供するべきではありません

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