不変オブジェクトが良い場合、なぜ人々は可変オブジェクトを作成し続けるのですか?[閉まっている]


250

不変オブジェクト¹が優れたシンプルなものであり、並行プログラミングでメリットがある場合、プログラマーはなぜ可変オブジェクトを作成し続けるのですか?

私はJavaプログラミングで4年の経験があり、それを見ると、クラスを作成した後に最初に行うことは、IDEでゲッターとセッターを生成することです(したがって、変更可能になります)。認識不足がありますか、ほとんどのシナリオで可変オブジェクトを使用しても問題ありませんか?


¹ 不変オブジェクトとは、作成後に状態を変更できないオブジェクトです。
² 可変オブジェクトは、作成後に変更できるオブジェクトです。


42
私は離れて正当な理由(以下ペーテルで述べたように)、「怠惰な開発者」から「愚かな」開発者よりも一般的な理由である、ということだと思う愚かな開発者の無知開発者は『『もあります』」そして前に。』。
ヨアヒム・ザウアー

201
すべての福音主義のプログラマー/ブロガーには、熱心なブログリーダーが1000人いて、すぐに自分自身を再発明して最新の技術を採用しています。一人一人に1万人のプログラマーがいます。1日の仕事を終わらせ、製品をドアから出すために、研ぎ澄まされた石に鼻を向けています。それらの人たちは、長年彼らのために働いてきた実証済みの信頼できる技術を使用しています。彼らは、新しい技術が広く採用されるまで待って、実際の利点を示してからそれを取り上げます。バカとは呼ばないでください。怠け者ではありません。代わりに「ビジー」と呼んでください。
バイナリの心配

22
@BinaryWorrier:不変オブジェクトはほとんど「新しいもの」ではありません。ドメインオブジェクトにはあまり使用されていなかったかもしれませんが、JavaとC#には最初から使用されていました。また、「怠laz」は必ずしも悪い言葉ではなく、ある種の「怠y」は開発者にとって絶対的な利点です。
ヨアヒムザウアー

11
@Joachim:上記の軽jor的な意味で「怠laz」が使用されたことはかなり明白だと思います:)また、不変オブジェクト(ラムダ計算、OOPなど-昔のことです-はい)突然その月の風味になる。私は彼らが悪いことだと主張しているわけではありません(彼らはそうではありません)、または彼らは自分の場所を持っていません(明らかにそうです)、彼らは最新のグッドワードと熱心に自分自身として回心している(「怠lazな」コメントのせいではない、あなたがそれを緩和しようとしたことは知っている)。
バイナリの心配

116
-1、不変オブジェクトは「良好」ではありません。特定の状況に多かれ少なかれ適切です。あるテクニックや別のテクニックを他のテクニックよりも客観的に「良い」または「悪い」と言っている人は誰でも宗教を売っています。
GrandmasterB

回答:


326

可変オブジェクトと不変オブジェクトには、それぞれ長所と短所があります。

多くの場合、不変オブジェクトは確かに人生をよりシンプルにします。これらは、オブジェクトにIDがないため、簡単に置き換えることができる値タイプに特に適用されます。また、並行プログラミングをより安全かつクリーンにすることができます(並行性のバグを見つけるのが難しいことで有名なのは、スレッド間で共有される可変状態が最終的に発生するためです)。ただし、大規模なオブジェクトや複雑なオブジェクトの場合、変更ごとにオブジェクトの新しいコピーを作成するのは非常にコストがかかり、面倒です。また、個別のIDを持つオブジェクトの場合、既存のオブジェクトの変更は、変更された新しいコピーを作成するよりもはるかに簡単で直感的です。

ゲームキャラクターについて考えます。ゲームでは速度が最優先であるため、ゲームキャラクターを可変オブジェクトで表すと、ゲームキャラクターの新しいコピーが小さな変更ごとに生成される代替実装よりも、ゲームの実行速度が大幅に向上する可能性が高くなります。

さらに、私たちの現実世界の認識は、必然的に可変オブジェクトに基づいています。ガソリンスタンドで車を燃料で満たすと、空のタンクのある古い車が連続した新しい車に交換されたかのようにではなく、同じオブジェクトとして認識されます(つまり、状態が変化している間、そのアイデンティティが維持されます)車のインスタンスが徐々にタンクをいっぱいにしていきます。したがって、プログラムで実世界のドメインをモデリングするときは常に、実世界のエンティティを表す可変オブジェクトを使用してドメインモデルを実装する方が通常はより簡単で簡単です。

悲しいかな、これらすべての正当な理由は別として、人々が可変オブジェクトを作成し続ける最も可能性の高い原因は、心の慣性、別名変化に対する抵抗です。今日のほとんどの開発者は、不変性(およびそれを含むパラダイム、関数型プログラミング)が影響範囲で「トレンド」になる前に十分に訓練されており、新しいツールや取引方法に関する知識を最新に保っていないことに注意してください-実際、私たち人間の多くは、新しいアイデアやプロセスに積極的に抵抗しています。「私はnn年間このようなプログラミングを行ってきましたが、最新の愚かな流行は気にしません!」


