web-dev-qa-db-ja.com

すべてのキーOOP機能を失うことなく、OOPで不変性を実際に使用できますか?

プログラム内のオブジェクトを不変にすることの利点がわかります。アプリケーションの優れたデザインについて本当に深く考えていると、多くの場合、オブジェクトの多くが不変であることに自然に到達します。私のオブジェクトをall不変にしたい場合がよくあります。

この質問 は同じ考えを扱っていますが、不変性への良いアプローチとは何か、そしていつそれを実際に使用するべきかについての答えはありません。良い不変のデザインパターンはありますか?一般的な考え方は、「変更することが絶対に必要でない限り、オブジェクトを不変にする」ことであるように見えますが、これは実際には役に立たないものです。

私の経験では、不変性により、コードはますます機能的なパラダイムに駆り立てられ、この進歩は常に起こります。

  1. リストやマップなどの(機能的な意味で)永続的なデータ構造が必要になります。
  2. 相互参照を使用することは非常に不便です(たとえば、ツリーノードが子を参照し、子が親を参照するなど)。これにより、相互参照がまったく使用されなくなり、データ構造とコードがより機能的になります。
  3. 継承は意味をなさないように停止し、代わりに構成を使用し始めます。
  4. カプセル化のようなOOPの基本的な概念全体が崩れ始め、オブジェクトが関数のように見え始めます。

この時点で、実際にはOOPパラダイムの何も使用しておらず、純粋に関数型の言語に切り替えることができます。したがって、私の質問:優れた不変OOP設計への一貫したアプローチはありますか、それとも、不変のアイデアを最大限に活用すると、常に何も必要としない関数型言語でプログラミングすることになります。もうOOPの世界から? OOPがバラバラにならないようにするために、どのクラスを不変にすべきか、どれを変更可能にしておくべきかを決定するための良いガイドラインはありますか?

便宜上、例を示します。不変のチェスの駒の不変のコレクションとしてChessBoardを持ちましょう(抽象クラ​​スPieceを拡張)。 OOPの観点から見ると、駒はボード上のその位置から有効な動きを生成する責任があります。しかし、ムーブを生成するには、駒がボードへの参照を必要とする一方で、駒がボードへの参照を必要とします。まあ、OOP言語に応じてこれらの不変の相互参照を作成するためのいくつかのトリックがありますが、それらは管理するのが面倒で、ボードを参照する部分がない方がよいでしょう。しかし、ボードの状態が分からないため、ピースはその動きを生成できません。そうすると、ピースはピースのタイプとその位置を保持する単なるデータ構造になります。その後、多態性関数を使用して、あらゆる種類のピースの動きを生成できます。これは関数型プログラミングでは完全に実現可能ですが、ランタイムタイプチェックやその他の悪いOOPの実践なしではOOPではほとんど不可能です...次に、移動は、新しいボードを作成する関数です古いボード、これも完璧な意味を持つ機能的なアイデアですが、もうOOPとは関係ありません。

11
lishaak

すべてのキーOOP=機能を失うことなく、OOPで不変性を実際に使用できますか?

理由がわからない。 Java=とにかく何年も前からそれを行っていました。とにかくすべてが機能するようになりました。文字列を聞いたことがありますか?最初から、不変です。

  1. リストやマップなどの(機能的な意味で)永続的なデータ構造が必要になります。

ずっとそれらを必要としてきました。私が読んでいる間にコレクションを変更したので、イテレータを無効にするのは失礼です。

  1. 相互参照を使用することは非常に不便です(たとえば、ツリーノードが子を参照し、子が親を参照するなど)。これにより、相互参照がまったく使用されなくなり、データ構造とコードがより機能的になります。

循環参照は特別な種類の地獄です。不変性はそれからあなたを救うことはありません。

  1. 継承は意味をなさないように停止し、代わりに構成を使用し始めます。

さてここで私はあなたと一緒ですが、それが不変性とどう関係しているのかわかりません。作曲が好きなのは、動的な戦略パターンが好きだからではなく、抽象化のレベルを変更できるからです。

  1. カプセル化のようなOOPの基本的な考え方全体がバラバラになり始め、私のオブジェクトは関数のように見え始めます。

「カプセル化のようなOOP」のあなたの考えが何であるかを考えると身震いします。ゲッターとセッターが含まれている場合は、カプセル化を呼び出さないでください。それは決してありませんでした。それはマニュアルのアスペクト指向プログラミングです。検証する場所とブレークポイントを配置する場所はいいですが、カプセル化されていません。カプセル化は、内部で何が起こっているのかを知らないか気にしないという私の権利を守っています。

オブジェクトは関数のように見えるはずです。それらは機能の袋です。それらは、一緒に動き、一緒に自分自身を再定義する機能のバッグです。

現時点では関数型プログラミングが流行しており、人々はOOPに関するいくつかの誤解を投げかけています。これがOOPの終わりであると信じ込ませないでください。機能的およびOOPは、非常にうまく共存できます。

  • 関数型プログラミングは、割り当てについて正式なものになっています。

  • OOPは関数ポインタについて正式なものです。

