質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

Q&A

解決済

6回答

5970閲覧

オブジェクト指向と状態遷移・不変性

退会済みユーザー

退会済みユーザー

総合スコア0

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

4グッド

5クリップ

投稿2016/03/12 23:36

編集2016/03/13 00:11

しばしば不変性と可変性という言葉に出会います.オブジェクトが内部状態を変えない不変オブジェクトは,同期が不要,テストが容易であるなどのメリットが示されていました.設計としても,不変オブジェクトならば,メソッドが最小単位で仕事を行いやすいですね.

しかしながら,実世界に存在するものは,様々な状態に変化します.例えばサイコロは出目という状態を持ち,人に投げられることで,その状態が変化します.JavaのBigIntegerのように,演算結果を新しいインスタンスによって返すような不変オブジェクト設計をサイコロに適用すると,サイコロが転がって新しいサイコロを作るのか?と納得がいきません.今あるものの状態が変化すべきという点で,可変オブジェクトは納得がいきます.不変オブジェクト,可変オブジェクト,どちらが適切に実世界を切り取っていると考えますか.

みなさまの様々なご意見いただければと思います.よろしくお願いします.

java

1final class NonStateDice { 2 private final Random random = SecureRandom.getInstanceStrong(); 3 int roll() { 4 return random.nextInt(6) + 1; 5 } 6} 7 8final class MutableDice { 9 private final Random random = SecureRandom.getInstanceStrong(); 10 private int roll; 11 void roll() { 12 roll = random.nextInt(6) + 1; 13 } 14 int show() { 15 return roll; 16 } 17} 18final class ImmutableDice { 19 private final Random random = SecureRandom.getInstanceStrong(); 20 private final int roll; 21 22 ImmutableDice(final int roll) { 23 this.roll = roll; 24 } 25 ImmutableDice roll() { 26 return new ImmutableDice(random.nextInt(6) + 1); 27 } 28 int show() { 29 return roll; 30 } 31}
Snsk, raccy, thesecret11, sho_cs👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

unau

2016/03/12 23:46

議論したいのは、オブジェクトが状態を持つか否か、ではなく、オブジェクトが持っている状態が変化するか否か、のように思いますが違いますか。ちょっと混同されているように見えますが。
退会済みユーザー

退会済みユーザー

2016/03/12 23:50

おっしゃる通りでした.修正いたします.オブジェクトは一般的に状態を持ち,その上で不変性の議論ですね.
guest

回答6

0

とても難しいテーマです。一度、関数型プログラミングを体験してみると答えが見えてくるかも知れません。

関数型プログラミングでは全てのデータやオブジェクトが不変です。そのため、状態遷移はモナドやFRPなどを使って実現する必要があり、命令型とは全く異なるコーディングスタイルが強いられます。内部状態が不変なオブジェクトをどのように扱うべきか、状態遷移をどう実現するか、ということが見えてくるでしょう。


さて、問題のサイコロですが、状態が不変なオブジェクトとして扱う場合、考え方から変える必要があります。もともとの考え方が「サイコロは現在の目という状態を持っている」というものでした。しかし、それでは状態がコロコロ変わっていきますので、不変オブジェクトとして扱えません。ではどうするかというと、サイコロと目の状態を分離します

現実のサイコロを考えて下さい。手に持っていたら現在の目も何もありません。あるのは1〜6を表す目を持っているとか、角が丸い立方体であるとか、ガラスでできているとか、不変なものです。サイコロを振ったときに、何かしらの目が出ます。その目はサイコロが持っている状態ではなく、振った結果という状況の一枚絵です。つまり、この振った結果を写真で取ってしまったようなものをサイコロとは別のオブジェクトとして扱えばいいのです。

Java

1import java.util.Random; 2import java.security.SecureRandom; 3import java.security.NoSuchAlgorithmException; 4public class Dice { 5 private final Random random; 6 public Dice() throws NoSuchAlgorithmException { 7 this.random = SecureRandom.getInstanceStrong(); 8 } 9 Dice.Roll roll() { 10 return new Roll(random.nextInt(6) + 1); 11 } 12 public class Roll { 13 private final int roll; 14 private Roll(int roll) { 15 this.roll = roll; 16 } 17 public int show() { 18 return this.roll; 19 } 20 } 21 22 public static void main(String[] args) throws NoSuchAlgorithmException { 23 Dice dice = new Dice(); 24 Dice.Roll roll1 = dice.roll(); 25 Dice.Roll roll2 = dice.roll(); 26 System.out.println("一回目は" + roll1.show() + "で、二回目は" + 27 roll2.show() + "です。"); 28 } 29}

