可変オブジェクトと不変オブジェクトに頭を悩ませようとしています。可変オブジェクトを使用すると、多くの悪いプレスが発生します(たとえば、メソッドから文字列の配列を返す)が、これによる悪影響を理解するのに苦労しています。可変オブジェクトの使用に関するベストプラクティスは何ですか?可能な限りそれらを避けるべきですか?
12 に答える
さて、これにはいくつかの側面があります。
参照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
と同等性が可変値に基づいているためです。これらの値はマップの外部で変更され、すべてのハッシュが廃止されました。理論家はこの点をハープするのが好きですが、実際にはそれがあまり問題になるとは思っていません。もう1つの側面は、コードの論理的な「合理性」です。これは、読みやすさから流れまですべてを網羅する、定義するのが難しい用語です。一般的に、コードの一部を見て、それが何をするのかを簡単に理解できるはずです。しかし、それよりも重要なのは、それが正しく機能することを自分自身に納得させることができるはずです。オブジェクトが異なるコード「ドメイン」間で独立して変更される可能性がある場合、何がどこにあるのか、そしてその理由を追跡することが困難になることがあります(「離れた場所での不気味なアクション」)。これは例示するのがより難しい概念ですが、より大きく、より複雑なアーキテクチャでしばしば直面するものです。
最後に、可変オブジェクトは同時発生の状況ではキラーです。別々のスレッドから可変オブジェクトにアクセスするときはいつでも、ロックに対処する必要があります。これによりスループットが低下し、コードの保守が大幅に困難になります。十分に複雑なシステムは、この問題を非常に不均衡に吹き飛ばし、(並行性の専門家でさえ)維持することがほぼ不可能になります。
不変オブジェクト(より具体的には、不変コレクション)は、これらの問題をすべて回避します。それらがどのように機能するかを理解すると、コードは読みやすく、保守しやすく、奇妙で予測できない方法で失敗する可能性が低いものに発展します。不変オブジェクトは、モック性が簡単であるだけでなく、強制する傾向のあるコードパターンもあるため、テストがさらに簡単です。要するに、彼らはいたるところに良い習慣です!
そうは言っても、私はこの問題に熱心ではありません。すべてが不変である場合、一部の問題はうまくモデル化されません。しかし、もちろん、これを支持できる言語を使用していると仮定すると、できるだけ多くのコードをその方向にプッシュするように努めるべきだと思います(C / C ++は、Javaと同様に、これを非常に困難にします) 。要するに、利点はあなたの問題にいくらか依存しますが、私は不変性を好む傾向があります。
不変オブジェクトと不変コレクション
ミュータブル オブジェクトとイミュータブル オブジェクトをめぐる議論の細かい点の 1 つは、イミュータビリティの概念をコレクションに拡張する可能性です。不変オブジェクトは、多くの場合、データの単一の論理構造 (不変文字列など) を表すオブジェクトです。不変オブジェクトへの参照がある場合、オブジェクトの内容は変更されません。
不変コレクションは、決して変更されないコレクションです。
可変コレクションに対して操作を実行すると、コレクションがその場で変更され、コレクションへの参照を持つすべてのエンティティに変更が表示されます。
不変コレクションに対して操作を実行すると、変更を反映した新しいコレクションに参照が返されます。コレクションの以前のバージョンへの参照を持つすべてのエンティティには、変更が表示されません。
巧妙な実装では、その不変性を提供するためにコレクション全体を必ずしもコピー (クローン) する必要はありません。最も単純な例は、単一リンク リストとプッシュ/ポップ操作として実装されたスタックです。新しいコレクションで以前のコレクションのすべてのノードを再利用できます。プッシュ用にノードを 1 つだけ追加し、ポップ用にノードを複製しません。一方、単一リンクリストでの push_tail 操作は、単純でも効率的でもありません。
不変対可変変数/参照
一部の関数型言語は、オブジェクト参照自体に不変性の概念を取り入れ、単一の参照割り当てのみを許可します。
- Erlang では、これはすべての「変数」に当てはまります。オブジェクトを参照に割り当てることができるのは 1 回だけです。コレクションを操作する場合、新しいコレクションを古い参照 (変数名) に再割り当てすることはできません。
- Scala はまた、すべての参照がvarまたはvalで宣言されている言語にこれを組み込みます。
- var/val 宣言は必須ですが、従来の多くの言語では JavaのfinalやCのconstなどのオプションの修飾子を使用しています。
開発の容易さとパフォーマンス
ほとんどの場合、不変オブジェクトを使用する理由は、副作用のないプログラミングとコードに関する単純な推論を促進することです (特に高度な並行/並列環境で)。オブジェクトが不変であれば、基になるデータが別のエンティティによって変更されることを心配する必要はありません。
主な欠点はパフォーマンスです。これは、おもちゃの問題で不変オブジェクトと可変オブジェクトを比較する 、Java で行った簡単なテストに関する記事です。
パフォーマンスの問題は多くのアプリケーションで意味がありませんが、すべてではありません。そのため、Python の Numpy Array クラスなどの多くの大規模な数値パッケージでは、大規模な配列のインプレース更新が許可されています。これは、大規模な行列およびベクトル演算を利用するアプリケーション分野にとって重要です。この大規模なデータ並列および計算集約型の問題は、その場で操作することで大幅な高速化を実現します。
不変オブジェクトは非常に強力な概念です。それらは、すべてのクライアントに対してオブジェクト/変数の一貫性を維持しようとする多くの負担を取り除きます。
これらは、主に値のセマンティクスで使用される、CPointクラスなどの低レベルの非ポリモーフィックオブジェクトに使用できます。
または、オブジェクトセマンティクスでのみ使用される高レベルの多態的なインターフェイス(数学関数を表すIFunctionなど)に使用することもできます。
最大の利点:不変性+オブジェクトセマンティクス+スマートポインターにより、オブジェクトの所有権は問題になりません。オブジェクトのすべてのクライアントは、デフォルトで独自のプライベートコピーを持っています。暗黙的に、これは並行性が存在する場合の決定論的動作も意味します。
短所:大量のデータを含むオブジェクトで使用すると、メモリ消費が問題になる可能性があります。これに対する解決策は、オブジェクトに対する操作をシンボリックに保ち、遅延評価を行うことです。ただし、これによりシンボリック計算のチェーンが発生する可能性があり、インターフェイスがシンボリック演算に対応するように設計されていない場合、パフォーマンスに悪影響を与える可能性があります。この場合、絶対に避けるべきことは、メソッドから大量のメモリを返すことです。連鎖シンボリック操作と組み合わせると、これは大量のメモリ消費とパフォーマンスの低下につながる可能性があります。
したがって、不変オブジェクトは間違いなくオブジェクト指向デザインについての私の主要な考え方ですが、それらは教義ではありません。それらは、オブジェクトのクライアントにとって多くの問題を解決しますが、特に実装者にとっても多くの問題を引き起こします。
このブログ投稿を確認してください: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . 不変オブジェクトが可変オブジェクトよりも優れている理由を説明します。要するに:
- 不変オブジェクトは、構築、テスト、および使用がより簡単です
- 真に不変なオブジェクトは常にスレッドセーフです
- それらは一時的な結合を避けるのに役立ちます
- それらの使用には副作用はありません(防御コピーはありません)
- ID の可変性の問題が回避される
- 彼らは常に失敗の原子性を持っています
- それらはキャッシュするのがはるかに簡単です
話している言語を指定する必要があります。CやC++のような低水準言語の場合、スペースを節約し、メモリのチャーンを減らすために、可変オブジェクトを使用することを好みます。高水準言語では、「離れた場所での不気味なアクション」がないため、不変オブジェクトを使用すると、コード(特にマルチスレッドコード)の動作を簡単に推測できます。
可変オブジェクトは、作成/インスタンス化後に変更できるオブジェクトであり、変更できない不変オブジェクトです(この件に関するウィキペディアのページを参照してください)。プログラミング言語でのこの例は、Pythonのリストとタプルです。リストは変更できますが(たとえば、作成後に新しいアイテムを追加できます)、タプルは変更できません。
すべての状況でどちらが優れているかについて、明確な答えがあるとは思いません。彼らは両方とも彼らの場所を持っています。
まもなく:
可変インスタンスは参照によって渡されます。
不変インスタンスは値渡しです。
抽象的な例。HDD にtxtfileという名前のファイルが存在するとします。txtfileファイルを提供するように依頼された場合は、次の 2 つのモードで行うことができます。
- txtfileへのショートカットを作成し、ショートカットをあなたに渡すことができます。
- txtfileファイルの完全なコピーを作成し、コピーしたファイルをあなたに渡すことができます。
最初のモードでは、返されるファイルは変更可能なファイルを表します。これは、ショートカット ファイルへの変更が元のファイルにも反映され、その逆も同様であるためです。
2 番目のモードでは、コピーされたファイルへの変更は元のファイルに反映されず、その逆も同様であるため、返されるファイルは不変ファイルを表します。
イミュータブルは変更できないことを意味し、ミュータブルは変更できることを意味します。
オブジェクトは Java のプリミティブとは異なります。プリミティブは組み込み型 (boolean、int など) であり、オブジェクト (クラス) はユーザーが作成した型です。
プリミティブとオブジェクトは、クラスの実装内でメンバー変数として定義されている場合、可変または不変にすることができます。
多くの人は、プリミティブと、その前に final 修飾子を持つオブジェクト変数は不変であると考えていますが、これは正確には真実ではありません。したがって、 final は変数に対して不変という意味ではほとんどありません。http://www.siteconsortium.com/h/D0000F.php の例を参照して
ください。