27
そのとおり。特にGUIプログラミングでは、可変オブジェクトは非常に便利です。
フロリアンサリホビッチ

23
単なる抵抗ではなく、多くの開発者が最新かつ最高のものを試してみたいと思うはずですが、これらの新しいプラクティスを適用できる平均的な開発者の環境では、どのくらいの頻度で新しいプロジェクトが生まれますか?不変の状態を試すためだけに趣味のプロジェクトを作成できる人はいません。
スティーブンエバーズ

29
これには2つの小さな注意点があります。(1)動くゲームキャラクターを取り上げます。Pointたとえば.NET のクラスは不変ですが、変更の結果として新しいポイントを作成するのは簡単であり、したがって手頃な価格です。不変のキャラクターのアニメーションは、「可動部分」を分離することで非常に安価にできます(ただし、はい、いくつかの側面は変更可能です)。(2)「大きなオブジェクトおよび/または複雑なオブジェクト」は非常に不変である可能性があります。多くの場合、文字列は大きく、通常は不変性の恩恵を受けます。私はかつて、複雑なグラフクラスを不変に書き換え、コードをより簡単で効率的にしました。そのような場合、変更可能なビルダーを持つことが重要です。
コンラッドルドルフ

4
@KonradRudolph、良い点、ありがとう。複雑なオブジェクトで不変性を使用することを除外するつもりはありませんでしたが、そのようなクラスを正しく効率的に実装することは簡単な作業ではなく、必要な追加の努力が常に正当化されるとは限りません。
ペテルトレック

6
状態とアイデンティティについて良い点を示します。これが、リッチヒッキー(Clojureの著者)がClojureで2つを分離した理由です。ガソリンの1/2タンクを搭載した車は、ガソリンの1/4タンクを搭載した車とは異なると主張することができます。それらは同じアイデンティティを持っていますが、同じではありません。現実の時間のすべての「目盛り」は、私たちの世界のすべてのオブジェクトのクローンを作成し、脳は単にこれらを共通のアイデンティティでつなぎ合わせます。Clojureには、時間を表す参照、アトム、エージェントなどがあります。また、実際の時間のマップ、ベクトル、リスト。
ティモシーボールドリッジ

130

あなたはすべて最も明白な答えを逃したと思います。命令型言語では可変性がデフォルトであるため、ほとんどの開発者は可変オブジェクトを作成します。私たちのほとんどは、デフォルトからコードを絶えず修正するよりも、より正確であるかどうかよりも、私たちの時間との関係で優れています。そして、不変性は他のどのアプローチよりも万能薬ではありません。いくつかのことが簡単になりますが、いくつかの答えがすでに指摘しているように、他のことははるかに難しくなります。


9
私が知っているほとんどの最新のIDEでは、ゲッターとセッターの両方を生成するのと同じくらいの労力で、ゲッターのみを生成します。それは追加のは事実ですがfinalconstその他には、余分な労力のビットを取る...あなたはコードテンプレート:-)を設定しない限り、
ペーテルTörök

10
@Pet それはまた、この種のものを落胆させます。
オノリオカテナッチ

5
これは、コミュニケーションと教育を強化することで克服できます。たとえば、既存のコードベースに実際に導入する前に、チームメイトに新しいコーディングスタイルについて話したりプレゼンテーションしたりすることをお勧めします。良いプロジェクトには、すべての(過去および現在の)プロジェクトメンバーが好むコーディングスタイルの単なる融合ではない共通のコーディングスタイルがあることは事実です。したがって、コーディングイディオムを導入または変更することは、チームが決定する必要があります。
ペテルトレック

3
@PéterTörök命令型言語では、可変オブジェクトがデフォルトの動作です。不変オブジェクトのみが必要な場合は、関数型言語に切り替えることをお勧めします。
エモリー

3
@PéterTörök不変をプログラムに組み込むことは、すべてのセッターをドロップすること以外の何ものでもないと仮定するのは少し単純です。プログラムの状態を段階的に変更する方法がまだ必要です。そのためには、ビルダー、またはポプシクルの不変性、または可変プロキシが必要です。これらはすべて、セッターをドロップするだけで約10億倍の労力を必要とします。
アサドSaeeduddin

49
  1. 可変性のための場所があります。 ドメイン駆動設計の原則は、何を変更可能にし、何を不変にする必要があるかをしっかりと理解することを提供します。考えてみると、オブジェクトの状態を変更するたびにそれを破壊し、再構成する必要があるシステムや、それを参照したすべてのオブジェクトを考えることは現実的ではないことに気付くでしょう。複雑なシステムでは、これによりシステム全体のオブジェクトグラフを完全に消去して再構築することになります。

  2. ほとんどの開発者は、パフォーマンス要件が十分に重要であるため、並行性(または、情報提供者によって一般的に良い慣行と見なされる他の多くの問題)に集中する必要がある場合は何も作成しません。

  3. 不変オブジェクトでは、双方向の関係があるなど、単純にできないこともいくつかあります。1つのオブジェクトに関連付け値を設定すると、そのIDが変更されます。したがって、他のオブジェクトに新しい値を設定すると、同様に変更されます。問題は、最初のオブジェクトの参照が無効になったことです。これは、参照を使用してオブジェクトを表す新しいインスタンスが作成されたためです。これを継続すると、無限の回帰が発生します。あなたの質問を読んだ後、私は少しのケーススタディをしました、それはどのように見えるかです。不変性を維持しながらそのような機能を許可する代替アプローチはありますか?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    