Diceがサイコロ、Dice.Rollがサイコロの目の状態です。もし、サイコロが状態を持っていた場合、二回振った時点で一回目の結果は失われます。しかし、一回目の結果を状態のオブジェクトとして取得するようにしておけば、その後に何回振ったとしても一回目の結果は変わりません。サイコロ自体も振った結果という状態も、両方とも不変オブジェクトとして扱えば、過去の結果を失わずに済むという利点が生まれます。

【!注意!】
厳密には、上の例のDiceは不変オブジェクトではありません。なぜなら、randomという可変オブジェクトをフィールドとして持つからです。乱数も不変オブジェクトとして扱う場合は、それこそモナドとか必要になってきますし、関数型プログラミングをあまり考慮していないJavaでは難しいため、Scala等の他言語でないと厳しいと思います。

投稿2016/03/13 02:54

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/03/13 09:57 編集

ご回答ありがとうございます. 「サイコロを振ったときに、何かしらの目が出ます。その目はサイコロが持っている状態ではなく、振った結果という状況の一枚絵です。」 非常に分かりやすかったです.可変性の分割によって不変にすることを目指すのですね. ただ,もし仮に前の出目に影響される特殊なサイコロがあった場合には,出目の状態をサイコロオブジェクトから切り取ってしまうのは,やり過ぎなのかなとも感じました. --- Dice.Rollクラスは必要であると感じつつも,質問では避けておりました.分割した方が適切ですね.ありがとうございます. --- 言われてみるとRandomが不変ではないのはその通りですね.乱数を使わざるを得ない以上,このDiceクラスは不変にはできませんね. 関数型言語難しそうです.Haskellに少しでも触ってみようと思います. ご回答ありがとうございました.
guest

0

ベストアンサー

可変オブジェクトは納得がいきます.不変オブジェクト,可変オブジェクト,どちらが適切に実世界を切り取っていると考えますか.

可変オブジェクトと不変オブジェクトでは実世界の切り取り方が違います。
可変オブジェクトで東京を表現する場合、東京の人口は増えたり減ったりするので、可変オブジェクトで表現するのは適切で不変オブジェクトで表現するのは不適切でしょう。

一方で、不変オブジェクトは歴史(変化のスナップショット)を表現します。不変オブジェクトの世界では今の東京と一秒後の東京は別ものです。江戸時代の江戸(東京)の人口は100万人ほどだったそうですが、これを今の1300万人に書き換えられたらおかしいでしょう。

切り取り方が違うだけで、どちらも適切に実世界を切り取っていると思います。


それはそうと、ある種のプログラマが不変オブジェクトを好むのはバグを作り込みにくいからであって、
仮にめちゃめちゃ実世界に沿っていたとしても、バグを作りこみやすければ使わないと思います。
(例えば、本物の自然言語でプログラムが書けるとか)

投稿2016/03/15 09:46

hello-world

総合スコア1342

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/03/15 22:35

ご回答ありがとうございます. 「可変オブジェクトと不変オブジェクトでは実世界の切り取り方が違います。~一方で、不変オブジェクトは歴史(変化のスナップショット)を表現します。」 この言い換えが大変分かりやすかったです.切り取り方が違うだけというのも非常に納得がいきました. --- 不変性はプログラマ,設計者の好みの問題になりますね.可変なものは共有に気を使わなきゃいけないので,少人数開発で情報共有が万全なら良いのかも知れませんね.これも実用性を考えての結論になるのでしょうね. ご回答ありがとうございました.
guest

0

こんにちは。

不変オブジェクトでググると、コンストラクト時に値が決まり、以降変更できないクラスのことを不変オブジェクト(不変クラス?)と呼ぶようですね。
確かにテストしやすい等メリットは多々ありますが、そもそも変更出来なきゃいけないものには適用できないです。無理やり適用して現実からかけ離れたものを設計すると、メンテナンス性がかなり落ちますし。
ですので、現実でも事実上変化しないものをモデル化するのに不変オブジェクトを使うのが良いのではないでしょうか?

でも、そのようなものって、大抵は定数で事足りていると思います。
何故にこんなに「不変オブジェクト」をことさら取り上げるのかなと思って読み進めると、Wikipediaにイミュータブルがありました。
この説明を読むと、Javaにはconstなパラメータが存在しない(final修飾してもメンバは変更できてしまう...) から、クラス設計段階で変更不可とし不用意な変更を防ぐことを狙っているっぽいです。
ネズミを撃つのに大砲を持ってくるような対策のように思えます。

