私は、可変オブジェクトと不変オブジェクトの両方を理解しようとしています。可変オブジェクトを使用すると、多くの悪い結果(たとえば、メソッドから文字列の配列を返すなど)が発生しますが、これによる悪影響について理解できません。可変オブジェクトの使用に関するベストプラクティスは何ですか?可能な限り回避する必要がありますか?
私は、可変オブジェクトと不変オブジェクトの両方を理解しようとしています。可変オブジェクトを使用すると、多くの悪い結果(たとえば、メソッドから文字列の配列を返すなど)が発生しますが、これによる悪影響について理解できません。可変オブジェクトの使用に関するベストプラクティスは何ですか?可能な限り回避する必要がありますか?
回答:
まあ、これにはいくつかの側面があります。
参照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
と平等を変更可能な値に基づいていました。これらの値はマップの外で変更され、すべてのハッシュは廃止されました。理論家はこの点について考えたがりますが、実際にはそれがあまりにも大きな問題であるとは知りませんでした。
別の側面は、コードの論理的な「合理性」です。これは定義するのが難しい用語で、読みやすさからフローまですべてを網羅しています。一般的には、コードを見て、それが何をするのかを簡単に理解できるはずです。しかし、それよりも重要なことは、正しく機能することを納得させることができるはずです。オブジェクトが異なるコードの「ドメイン」間で独立して変更できる場合、どこに何があるのかを追跡することが困難になる場合があります(「距離のある不気味なアクション」)。これは例示するのが難しい概念ですが、より大きく、より複雑なアーキテクチャーでしばしば直面するものです。
最後に、変更可能なオブジェクトは、同時状況ではキラーです。別のスレッドから変更可能なオブジェクトにアクセスするときは常に、ロックを処理する必要があります。これにより、スループットが低下し、コードの保守が劇的に難しくなります。十分に複雑なシステムでは、この問題のバランスが取れていないため、維持することがほぼ不可能になります(同時実行の専門家であっても)。
不変オブジェクト(より具体的には、不変コレクション)は、これらの問題をすべて回避します。それらがどのように機能するかを理解すると、コードは読みやすく、保守が容易になり、奇妙で予測できない方法で失敗する可能性が低くなるコードに発展します。不変オブジェクトは、モック可能性が高いだけでなく、実行する傾向のあるコードパターンにより、テストがさらに容易になります。要するに、それらはすべての周りの良い習慣です!
そうは言っても、私はこの問題について熱心ではありません。すべてが不変である場合、いくつかの問題はうまくモデル化しません。しかし、もちろんこれを否定できない意見にする言語を使用していると仮定して、できるだけ多くのコードをその方向に押し出そうとするべきだと思います(C / C ++はJavaと同様にこれを非常に難しくします)。 。つまり、利点は問題によって多少異なりますが、私は不変性を好む傾向があります。
ミュータブルオブジェクトとイミュータブルオブジェクトの議論におけるより細かい点の1つは、コレクションへの不変性の概念の拡張の可能性です。不変オブジェクトは、データの単一の論理構造を表すことが多いオブジェクトです(たとえば、不変文字列)。不変オブジェクトへの参照がある場合、オブジェクトの内容は変更されません。
不変コレクションは、決して変更されないコレクションです。
変更可能なコレクションで操作を実行すると、コレクションが適切に変更され、コレクションへの参照を持つすべてのエンティティに変更が反映されます。
不変のコレクションに対して操作を実行すると、変更を反映した新しいコレクションへの参照が返されます。コレクションの以前のバージョンへの参照を持つすべてのエンティティは、変更を認識しません。
巧妙な実装では、その不変性を提供するためにコレクション全体をコピー(複製)する必要はありません。最も単純な例は、単一リンクリストとして実装されたスタックとプッシュ/ポップ操作です。以前のコレクションのすべてのノードを新しいコレクションで再利用して、プッシュ用に1つのノードのみを追加し、ポップ用にノードを複製することはできません。一方、単一リンクリストのpush_tail操作は、それほど単純でも効率的でもありません。
一部の関数型言語は、オブジェクト参照自体に不変性の概念を採用しており、単一の参照割り当てのみを許可しています。
ほとんどの場合、不変オブジェクトを使用する理由は、副作用のないプログラミングとコードに関する簡単な推論を促進するためです(特に、並行性が高い/並列環境では)。オブジェクトが不変である場合、基になるデータが別のエンティティによって変更されることを心配する必要はありません。
主な欠点はパフォーマンスです。ここに私がJavaで行った簡単なテストの記事がありますおもちゃの問題で不変オブジェクトと可変オブジェクトを比較です。
パフォーマンスの問題はすべてではありませんが、多くのアプリケーションでは問題ではありません。そのため、PythonのNumpy Arrayクラスなどの多くの大きな数値パッケージでは、大きな配列のインプレース更新が可能です。これは、大きな行列とベクトル演算を利用するアプリケーション領域にとって重要です。この大規模なデータ並列および計算集約型の問題は、適切な場所で動作することにより、大幅な高速化を実現します。
このブログ投稿を確認してください:http : //www.yegor256.com/2014/06/09/objects-should-be-immutable.html。不変オブジェクトが可変オブジェクトより優れている理由を説明しています。要するに:
不変オブジェクトは非常に強力な概念です。すべてのクライアントでオブジェクト/変数の一貫性を維持しようとする多くの負担を取り除きます。
これらは、主に値のセマンティクスで使用されるCPointクラスのような低レベルの非ポリモーフィックオブジェクトに使用できます。
または、オブジェクトセマンティクスでのみ使用される、数学関数を表すIFunctionのような、高レベルの多相インターフェースにそれらを使用できます。
最大の利点:不変性+オブジェクトのセマンティクス+スマートポインターにより、オブジェクトの所有権は問題にならず、オブジェクトのすべてのクライアントには、デフォルトで独自のプライベートコピーがあります。暗黙的に、これは並行性が存在する場合の確定的な動作も意味します。
欠点:大量のデータを含むオブジェクトで使用すると、メモリ消費が問題になる可能性があります。これに対する解決策は、オブジェクトの操作をシンボリックに保ち、遅延評価を行うことです。ただし、これにより、シンボリック計算のチェーンが発生する可能性があり、インターフェースがシンボリック操作に対応するように設計されていない場合、パフォーマンスに悪影響を及ぼす可能性があります。この場合に避けなければならないのは、メソッドから大量のメモリを返すことです。チェーンされたシンボリック操作と組み合わせると、大量のメモリ消費とパフォーマンスの低下につながる可能性があります。
したがって、不変オブジェクトは間違いなく、オブジェクト指向設計についての私の主な考え方ですが、それらは教義ではありません。それらはオブジェクトのクライアントの多くの問題を解決しますが、特に実装者にとっても多くの問題を引き起こします。
話している言語を指定する必要があります。CやC ++などの低レベル言語では、可変オブジェクトを使用してスペースを節約し、メモリチャーンを減らします。高水準言語では、不変オブジェクトを使用すると、コード(特にマルチスレッドコード)の動作を簡単に推論できます。これは、「遠くからの不気味なアクション」がないためです。
変更可能なオブジェクトは、作成またはインスタンス化された後に変更できるオブジェクトであり、変更できない不変のオブジェクトです(件名のWikipediaページを参照)。プログラミング言語でのこの例は、Pythonのリストとタプルです。リストは変更できます(たとえば、作成後に新しい項目を追加できます)が、タプルはできません。
どの状況でもどちらが良いかについて明確な答えがあるとは思いません。彼らは両方とも自分の居場所を持っています。
クラス型が可変である場合、そのクラス型の変数はいくつかの異なる意味を持つことができます。たとえば、オブジェクトfoo
にフィールドint[] arr
がありint[3]
、数値{5、7、9}を保持する参照を保持しているとします。フィールドのタイプはわかっていますが、フィールドが表すことができるものは少なくとも4つあります。
潜在的に共有参照、場合その保有者には値をカプセル化することだけを気の全てが、5、7、および9 foo
の欲求がarr
異なる値をカプセル化し、それが所望の値が含まれている別のアレイに置き換える必要があります。のコピーを作成したい場合はfoo
、arr
{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つの意味しかありません。不変オブジェクトの「力」の多くは、その事実に由来しています。
可変インスタンスは参照によって渡されます。
不変のインスタンスは値で渡されます。
抽象的な例。HDDにtxtfileという名前のファイルが存在するとします。今、私にtxtfileを要求すると、2つのモードでそれを返すことができます。
最初のモードでは、ショートカットファイルを変更すると元のファイルも変更されるため、返されるtxtfileは変更可能なファイルです。このモードの利点は、返される各ショートカットに必要なメモリ(RAMまたはHDD)が少なくてすむことと、すべての人(私だけでなく所有者も)がファイルの内容を変更する権限を持っていることです。
2番目のモードでは、受信したファイルのすべての変更が元のファイルを参照しないため、返されるtxtfileは不変ファイルです。このモードの利点は、私(所有者)だけが元のファイルを変更できることです。欠点は、返される各コピーにメモリ(RAMまたはHDD)が必要であることです。
配列または文字列の参照を返す場合、外界はそのオブジェクトのコンテンツを変更できるため、変更可能な(変更可能な)オブジェクトにすることができます。
イミュータブルは変更できないことを意味し、ミュータブルは変更できることを意味します。
オブジェクトはJavaのプリミティブとは異なります。プリミティブは組み込み型(boolean、intなど)であり、オブジェクト(クラス)はユーザー作成型です。
クラスの実装内でメンバー変数として定義されている場合、プリミティブとオブジェクトは変更可能または不変にすることができます。
多くの人は、プリミティブとその前にfinal修飾子を持つオブジェクト変数は不変であると考えていますが、これは厳密には正しくありません。したがって、最後は変数に対して不変を意味することはほとんどありません。こちらの例
http://www.siteconsortium.com/h/D0000F.phpを参照してください。
Immutable object
-作成後に変更できないオブジェクトの状態です。すべてのフィールドが不変である場合、オブジェクトは不変です
スレッドセーフ
Immutableオブジェクトの主な利点は、当然ながら並行環境に適していることです。並行性の最大の問題は、shared resource
どのスレッドでも変更できることです。しかし、オブジェクトが不変である場合、read-only
それはスレッドセーフな操作です。元の不変オブジェクトを変更すると、コピーが返されます
副作用なし
開発者は、不変オブジェクトの状態がどこからでも変更できないことを完全に確信しています(故意かどうかにかかわらず)
コンパイル最適化
性能を上げる
不利益:
オブジェクトのコピーは、変更可能なオブジェクトを変更するよりも操作が重いため、パフォーマンスのフットプリントがあります。
immutable
オブジェクトを作成するには、以下を使用する必要があります。
言語レベル。各言語には、それを支援するツールが含まれています。たとえば、Javaにはとがfinal
ありprimitives
、Swiftにはlet
とstruct
[About]があります。言語は変数のタイプを定義します。たとえば、Javaには型primitive
とreference
型があり、Swiftには型value
とreference
型[About]があります。不変オブジェクトの場合、より便利なのはprimitives
、value
デフォルトでコピーを作成するタイプです。用としてreference
のタイプはより困難(あなたはそれのうち、オブジェクトの状態を変更することができますので)が、可能です。たとえばclone
、開発者レベルでパターンを使用できます
開発者レベル。開発者として、状態を変更するためのインターフェースを提供するべきではありません
string
少なくとも.NETでは不変であり、他の多くの現代言語でも同様だと思います。