2
@AndresF。、不変オブジェクトのみを使用して双方向の関係を持つ複雑なグラフを維持する方法について異なる見解を持っているなら、それを聞きたいです。(コレクション/配列がオブジェクトであることに同意できると思います)
-smartcaveman

2
@AndresF。、(1)私の最初の発言は普遍的ではなかったので、偽りではありませんでした。実際にアプリケーションの開発で非常に一般的な特定のケースでそれがどのように真実であるかを説明するために、実際にコード例を提供しました。(2)一般に、双方向の関係には可変性が必要です。Javaが犯人だとは思わない。私が言ったように、私はあなたが提案する建設的な代替案を評価したいと思いますが、この時点であなたのコメントは「私がそう言ったので間違っています」のように聞こえます。
smartcaveman

14
@smartcaveman(2)についても、私は同意しません。一般に、「双方向の関係」は、可変性に直交する数学的概念です。通常Javaで実装されるように、可変性が必要です(その点については同意しました)。ただし、別の実装を考えることができますRelationship(a, b)。コンストラクタを使用した2つのオブジェクト間の関係クラス。関係を作成する時点で、両方のエンティティab既に存在し、関係自体も不変です。このアプローチがJavaで実用的だとは言いません。可能だというだけです。
アンドレスF.

4
@AndresF。、だから、あなたが言っていることに基づいて、If RRelationship(a,b)andでありab不変である場合、への参照ab保持もしませんR。これが機能するには、参照を別の場所(静的クラスなど)に保存する必要があります。あなたの意図を正しく理解していますか?
smartcaveman

9
Chthulhuが遅延を介して指摘するように、不変データの双方向の関係を保存することができます。これを行う1つの方法を次に示し
トーマスエディング

36

私は「純粋に機能的なデータ構造」を読んでいますが、可変オブジェクトを使用して実装する方がはるかに簡単なデータ構造がかなりあることを実感しました。

バイナリ検索ツリーを実装するには、毎回新しいツリーを返す必要があります。新しいツリーでは、変更された各ノードのコピーを作成する必要があります(変更されていないブランチは共有されます)。挿入関数の場合、これはそれほど悪くはありませんが、私にとっては、削除と再バランスの作業を始めたとき、物事はすぐにかなり非効率になりました。

もう1つ理解しておくべきことは、オブジェクト指向コードを何年も書くことができ、同時実行の問題を公開する方法でコードが実行されない場合、共有可能な可変状態がどれほどひどいものであるかを実際に認識しないことです。


3
これは岡崎の本ですか?
メカニカルカタツムリ

うん。ちょっと乾いたが、良い情報のトン...
ポールSanwald

4
おかしい、私はいつも岡崎の赤/黒の木がとても簡単だと思っていました。10行程度。最大の利点は、実際に古いバージョンを保持したい場合です。
トーマスエーレ

最後の文は、おそらく過去に真となっているが、それは現在のハードウェアなどの傾向を考えると、将来的には真のままになり、その明確ではないこと
JK。

29

私の観点から、それは認識の欠如です。他の既知のJVM言語(Scala、Clojure)を見ると、変更可能なオブジェクトはコード内でめったに見られないため、シングルスレッドでは不十分なシナリオでそれらを使用し始めます。

私は現在Clojureを学んでおり、Scala(Javaでも4年以上)で少し経験があり、状態を認識するためにコーディングスタイルが変わります。


おそらく、「人気のある」よりも「知られている」のがより良い選択の言葉でしょう。
デン

はい、そうです。
フロリアンサリホビッチ

5
+1:同意します。ScalaとHaskellを学んだ後は、Javaでfinalを使用し、どこでもC ++でconstを使用する傾向があります。可能な場合は不変オブジェクトも使用しますが、可変オブジェクトは依然として非常に頻繁に必要ですが、不変オブジェクトを使用できる頻度は驚くべきものです。
ジョルジオ

5
私はこのコメントを2年半後に読みましたが、不変を支持して意見が変わりました。私の現在のプロジェクト(Python)では、可変オブジェクトを使用することはほとんどありません。永続データでも不変です。一部の操作の結果として新しいレコードを作成し、不要になった古いレコードを削除しますが、ディスク上のレコードを更新することはありません。言うまでもなく、これにより、コンカレントマルチユーザーアプリケーションの実装と保守がこれまでよりはるかに簡単になりました。
ジョルジオ14

13