いろいろやっている内にC#にはなぜconstが無いのか?も出てきました。ちょっと面白い議論をしています。

投稿2016/03/13 02:25

編集2016/03/13 02:28
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/03/13 09:49 編集

ご回答ありがとうございます. 「そもそも変更出来なきゃいけないものには適用できないです。無理やり適用して現実からかけ離れたものを設計すると、メンテナンス性がかなり落ちますし。 」 これは,全くその通りだと思います.メンテナンス性のためのプログラミングテクニックですね. しかし,現実に不変なものって存在するのでしょうか.電車のつり革,人の履く靴など現実世界に存在するものすべては少なくとも位置情報を持っていなくてはいけませんね.(位置情報をオブジェクト自身が持っているのかは議論の余地がありますが) この辺をいかにして切り分けるかが課題ですね.もう少し自分で検討していきたいと思います. --- 「ネズミを撃つのに大砲を持ってくるような対策のように思えます。」 言葉は悪いですが,まさにそのネズミを殲滅することが,信頼性設計の最終目標ですね. --- const自体は存じており,非常に強力な制約だと思います.C#はUnityをかじった程度で,なぜconstがないのかという議論にはなんとも言えませんが,C#ではどちらかといえば可変オブジェクトを許す向きなのでしょうかね. ご回答ありがとうございました.
Chironian

2016/03/13 10:51

> 現実に不変なものって存在するのでしょうか 現実では変化するものでも、そのオブジェクトが生きている間変化しないのならプログラム的には不変と考えて設計すると安全性を高めることができる機会が増えますね。 でも、不変にできるようなクラスは確かに多くはないように感じます。具体例を全く思いつきません。単なる定数よりは複雑で、しかし、コンストラクタだけで最終形にできる程度に単純でないといけないので、限定的だからかなと思います。 > 「ネズミを撃つのに大砲を持ってくるような対策のように思えます。」 ネズミは、「関数に渡したインスタンスを書き換えられないようにする」ことです。 それに対して、「クラスのインスタンスを全て書き換えられないようにする」という過剰な手段なのでこのように表現しました。 先にも書きましたが、小回りが効かなすぎるので、役に立つ場面はあまり多くはないだろうと感じます。
退会済みユーザー

退会済みユーザー

2016/03/14 00:34

「現実では変化するものでも、そのオブジェクトが生きている間変化しないのならプログラム的には不変と考えて設計すると安全性を高めることができる機会が増えますね。」 プログラムの生存期間に合わせて,オブジェクトの一部を切り取るとそのようにできますね. サイコロの目,画像などのように,実体を持ちにくい,人間が扱いやすいように切り取られた,抽象化が施されたオブジェクトに限定されそうです. --- 「ネズミは、「関数に渡したインスタンスを書き換えられないようにする」ことです。 それに対して、「クラスのインスタンスを全て書き換えられないようにする」という過剰な手段なのでこのように表現しました。」 これは誤解しておりました.失礼しました.確かに過剰ですね.インスタンスに対して書き換え不可能にするという点では,Collections.unmodifiableなど,書き換えの際に例外を投げるラッピングもありますね.しかしながら,このunmodifiableラッピング,ネズミ取りを要所要所で実装しておくことは,クラス数が増加するので少々手間ですね. その手間を考慮すると,最初から不変クラスを,大砲を持ち出してしまえってことになっているのかもしれませんね.
Chironian

2016/03/14 01:55

「Collections.unmodifiable」は初めて聞きましたが、list等のコレクションへの追加/削除等ができないだけのような印象です。 その中の要素まで変更不可になってくれるのでしょうか? もしそうなら、それは不変インスタンスですね。関数内で不用意に変更することを防げます。不変クラスに比べると適用範囲が遥かに広いと思います。
退会済みユーザー

退会済みユーザー

2016/03/15 22:25

追加/削除など,リストの中身が書き換えられるのを防止します,リストの中身のオブジェクトの中身まではサポートしませんが.不変インスタンス,そのようなことも考えられるのですね. 貴重なコメントありがとうございました.
guest

0

オブジェクト指向の話をしているなら、内部に隠蔽されている値は、当然可変です。
不変にすると、それは実質的に定数になってしまいます。

あなたが書いたImmutableDiceクラスが定義しているのは、「ダイス」ではなく、「ダイスの値」です。
つまり、オブジェクトを定義したものではありません。
random変数とrollメソッドは、staticであるべきです。