本当にそれ。 Dykstraはgotoは有害であると私たちに言ったので、それについて正式に理解し、構造化プログラミングを作成しました。ちょうどそのように、これら2つのパラダイムは、これらの厄介なことを何気なく行うことから生じる落とし穴を回避しながら、物事を成し遂げる方法を見つけることに関するものです。

何かお見せしましょう:

f(バツ)

それは機能です。それは実際には一連の関数です:

f1(バツ)
f2(バツ)
...
f(バツ)

OOP言語でそれをどのように表現するか?

n.f(x)

その小さなnは、使用されるfの実装を選択し、その関数で使用される定数の一部を決定します(正直に同じことを意味します)。例えば:

f1(x)= x + 1
f2(x)= x + 2

これは、クロージャーが提供するものと同じです。クロージャがそれらを含むスコープを参照する場合、オブジェクトメソッドはインスタンスの状態を参照します。オブジェクトはクロージャを1つ上手に実行できます。クロージャーは、別の関数から返される単一の関数です。コンストラクターは、関数のバッグ全体への参照を返します。

g1(x)= x2 + 1
g2(x)= x2 + 2

うん、それを推測した:

n.g(x)

fとgは、一緒に変化し、一緒に動き回る関数です。同じ袋に入れます。これが実際のオブジェクトです。 n定数(不変)を保持することは、呼び出し時にこれらが何を行うかを予測するのが簡単になることを意味します。

これが単なる構造です。 OOPは、他の小さなことと対話する小さなことの集まりです。うまくいけば、小さなものの小さな選択グループに期待します。コード化するとき、自分がオブジェクトであると想像します。オブジェクトの観点から物事を行います。そして、オブジェクトを使いすぎないように、怠惰になるようにします。私は、単純なメッセージを取り込んで、少し作業し、単純なメッセージを親友だけに送信します。そのオブジェクトを使い終わったら、別のオブジェクトに飛び乗って、その観点から物事を調べます。

クラス責任カードは、私がそのように考えることを私に教える最初の人でした。当時私はそれらについて混乱していましたが、今日それらがまだ関連していない場合はいまいましいです。

不変のチェスの駒の不変のコレクション(抽象クラ​​スPieceを拡張)としてChessBoardを持ちましょう。 OOPの観点から見ると、ピースはボード上の位置から有効な移動を生成する責任があります。ただし、移動を生成するには、ボードが参照を持つ必要がある一方で、ピースはボードへの参照を必要とします。その部分に。

アーグ!ここでも、不必要な循環参照があります。

方法:ChessBoardDataStructureは、x yコードをピース参照に変換します。これらの部分には、x、y、および特定のChessBoardDataStructureを取得して、ブランドスパンキングの新しいChessBoardDataStructuresのコレクションに変換するメソッドがあります。次に、それを最良の動きを選択できるものに押し込みます。これでChessBoardDataStructureは不変になり、ピースも不変になります。このようにして、記憶に白いポーンが1つだけ存在します。正しいxとyの位置に、いくつかの参照があります。オブジェクト指向、機能的、不変。

ちょっと待ってください チェスについてはもう話しませんでしたか?

24
candied_orange

私の考えでは、OOPによって主流に導入された最も有用な概念は次のとおりです。

  • 適切なモジュール化。
  • データのカプセル化。
  • プライベートインターフェイスとパブリックインターフェイスの明確な分離。
  • コードの拡張性のための明確なメカニズム。

これらの利点はすべて、継承やクラスなどの従来の実装の詳細なしでも実現できます。アランケイの「オブジェクト指向システム」の当初のアイデアは、「メソッド」ではなく「メッセージ」を使用しており、Erlangに近いものでした。 C++。多くの伝統的なOOP実装の詳細を取り除きますが、それでも合理的にオブジェクト指向に感じられます。

不変オブジェクトを使用する場合でも、従来のほとんどのOOPメリット:インターフェース、動的ディスパッチ、カプセル化を使用できます。セッターは不要で、多くの場合、よりシンプルなゲッターも不要です。オブジェクト。不変性のメリットを享受することもできます。その間、オブジェクトが変更されていないこと、防御的なコピーが行われていないこと、データ競合がないこと、そしてメソッドを純粋にすることは簡単です。

Scalaが不変性とFPのアプローチをOOPと組み合わせようとする方法をご覧ください。最もシンプルでエレガントな言語ではないことは認められます。ただし、実用的にはかなり成功しています。また、類似のミックスに対して多くのツールとアプローチを提供するKotlinもご覧ください。

言語の作成者が考えていたものとは異なるアプローチを試みる際の通常の問題[〜#〜] n [〜#〜]年前は、標準ライブラリとの「インピーダンスの不一致」です。 OTOH両方Javaと.NETエコシステムは現在、不変のデータ構造に対して妥当な標準ライブラリサポートを持っています;もちろん、サードパーティのライブラリも存在します。

2
9000