私は1つの主要な要因が無視されてきたと思う:Javaの豆は、オブジェクトを変異、および(特にソース考慮して)の特定のスタイルに大きく依存しているかなりの数の人々が取るように見えること(あるいはなどの)標準的な例どのようにすべてのJava書かれるべきです。


4
+1、ゲッター/セッターパターンは、最初のデータ分析後に何らかのデフォルトの実装として頻繁に使用されます。
Jaap

これはおそらく大きなポイントです...「それが他のすべての人がそれをやっている方法だから」...だから、それは正しいに違いない。「Hello World」プログラムの場合、おそらく最も簡単です。多数の可変プロパティによるオブジェクトの状態変更の管理...これは、「Hello World」の理解の深さよりも少し複雑です。20年後、20世紀のプログラミングの頂点は、構造をまったく持たないオブジェクトのすべての属性に対してgetXメソッドとsetXメソッドを書くこと(非常に退屈です)に非常に驚いています。100%の可変性を備えたパブリックプロパティへの直接アクセスからわずか1ステップです。
ダレルティーグ

12

私がキャリアで取り組んできたすべてのエンタープライズJavaシステムは、HibernateまたはJava Persistence API(JPA)を使用します。HibernateとJPAは、システムが変更可能なオブジェクトを使用することを基本的に規定しています。なぜなら、それらの全体的な前提は、データオブジェクトへの変更を検出して保存するからです。多くのプロジェクトにとって、Hibernateがもたらす開発の容易さは、不変オブジェクトの利点よりも魅力的です。

明らかに、可変オブジェクトはHibernateよりもずっと長い間存在しているため、Hibernateはおそらく可変オブジェクトの人気の元の「原因」ではないでしょう。可変オブジェクトの人気がHibernateの繁栄を可能にしたのかもしれません。

しかし、今日、多くのジュニアプログラマがHibernateまたは別のORMを使用してエンタープライズシステムに歯を磨くと、おそらく、可変オブジェクトを使用する習慣を身に付けることになるでしょう。Hibernateのようなフレームワークは、可変オブジェクトの人気を確立している可能性があります。


素晴らしい点。すべての人々にとってすべてのものであるために、そのようなフレームワークには選択肢がほとんどありませんが、最も一般的な分母に落とし、リフレクションベースのプロキシを使用し、柔軟性を得る/設定します。もちろん、これにより、状態遷移規則がほとんどないか、または残念ながらそれらを実装する一般的な手段を持つシステムが作成されます。私は多くのプロジェクトの後で、便宜性、拡張性、および正確性のために何がより良いかを完全に確信していません。私はハイブリッドだと思う傾向があります。動的ORMの良さ。ただし、どのフィールドが必須であり、どの状態変更が可能であるべきかについてのいくつかの定義があります。
ダレルティーグ

9

まだ言及されていない主要な点は、オブジェクトの状態を可変にすると、その状態をカプセル化するオブジェクトのアイデンティティを不変にすることができるということです。