投稿2016/03/13 01:20

Stripe

総合スコア2183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/03/13 23:46

ご回答ありがとうございます. 「不変にすると、それは実質的に定数になってしまいます。」 これはもっともですが,実質的に定数にすることが,不変オブジェクトというテクニックの目的かと思っております. --- 「あなたが書いたImmutableDiceクラスが定義しているのは、「ダイス」ではなく、「ダイスの値」です。 」 そのように読めてしまいますね.rollの戻り値はRollなどの別クラスを定義し,そのインスタンスを返すべきですね. --- 「つまり、オブジェクトを定義したものではありません。 random変数とrollメソッドは、staticであるべきです。」 例のソースコードのオブジェクト定義が甘いから,このImmutableDiceを作るなら,staticなrollメソッドを作るべきということですね. これは,Diceでなく,出目Rollを返すことで改善できそうです. ありがとうございました.
Stripe

2016/03/14 11:12

あなたは、本当にオブジェクト指向の話をしているのでしょうか? 「オブジェクト指向におけるオブジェクト」と「クラスのインスタンス」を混同しているのでは?
guest

0

サイコロでいえば、サイコロをn回振る合計を求める場合に、ふった値を書いておくか、合計を書き換えてゆくか、といった違いになると思います。

「実世界を切り取る」という話でいえば、発生したイベントを全部記録して、見るときは時系列に全部集計して見る仕組みだと不変オブジェクトの設計で、最終的な状態のみを持つ場合が可変オブジェクトの設計となります。

一日の最初にそろばんをクリアして、売り上げるごとにパチパチ足して、一日の終わりの状態が売上・・というのが可変。
売上を大福帳に書いていって、一日の最後に全部足して売上を数えるのが不変ですね。

不変だと精密ですけど、容量を食いますし計算も大変ですね。
たぶん、ハードディスクとCPU性能の値段が下がるとともに、正確・精密さが安価になって、不変オブジェクト設計のほうに動いてるように見えます。

投稿2016/03/13 01:12

thesecret11

総合スコア234

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/03/13 10:02

ご回答ありがとうございます. 「たぶん、ハードディスクとCPU性能の値段が下がるとともに、正確・精密さが安価になって、不変オブジェクト設計のほうに動いてるように見えます。」 そうですね.計算量的なコストはマシンパワーでどうにかして,設計のしやすさ,テストのしやすさに流れているのだと思います. 可変オブジェクトは,だれが触ったとかけんかになりそうですね.すべてのsetterにlogを付ければ,修正も簡単なんですかね.それよりはsetterを使わない,不変なものの方が信頼して使えますね. ありがとうございました.
thesecret11

2016/03/13 22:20

みなさんの回答みると、いろいろですね。 私は「テスト」や「世界を切り取る」とかいう話なら、モデル設計としての不変性だけで見るべきで、言語がどうだとかは関係ないと思いますけどね。コンピューターシステムである必要すらないですから。
guest

0

・目的による
・定義による
・要否による
としか答えられないです。

■目的
Dice1は同じダイスを何度も振ることができる
Dice2は一度振ったら目を確定し、出目を保存、同一のダイスは二度と振らせない

2人で同じダイスを使用し、結果を比較するなら
Dice1のroll()を受け取って比較に使えばいい。

過去に出たダイスの目を飾って鑑賞したいならDice2

■定義
Diceは何度も振ることが可能
値を振った直後に使用し値の保存を必要としない。ならDice1

振ったら最後、二度と同じDiceを振ることはできない。ならDice2

■要否
極端な例で状態を持つDice
静、動、出目、位置エネルギー、運動エネルギー、速度、加速度、xyz角度、回転速度、回転角度、傷etc.etc....
そこまで必要なの?

投稿2016/03/13 01:00

Ryo

総合スコア507

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Ryo

2016/03/13 01:09

ダイスで不変にするなら、面の数、各面の値でしょうか
退会済みユーザー

退会済みユーザー

2016/03/13 10:14

ご回答ありがとうございます.修正前の段階で,素早いご回答本当にありがとうございます. 目的意識は,何回でも振ることのできるダイスのモデル化であり,現実的な自動サイコロ振り機では,入れた角度に影響を受けることなどを考えていましたが,そもそもモデルとして不適当だったと思いました. モデル自体を検討したいと思います.現在の段階では,返り値を修正して,質問文中のNonStateDiceのような形が一番適切かと考えております. ご回答ありがとうございました.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問