web-dev-qa-db-ja.com

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

私は、可変オブジェクトと不変オブジェクトの両方について頭を動かそうとしています。可変オブジェクトを使用すると、多くの悪いプレスが発生します(たとえば、メソッドから文字列の配列を返す)が、これのマイナスの影響を理解するのに苦労しています。可変オブジェクトの使用に関するベストプラクティスは何ですか?可能な限り避けるべきですか?

162
Alex Angas

さて、これにはいくつかの側面があります。第一に、参照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であり、等価性が可変値に基づいていたため、キーとして使用すると、マップ内で「失われます」。これらの値はマップ外で変更され、すべてのハッシュは廃止されました。理論家はこの点をハープしたいのですが、実際にはそれがあまりにも大きな問題だとは思いませんでした。

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

最後に、並行オブジェクトでは、可変オブジェクトはkillerです。個別のスレッドから可変オブジェクトにアクセスするときは常に、ロックを処理する必要があります。これによりスループットが低下し、コードが劇的に維持しにくくなります。十分に複雑なシステムでは、この問題はあまりに大きな割合で発生するため、(並行処理の専門家であっても)維持することがほぼ不可能になります。

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

そうは言っても、私はこの問題に関してほとんど熱心ではありません。すべてが不変である場合、一部の問題はうまくモデル化されません。しかし、もちろん、あなたがこれを受け入れがたい意見にする言語を使用していると仮定して、できるだけ多くのコードをその方向にプッシュしようとするべきだと思います(C/C++は、Javaのようにこれを非常に難しくします) 。要するに、利点はあなたの問題にいくらか依存しますが、私は不変性を好む傾向があります。

155
Daniel Spiewak

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

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

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

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

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

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

不変と可変の変数/参照

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

  • Erlangでは、これはすべての「変数」に当てはまります。オブジェクトを参照に割り当てることができるのは一度だけです。コレクションを操作する場合、新しいコレクションを古い参照(変数名)に再割り当てすることはできません。
  • Scalaはこれを言語に組み込み、すべての参照をvarまたはvalで宣言します。valsは単一の割り当てであり、機能を促進しますスタイルですが、C言語またはJavaに似たプログラム構造を許可する変数。
  • Var/val宣言が必要ですが、多くの従来の言語では、JavaのfinalconstCで.

開発のしやすさとパフォーマンス

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

主な欠点はパフォーマンスです。 Javaで行った簡単なテスト おもちゃの問題におけるいくつかの不変オブジェクトと可変オブジェクトの比較について説明します。

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

25
Ben Jackson

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

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

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

これらは、CPointクラスのように、ほとんど値セマンティクスで使用される低レベルの非多態性オブジェクトに使用できます。

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

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

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

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

10
QBziZ

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

6
John Millikin

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

私は、どの状況がすべての状況に適しているかについて明確な答えがあるとは本当に思いません。彼らは両方の場所を持っています。

4
willurd

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

  • 潜在的に共有される参照。所有者はすべて、値5、7、および9をカプセル化することだけを気にします。fooarrに異なる値をカプセル化することを望む場合、目的の値が含まれています。 fooのコピーを作成する場合は、コピーにarrへの参照または値{1,2,3}を保持する新しい配列のいずれか、どちらか便利な方を与えることができます。

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

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

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

理論的には、int[]はニースの単純で明確な型である必要がありますが、4つの非常に異なる意味があります。対照的に、不変オブジェクト(たとえば、String)への参照は、通常1つの意味しかありません。不変オブジェクトの「パワー」の多くは、その事実に由来しています。

1
supercat

不変の手段は変更できず、不変の手段は変更できることを意味します。

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

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

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

0
JTHouseCat

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

0
user1923551

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

Immutableインスタンスは値で渡されます。

抽象的な例。 HDDにtxtfileという名前のファイルが存在するとします。さて、あなたにtxtfileを尋ねると、2つのモードで返すことができます:

  1. txtfileへのショートカットを作成し、あなたへのpasショートカット、または
  2. txtfileのコピーを取り、pasコピーをあなたに渡します。

最初のモードで返されるtxtfileは可変ファイルです。これは、ショートカットファイルを変更すると、元のファイルも変更されるためです。このモードの利点は、返されるショートカットごとに(RAMまたはHDDで)必要なメモリが少なくなり、欠点は、ファイルコンテンツを変更する権限を所有していることです。

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

0
Teodor