多くのプログラムは、本質的に可変である実世界のものをモデル化するように設計されています。12:51 amに、変数(AllTrucksオブジェクト#451への参照)を保持すると仮定します。オブジェクト#451は、その瞬間(12:51 am)にフリートのすべてのトラックに含まれる貨物を示すデータ構造のルートです。BobsTruckオブジェクト#24601への参照を取得するために使用して、その時点(12:51 am)にボブのトラックにどの貨物が含まれているかを示すオブジェクトを指すことができます。12:52 amに、一部のトラック(ボブのトラックを含む)が積み降ろされ、データ構造が更新さAllTrucksれ、12:52amの時点で貨物がすべてのトラックにあることを示すデータ構造への参照が保持されます。

何が起こるのBobsTruck

各トラックオブジェクトの「cargo」プロパティが不変の場合、オブジェクト#24601は、ボブのトラックが午前12時51分に持っていた状態を永久に表します。BobsTruckオブジェクト#24601への直接参照を保持している場合、更新するコードAllTrucksがupdate BobsTruckにも発生しない限り、ボブのトラックの現在の状態を表すのをやめます。さらにBobsTruck、何らかの形式の可変オブジェクトに格納されていない限り、更新するコードが更新AllTrucksできる唯一の方法は、コードがそうするように明示的にプログラムされている場合です。

BobsTruckすべてのオブジェクトを不変に保ちながらボブのトラックの状態を観察できるようにしたい場合は、特定の時点でのBobsTruckAllTrucksまたは特定の時点での値を指定すると、ボブのトラックの状態を取得する不変の関数を使用できますその時。1つは不変の関数のペアを保持することもできます。1つは上記のようになり、もう1つは艦隊状態と新しいトラック状態への参照を受け入れ、新しい艦隊状態への参照を返します。ボブのトラックが新しい状態になることを除いて、古いものと一致しました。

残念ながら、ボブのトラックの状態にアクセスするたびにそのような機能を使用しなければならないことは、かなり面倒で扱いにくいものになる可能性があります。別のアプローチは、オブジェクト#24601が常にそして永遠に(誰かがそれへの参照を保持している限り)ボブのトラックの現在の状態を表すということです。ボブのトラックの現在の状態に繰り返しアクセスするコードは、時間のかかる関数を毎回実行する必要はありません。オブジェクト#24601がボブのトラックであることを確認するために1回だけルックアップ関数を実行できます。ボブのトラックの現在の状態を見たいときはいつでもそのオブジェクトにアクセスします。

機能的なアプローチは、シングルスレッド環境、またはスレッドがデータを変更するのではなく単に監視するだけのマルチスレッド環境で利点がないわけではないことに注意してください。に含まれるオブジェクト参照をコピーするオブザーバースレッドAllTrucksそして、それによって表されるトラックの状態を調べると、参照を取得した瞬間のすべてのトラックの状態が表示されます。オブザーバスレッドは、いつでも新しいデータを確認したい場合に、参照を再取得できます。一方、フリートの状態全体を単一の不変オブジェクトで表すと、2つのスレッドが異なるトラックを同時に更新する可能性がなくなります。各スレッドが独自のデバイスに残されると、新しいフリート状態オブジェクトが生成されるためです。トラックの新しい状態と他のすべての古い状態。各スレッドが、変更されていない場合にのみCompareExchange更新に使用AllTrucksし、失敗に応答する場合、正確性が保証される場合がありますCompareExchange状態オブジェクトを再生成して操作を再試行しますが、複数のスレッドが同時書き込み操作を試みると、一般にすべての書き込みが単一のスレッドで行われた場合よりもパフォーマンスが低下します。このような同時操作を試行するスレッドが多いほど、パフォーマンスが低下します。

個々のトラックオブジェクトが可変であるが、不変のIDを持っている場合、マルチスレッドシナリオはよりクリーンになります。特定のトラックで一度に動作できるスレッドは1つだけですが、異なるトラックで動作するスレッドは干渉なしで動作できます。不変オブジェクトを使用する場合でも、このような動作をエミュレートする方法があります(たとえば、「AllTrucks」オブジェクトを定義して、XXXに属するトラックの状態をSSSに設定するには、「As of [Time] [XXX]に属するトラックの状態は[SSS]になり、他のすべての状態は[古い値のAllTrucks] "になります。このようなオブジェクトの生成は、競合が存在する場合でも十分に高速です。CompareExchangeループは長くかかりません。一方、このようなデータ構造を使用すると、特定の人のトラックを見つけるのに必要な時間が大幅に増加します。不変のIDを持つ可変オブジェクトを使用すると、その問題を回避できます。


8

正しいか間違っているかはありません、それはあなたが好むものに依存します。一部の人々が、あるパラダイムを別のパラダイムよりも優先し、1つのデータモデルを別のパラダイムよりも優先する言語を好む理由があります。それはあなたの好みと達成したいものに依存します(そして、一方の熱心なファンを疎外することなく両方のアプローチを簡単に使用できることは、いくつかの言語が求めている聖杯です)。

あなたの質問に答えるための最良かつ最も速い方法は、不変対可変の長所と短所に立ち向かうことだと思います。


7

:このブログの記事をチェックhttp://www.yegor256.com/2014/06/09/objects-should-be-immutable.htmlを。不変オブジェクトが可変よりも優れている理由を要約しています。引数の短いリストは次のとおりです。

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

私が理解している限り、人々はOOPと命令型手続き型プログラミングを混合しているため、人々は可変オブジェクトを使用しています。


5

Javaでは、不変オブジェクトには、オブジェクトのすべてのプロパティを取得するコンストラクターが必要です(または、コンストラクターが他の引数またはデフォルトからプロパティを作成します)。これらのプロパティにはfinalのマークを付ける必要があります。

これには主にデータバインディングに関係する4つの問題があります。

  1. Javaコンストラクターのリフレクションメタデータは、引数名を保持しません。
  2. Javaコンストラクター(およびメソッド)には名前付きパラメーター(ラベルとも呼ばれる)がないため、多くのパラメーターと混同されます。
  3. 別の不変オブジェクトを継承する場合、コンストラクターの適切な順序を呼び出す必要があります。これは、フィールドの1つをファイナル以外の状態のままにしておくという点では、かなり注意が必要です。
  4. ほとんどのバインディングテクノロジー(Spring MVCデータバインディング、Hibernateなど)は、引数なしのデフォルトコンストラクターでのみ動作します(これは、注釈が常に存在するわけではないためです)。

#1と#2のようなアノテーションを使用して@ConstructorProperties、別の可変ビルダーオブジェクト(通常は流fluent)を作成して不変オブジェクトを作成できます。


5

パフォーマンス最適化の利点について誰も言及していないことに驚いた。言語に応じて、コンパイラは不変データを処理するときに最適化を行うことができます。これは、データが決して変更されないことを知っているためです。あらゆる種類のものがスキップされるため、パフォーマンスが大幅に向上します。

また、不変オブジェクトは、状態バグのクラス全体をほぼ排除します。

難しく、すべての言語に適用されず、ほとんどの人が命令型コーディングを教えられたため、それほど大きくありません。

また、私はほとんどのプログラマーが彼らの箱に満足しており、しばしば彼らが完全に理解していない新しいアイデアに抵抗することを発見しました。一般に、人々は変化を好まない。

また、ほとんどのプログラマーの状態が悪いことを忘れないでください。野外で行われるほとんどのプログラミングはひどいものであり、その原因は理解と政治の欠如です。


4

なぜ人々は強力な機能を使用するのですか?なぜ人々はメタプログラミング、怠、または動的なタイピングを使用するのですか?答えは便利です。可変状態はとても簡単です。その場で更新するのはとても簡単で、不変の状態が可変の状態よりも生産的であるプロジェクトのサイズのしきい値は非常に高いため、しばらくの間は選択肢がありません。


4

プログラミング言語は、コンピューターによる実行用に設計されています。CPU、RAM、キャッシュ、ディスクなど、コンピューターのすべての重要な構成要素は変更可能です。それらが(BIOS)でない場合、それらは本当に不変であり、新しい不変オブジェクトを作成することもできません。

したがって、不変オブジェクトの上に構築されたプログラミング言語は、その実装に表現のギャップがあります。そして、Cなどの初期の言語では、それは大きな障害でした。


1

可変オブジェクトがなければ、状態はありません。確かに、これを管理でき、オブジェクトが複数のスレッドから参照される可能性がある場合、これは良いことです。しかし、プログラムはかなり退屈になるでしょう。多くのソフトウェア、特にWebサーバーは、データベース、オペレーティングシステム、システムライブラリなどの可変性を無効にすることで、可変オブジェクトの責任を回避します。実際問題として、プログラマーは可変性の問題から解放され、Web(およびその他)手頃な開発。しかし、可変性はまだそこにあります。

一般に、3つのタイプのクラスがあります。通常の非スレッドセーフクラスは、慎重に保護および保護する必要があります。自由に使用できる不変クラス。そして、自由に使用できるが、細心の注意を払って書かなければならない、可変でスレッドセーフなクラス。最初のタイプは面倒なタイプで、最悪のタイプは3番目のタイプと考えられるものです。もちろん、最初のタイプは書きやすいものです。

私は通常、非常に慎重に見なければならない通常の可変クラスがたくさんあることになります。マルチスレッドの状況では、致命的な抱擁を回避できる場合でも、必要な同期によりすべてが遅くなります。だから私は通常、可変クラスの不変のコピーを作成し、それを使用できる人に引き渡します。元の変異が発生するたびに新しい不変のコピーが必要になるので、元のコピーが100個あることを想像してください。ガベージコレクションに完全に依存しています。

要約すると、複数のスレッドを使用していない場合、スレッドセーフではない可変オブジェクトは問題ありません。(ただし、マルチスレッド化はどこでも膨らんでいます-注意してください!)ローカル変数に制限するか、厳密に同期する場合は、安全に使用できます。他の人の実績のあるコード(DB、システムコールなど)を使用してそれらを回避できる場合は、そうしてください。あなたがいる場合することができます不変クラスを使用し、そうします。そして、一般的に、人々はマルチスレッドの問題に気付いていない、(賢明に)それらを恐れており、あらゆる種類のトリックを使用してマルチスレッドを回避しています(むしろ、他の場所に責任を押し付けている)と思います

PSとして、Javaのゲッターとセッターが手に負えなくなっていると感じています。これをチェックてください。


7
不変の状態はまだ状態です。
ジェレミー

3
@JeremyHeiler:確かに、しかしそれ変更可能なものの状態です。何も変化しない場合、状態は1つだけです。これは、状態がまったくないことと同じことです。
ラルフシャピン

1

多くの人が良い答えを持っていたので、あなたが触れたものは非常に注意深く、非常に真実であり、ここで言及されていないことを指摘したいと思います。

セッターとゲッターを自動的に作成することは、恐ろしく恐ろしいアイデアですが、手続き指向の人々がオブジェクト指向を自分の考え方に強制しようとする最初の方法です。セッターとゲッター、およびプロパティは、デフォルトではなく、必要なときにのみ作成する必要があります

実際、ゲッターはかなり定期的に必要ですが、セッターまたは書き込み可能なプロパティがコードに存在する唯一の方法は、オブジェクトが完全にインスタンス化された後にロックダウンされるビルダーパターンを使用することです。

多くのクラスは作成後に変更できますが、プロパティを直接操作することはできません-その代わりに、実際のビジネスロジックを含むメソッド呼び出しを通じてプロパティを操作するように求められるべきです(はい、セッターはほとんど同じです)プロパティを直接操作するもの)

現在、これは実際には「スクリプト」スタイルのコード/言語には適用されませんが、他の人のために作成し、長年にわたって繰り返し読むことを期待するコードに適用されます。Groovyをいじるのがとても好きで、ターゲットに大きな違いがあるので、私は最近その区別をし始めなければなりませんでした。


1

可変オブジェクトは、オブジェクトのインスタンス化後に複数の値を設定する必要がある場合に使用されます。

たとえば、6つのパラメーターを持つコンストラクターはありません。代わりに、セッターメソッドを使用してオブジェクトを変更します。

この例は、フォント、向きなどのセッターを備えたReportオブジェクトです。

簡単に言えば、オブジェクトに設定する状態がたくさんあり、非常に長いコンストラクタシグネチャを持つことは実用的ではない場合に、ミュータブルは便利です。

編集:Builderパターンを使用して、オブジェクトの状態全体を作成できます。


3
これは、インスタンス化後に可変性と変更が複数の値を設定する唯一の方法であるかのように提示されます。完全を期すために、Builderパターンは同等以上の強力な機能を提供し、不変性を犠牲にする必要がないことに注意してください。new ReportBuilder().font("Arial").orientation("landscape").build()
gnat

1
「非常に長いコンストラクタシグネチャを持つことは実用的ではありません。」:パラメータをより小さなオブジェクトにグループ化し、これらのオブジェクトをパラメータとしてコンストラクタに渡すことができます。
ジョルジオ

1
@cHao 10個または15個の属性を設定する場合はどうなりますか?また、という名前のメソッドがをwithFont返すのは良くないようですReport
Tulainsコルドバ

1
10または15の属性を設定する限り、(1)Javaがそれらすべての中間オブジェクトの構築を排除する方法を知っていて、(2)再び名前が標準化されていれば、そうするためのコードは厄介ではありません。不変性の問題ではありません。Javaがそれをうまく行う方法を知らないという問題です。
cHao

1
@cHao 20個の属性の呼び出しチェーンを作成するのはいです。glyいコードは品質の低いコードになる傾向があります。
Tulainsコルドバ

1

可変オブジェクトの使用は命令型思考に起因すると考えています。可変変数の内容を段階的に変更して結果を計算します(副作用による計算)。

機能的に考える場合、関数を適用し、古い値から新しい値を作成することにより、不変の状態を持ち、システムの後続の状態を表現する必要があります。

機能的アプローチはよりクリーンで堅牢ですが、コピーのために非常に非効率になる可能性があるため、増分的に変更する共有データ構造にフォールバックする必要があります。

私が最も妥当だと思うトレードオフは、不変オブジェクトから始めて、実装が十分に速くない場合は可変オブジェクトに切り替えることです。この観点から、可変オブジェクトを最初から体系的に使用することは、ある種の時期尚早な最適化と見なすことができます。最初からより効率的な(ただし、理解とデバッグが難しい)実装を選択します。

では、なぜ多くのプログラマーが可変オブジェクトを使用するのですか?私見には2つの理由があります:

  1. 多くのプログラマーは、命令型(手続き型またはオブジェクト指向)のパラダイムを使用してプログラミングする方法を学びました。したがって、可変性は計算を定義するための基本的なアプローチです。
  2. 多くのプログラマーはパフォーマンスをあまりにも早く懸念しますが、多くの場合、最初は機能的に正しいプログラムの作成に集中してから、ボトルネックを見つけて最適化する方が効果的です。

1
問題は速度だけではありません。可変型を使用しないと実装できない単純な便利な構造体が数多くあります。とりわけ、2つのスレッド間の通信には、少なくとも1つがデータを入力し、もう1つがそれを読み取ることができる共有オブジェクトへの参照が必要です。さらに、「このオブジェクトを取得し、Pの値を除いてそれと同じような新しいオブジェクトを作成し、次に、 Q "以外はまったく同じです。
supercat

1
私はFPの専門家ではありませんが、AFAIK(1)スレッド通信は、1つのスレッドで不変の値を作成し、別のスレッドでそれを読み取ることで実現できます(繰り返しますが、これはアーランアプローチです)(2)プロパティを設定するための表記法プロパティの設定結果が新しい不変レコードであるため、セマンティクスのみが変更されます(たとえば、HaskellのsetPropertyレコードの値はJavaのrecord.setProperty(value)に対応します)。
ジョルジオ

1
「どちらもデータを入力し、もう一方がデータを読み取ることができる共有オブジェクトへの参照を持っている必要があります」:より正確には、オブジェクトを不変(C ++のすべてのメンバーconstまたはJavaのfinal)にし、すべてのコンテンツをコンストラクタ。次に、この不変オブジェクトがプロデューサースレッドからコンシューマスレッドに引き渡されます。
ジョルジオ

1
(2)不変状態を使用しない理由として、すでにパフォーマンスを挙げています。(1)に関しては、もちろん、スレッド間の通信を実装するメカニズムには変更可能な状態が必要ですが、これをプログラミングモデルから隠すことができます。たとえば、アクターモデル(en.wikipedia.org/wiki/Actor_model)を参照してください。したがって、下位レベルで通信を実装するために可変性が必要な場合でも、不変オブジェクトを前後に送信するスレッド間で通信する上位抽象化レベルを設定できます。
ジョルジオ

1
私はあなたを完全に理解しているとは思いませんが、すべての値が不変である純粋な関数型言語でさえ、プログラムの状態、つまり現在のプログラムスタックと変数バインディングがあります。しかし、これは実行環境です。とにかく、チャットでこれについて話し合ってから、この質問のメッセージを整理することをお勧めします。
ジョルジオ

1

あなたがJavaについて尋ねていることは知っていますが、objective-cでは常に可変対不変を使用しています。不変配列NSArrayと可変配列NSMutableArrayがあります。これらは、正確な使用を処理するために最適化された方法で具体的に記述された2つの異なるクラスです。配列を作成し、その内容を変更する必要がない場合は、NSArrayを使用します。NSArrayは、より小さいオブジェクトであり、可変配列に比べて処理速度がはるかに高速です。

したがって、不変のPersonオブジェクトを作成する場合、必要なのはコンストラクタとゲッターだけです。したがって、オブジェクトは小さくなり、メモリの使用量が少なくなり、プログラムが実際に高速になります。作成後にオブジェクトを変更する必要がある場合、新しいオブジェクトを作成する代わりに値を変更できるように、可変のPersonオブジェクトの方が適しています。

そのため、オブジェクトで何をするつもりかによって、可変と不変を選択することは、パフォーマンスに関して大きな違いを生む可能性があります。


1

ここで挙げた他の多くの理由に加えて、問題は主流の言語が不変性をうまくサポートしていないことです。少なくとも、constやfinalなどの追加のキーワードを追加する必要があり、多くの多くの引数またはコードの長いビルダーパターンを持つ判読できないコンストラクターを使用する必要があるという点で、不変性に対してペナルティが科されます。

これは、不変性を念頭に置いて作成された言語でははるかに簡単です。オプションで名前付き引数を使用してクラスPersonを定義し、属性が変更されたコピーを作成するために、このScalaスニペットを検討してください。

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

したがって、開発者に不変性を採用したい場合、これが、より現代的なプログラミング言語への切り替えを検討する理由の1つです。


ポイントが作られ、前24件の答えで説明した上で、これはかなりのものを追加していないようだ
ブヨ

@gnatこれまでの回答のうち、ほとんどの主流言語が不変性を適切にサポートしていないと主張するものはどれですか?私はその要点が述べられていないだけだと思います(私はチェックしました)が、これは非常に重要な障害です。
ハンスピーターシュトルー14

この1例については、Javaでこの問題を説明する深さになります。そして、少なくとも3件の他の回答間接的に関連する
ブヨ

0

不変とは、値を変更できないことを意味し、可変とは、プリミティブとオブジェクトの観点から考えると値を変更できることを意味します。オブジェクトは、型に組み込まれているという点でJavaのプリミティブとは異なります。プリミティブは、int、boolean、voidなどの型で構築されます。

多くの人々は、プリミティブやオブジェクト変数の前に最終修飾子が付いている変数は不変であると考えていますが、これは必ずしも正しくありません。したがって、finalは、変数に対して不変であることをほとんど意味しません。コードサンプルについては、次のリンクをご覧ください。

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}

-1

適切な理由は、インターフェイスウィンドウのような「実際の」可変の「オブジェクト」をモデル化することだと思います。誰かが貨物港の操作を制御するソフトウェアを作成しようとしたときにOOPが発明されたことを読んだことを漠然と覚えています。


1
OOPの起源についてこれを読んだ場所はまだ見つかりませんが、ウィキペディアよると、大型コンテナ船会社OOCLの統合地域情報システムはSmalltalkで書かれています。
アレクセイ14年

-2

Javaでは、多くの場合、可変オブジェクトが必要です。たとえば、ビジターまたは実行可能ファイルで何かをカウントまたは返す場合、マルチスレッドを使用しなくても最終変数が必要です。


5
final実際には、不変性のステップの約1/4です。あなたがそれについて言及する理由が見えない。
cHao

私の投稿を実際に読んでくれましたか?
ラルフH

実際に質問を読みましたか?:)それはまったく関係がありませんでしたfinal。この文脈でそれを持ち出すことはfinal、ある種の突然変異final防ぐことがまさにその目的であるとき、「ミュータブル」との本当に奇妙な混同を想起させます。(ところで、私のダウン票ではありません。)
cHao

ここにどこかに有効なポイントがないと言っているわけではありません。あなたはそれをあまりうまく説明していないと言っています。私はあなたがおそらくどこに行くのかを半ば見ていますが、あなたはさらに進む必要があります。そのまま、混乱しているように見えます。
cHao

1
可変オブジェクトが必要な場合は、実際にはほとんどありませ。可変オブジェクトを使用することで、コードがより明確になり、場合によってはより高速になる場合があります。しかし、デザインパターンの大部分は、基本的に、ネイティブにサポートしていない言語の関数型プログラミングの半ば演技にすぎないと考えてください。それの楽しい部分は、FPは非常に限られた場所(つまり、副作用が発生する必要がある場所)でのみ可変性を必要とし、それらの場所は一般に数、サイズ、およびスコープが厳しく制限されています。
cHao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.