並べ替え
浅見 幸宏さんのプロフィール写真

不当だと思います。

オブジェクト指向批判の文脈では優れた回答がありますので別の視点から。

だいたい、どの言語もそうですけど、歴史の長い言語は間違ってつけちゃった機能があります。だいたい、いろんな人のいうことを聞いて、海鮮全部丼、トッピング全部のせみたいに出来上がってます。

私の偏見ですが、企画する人、規格を作る人と実装する人が別々だと、奇々怪界な仕様になります。とりあえずつけておけって機能も盛り込まれます。

JavaScriptのwithとか。PHPのトレイトとか。

仕事人として正しい態度は、本当にそれは必要かを良く考えて、自分のプロジェクト用にサブセットを作ることを心がけています。PHPで言えばインプリメントも自作のものでは使いません。

時々、高齢者にワードを使わせると、文字修飾を全部使い切った文書をつくりますがあれみたいです。

自分に与えられた問題を効果的に解決するツールセットがあれば、それで良いという考え方って結構大事です。

なんですけど、その方式を開陳する時には格段の注意が必要です。

例えば、私は仮想サーバーとfreebsdとpkg、bashを愛用していて、これで構成管理を全てやってます。ベンダーロックを避けるこのやり方だと、リソースの仕入れを2–30%安くすることができます。それはビジネスの成功にはとても大事な要素です。bashとpkgを手動で導入したらあとは、全てbashのスクリプトでサ

不当だと思います。

オブジェクト指向批判の文脈では優れた回答がありますので別の視点から。

だいたい、どの言語もそうですけど、歴史の長い言語は間違ってつけちゃった機能があります。だいたい、いろんな人のいうことを聞いて、海鮮全部丼、トッピング全部のせみたいに出来上がってます。

私の偏見ですが、企画する人、規格を作る人と実装する人が別々だと、奇々怪界な仕様になります。とりあえずつけておけって機能も盛り込まれます。

JavaScriptのwithとか。PHPのトレイトとか。

仕事人として正しい態度は、本当にそれは必要かを良く考えて、自分のプロジェクト用にサブセットを作ることを心がけています。PHPで言えばインプリメントも自作のものでは使いません。

時々、高齢者にワードを使わせると、文字修飾を全部使い切った文書をつくりますがあれみたいです。

自分に与えられた問題を効果的に解決するツールセットがあれば、それで良いという考え方って結構大事です。

なんですけど、その方式を開陳する時には格段の注意が必要です。

例えば、私は仮想サーバーとfreebsdとpkg、bashを愛用していて、これで構成管理を全てやってます。ベンダーロックを避けるこのやり方だと、リソースの仕入れを2–30%安くすることができます。それはビジネスの成功にはとても大事な要素です。bashとpkgを手動で導入したらあとは、全てbashのスクリプトでサーバーを立ち上げます。サーバーをゼロから作るのは半年に1回ですから、使い方が5年ぐらい変わらないのがいいんです。Bashもpkgも枯れ切っていて、安定し切ってます。もちろんメンテナンスも出過ぎた真似をしないから、変なトラブルに巻き込ませないし、慎重な配慮が常に行き届いています。仕入れが安いのと、常にOSのレベルで問題を自己解決できるというのは、私にとってはとても重要です。

これを一般的に採用されてるDockerやRubyベースの構成管理ツールをこき下ろし、DevOpsの文脈で語り出すと、きっとBashおじさんとか言われるんでしょう。

話を言語に戻すと、機能を限定して使うのはそんなに奇天烈なテクニックではないと思います。Old Plain Cとかいう人もいます。

部分継承やインプリメントを使わないようにしたり、静的関数で済ませられないかとかは考えます。

自分独自のやり方を人に紹介する時には、もっと下から丁寧に説明する方が良いと思っています。

Kojima Tadashiさんのプロフィール写真

もちろん正当な反論もたくさんあるとは思いますが、今見れば、明らかに、不当な反論もたくさんあります。

まあ、そんな時代もあったな、ということでしょうね。

端的に言うと、staticおじさん批判が全盛だったころには、インターフェイスに対するプログラムやカプセル化といった、実現したいこと(目的)と、そのための手段が、区別されずに話されていた傾向があると思います。

例えば、

  • 「ポリモーフィズム」つまり、具象型ではなくて、インターフェイスに対してプログラ厶する、という目的と、その実現手段の一つにすぎない「継承」がゴッチャになって語られている。とくに、「継承」という手段は、つまり実装の再利用(≒コピペ)なので、デメリットも大きいですし。
  • 「カプセル化」、つまり、状態変数の影響範囲を局在化するという目的と、その実現手段の一つにすぎない、システム全体の状態をオブジェクト(インスタンス)単位に分割して管理するという手法、がゴッチャになって語られている。状態変数をオブジェクト単位に分割する手法は、考えられる分割方法(オブジェクト構成)が一つしかないのであればうまくいきますが、行いたい複数の処理ごとに、最適な分割方法(オブジェクト構成)が異なる場合(処理Aのためには、こういうオブジェクト構成が望ましいけど、処理Bのためには、別のオブジェクト構成が望ましい、など)には、あまり、うまくいきません。下手すると、あっとい

もちろん正当な反論もたくさんあるとは思いますが、今見れば、明らかに、不当な反論もたくさんあります。

まあ、そんな時代もあったな、ということでしょうね。

端的に言うと、staticおじさん批判が全盛だったころには、インターフェイスに対するプログラムやカプセル化といった、実現したいこと(目的)と、そのための手段が、区別されずに話されていた傾向があると思います。

例えば、

  • 「ポリモーフィズム」つまり、具象型ではなくて、インターフェイスに対してプログラ厶する、という目的と、その実現手段の一つにすぎない「継承」がゴッチャになって語られている。とくに、「継承」という手段は、つまり実装の再利用(≒コピペ)なので、デメリットも大きいですし。
  • 「カプセル化」、つまり、状態変数の影響範囲を局在化するという目的と、その実現手段の一つにすぎない、システム全体の状態をオブジェクト(インスタンス)単位に分割して管理するという手法、がゴッチャになって語られている。状態変数をオブジェクト単位に分割する手法は、考えられる分割方法(オブジェクト構成)が一つしかないのであればうまくいきますが、行いたい複数の処理ごとに、最適な分割方法(オブジェクト構成)が異なる場合(処理Aのためには、こういうオブジェクト構成が望ましいけど、処理Bのためには、別のオブジェクト構成が望ましい、など)には、あまり、うまくいきません。下手すると、あっという間に、誰も理解できない「スパゲッティ・オブジェクト・プログラム」ができあがります。

今は、当時に比べて、インターフェイスに対するプログラムや、カプセル化(状態変数の影響範囲の局在化)などの目的を達成する手段は一つではなくて、いろいろな手段があること、さらには、それぞれの手段のメリット・デメリットの比較、なんかについて、世間の理解が進んできた、ということだと思っています。

Quora Userさんのプロフィール写真

staticおじさんについて悪いのはラベリングです。マスコミは名前をつけて、ステレオタイプな偏見イメージを作り出します。そういうアオリが喝采を受けた時代でした。

自分の経験で言えば、staticメソッドはthisを参照できず、機能が少ないのだから、staticメソッドでもインスタンスメソッドでもできるthisに依存しない処理なら、機能の少ないstaticメソッドを選択すべきだ

と言ったら「えー!static」みたいな反応を受けたこともあり、苦い思いも正直ありますね。

価値観は文化圏の中で決まります。そして変わります。正直、自分もOOPおにいさんでした。バードランドメイヤー先生のオブジェクト指向入門に感銘を受け、無謀にもあの一冊だけでEiffel言語のサブセット実装に挑戦したものでした。多重継承時の仮想関数テーブルの配置方法を編みだせずに挫折しましたが。

思うこととして、関数型言語はOOPのある面を参考にしてもいるし、C++のテンプレートライブラリを参考にしたり、あるいは反面教師にもしているわけです。Haskellの型クラスはOOPの仮想テーブルを参考に発案されたとかですね。

なので私にとって両者は基本的に排他の関係にはありませんし、敵同士でもありません。良し悪しもなく特定の状況で特定の目的達成に役立つ度合いの違いだけです。

インスタンスメソッドは、オーバーライド定義しなければ第一引数にth

脚注

staticおじさんについて悪いのはラベリングです。マスコミは名前をつけて、ステレオタイプな偏見イメージを作り出します。そういうアオリが喝采を受けた時代でした。

自分の経験で言えば、staticメソッドはthisを参照できず、機能が少ないのだから、staticメソッドでもインスタンスメソッドでもできるthisに依存しない処理なら、機能の少ないstaticメソッドを選択すべきだ

と言ったら「えー!static」みたいな反応を受けたこともあり、苦い思いも正直ありますね。

価値観は文化圏の中で決まります。そして変わります。正直、自分もOOPおにいさんでした。バードランドメイヤー先生のオブジェクト指向入門に感銘を受け、無謀にもあの一冊だけでEiffel言語のサブセット実装に挑戦したものでした。多重継承時の仮想関数テーブルの配置方法を編みだせずに挫折しましたが。

思うこととして、関数型言語はOOPのある面を参考にしてもいるし、C++のテンプレートライブラリを参考にしたり、あるいは反面教師にもしているわけです。Haskellの型クラスはOOPの仮想テーブルを参考に発案されたとかですね。

なので私にとって両者は基本的に排他の関係にはありませんし、敵同士でもありません。良し悪しもなく特定の状況で特定の目的達成に役立つ度合いの違いだけです。

インスタンスメソッドは、オーバーライド定義しなければ第一引数にthisを取るstaticメソッドのシンタックスシュガーだし、クラス定義に縛られないダブルディスパッチ可能な仮想テーブルが欲しければ型クラスを作ってもよい

のです。

問題は単面的であることで、重要なのはラベリングではなく、大ざっぱで輪郭も定まらない「oop」なるカテゴリを追うのではなく、道具箱であるプログラミング言語

の言語機能個別にバラしてそれぞれ(継承、状態、仮想関数、モジュール化,バインディング、不変性と定数性..)の本質と効用を理解し、現実世界の制約含めメリデメで評価し組み合わせることだと思っています。たとえば莫大な画面数のERPシステムを作るとしたらJava/OOP以外の選択肢はまだありません。

脚注

Tanino Kenichiさんのプロフィール写真

んー、なんか文脈が間違って理解されてるような。

「staticおじさん」に反対する人のことを「オブジェクト指向擁護者」って言われると違和感しかありません。

そもそも「staticおじさん」の認識が間違ってるように思います。もともとは、Java言語を理解できないのに、Javaを否定する人のことを指してました。むしろプログラマと言うより、仕様書を書くだけのSEを指しているはずです。

プログラマ崩れで、SEと称して分厚い仕様書を書いている「だけ」人が、勉強もしないのに無知でJavaのことを悪く言っている、と言うようにとらえています。だからあんなに叩かれたのだろうと。

よって、「staticおじさん」は実質上プログラマではなく、勉強もしない、頭が固い、無能SEを指しているのであって、反対している人は、いわば「無能SE反対者」です。Java等の「オブジェクト指向擁護者」と言われるはおそらく心外です。

「staticおじさん」のいうJavaの評価が間違ってると言ってるのであって、Java「全体」の擁護をしているのではありません。

その文脈を間違ってはいけないと思います。

その文脈を間違えると、個人攻撃をしているように感じたり、あたかもJava(orオブジェクト指向)全体の擁護をしているように感じるのだと思います。

いわゆるITドカタと言われる、SE->下請けプログラマ(n次請け)の構造を前提に、SEが知らない

んー、なんか文脈が間違って理解されてるような。

「staticおじさん」に反対する人のことを「オブジェクト指向擁護者」って言われると違和感しかありません。

そもそも「staticおじさん」の認識が間違ってるように思います。もともとは、Java言語を理解できないのに、Javaを否定する人のことを指してました。むしろプログラマと言うより、仕様書を書くだけのSEを指しているはずです。

プログラマ崩れで、SEと称して分厚い仕様書を書いている「だけ」人が、勉強もしないのに無知でJavaのことを悪く言っている、と言うようにとらえています。だからあんなに叩かれたのだろうと。

よって、「staticおじさん」は実質上プログラマではなく、勉強もしない、頭が固い、無能SEを指しているのであって、反対している人は、いわば「無能SE反対者」です。Java等の「オブジェクト指向擁護者」と言われるはおそらく心外です。

「staticおじさん」のいうJavaの評価が間違ってると言ってるのであって、Java「全体」の擁護をしているのではありません。

その文脈を間違ってはいけないと思います。

その文脈を間違えると、個人攻撃をしているように感じたり、あたかもJava(orオブジェクト指向)全体の擁護をしているように感じるのだと思います。

いわゆるITドカタと言われる、SE->下請けプログラマ(n次請け)の構造を前提に、SEが知らないのに新しい技術(Java)を悪く言ってる、と言う文脈なので、このITドカタ構造を悪く言ってることになります。

ということで少なくとも、私は「staticおじさん」を評価する気があまりありません。勉強せずにJavaを勝手に評価し、しかも内容は間違ってます。

でも、オブジェクト指向が絶対だとも思ってません。そもそも技術は進歩するものです。

「staticおじさん」は自分の理屈が絶対だと信じていました。そしてその「自分の理屈が絶対」であることが主に批判されてます。そしてそれが主に馬鹿にされていたところです。技術の内容の話ではありません。

それに反対する人が、自分の理屈が絶対だと仮定するのはおかしいし、する必要もありません。よって「オブジェクト指向擁護者」って言われると、何かが違うと思います。そもそもオブジェクト指向が絶対だとか思ってませんので。

ということで、私から見ると不思議な文脈で理解されているようなので書いておきました。

Daisuke Sawadaさんのプロフィール写真

バカにするのは不当でしょう。

自分の中で確立した理論や考察があり、その上で技術者として相手の主張に対して技術面での理由をつけて全否定することがあるのは仕方ありません。その主張自体が正しいかどうかは第三者、あるいはその後の議論で決まることですから、あとで「ごめんなさい、間違っていました」と言うなら立派です。しかし、主張に対して否定できるのはその主張自体までであって、「老害」とか人格まで否定出来るものではないし、ましてや自分なりの理解もないのに「流行りの理論に従って強者につく」のは技術者のやることではありません。

オブジェクト指向プログラミング -- 1兆ドル規模の大失敗 という記事があります。そうなんでしょうか?に対するDaisuke Sawadaさんの回答

私も昔はOOP信者でした。this(self)って素晴らしい、継承考えた人天才、パターンしっかり覚えなくっちゃ、でした。GUIプログラミングと相性が良かったからというのもあるでしょう。でもその当時の私はクロージャも高階関数も理解していませんでした。staticでないと結局困る場面も体感します。OOPって実際は生産性や品質向上に対して強力無比な手法だったのだろうか?と再考し始めます。そして新しいプログラミング言語、たとえばSwiftやRustあたりを知ると、「OOPもパラダイムとして取り込んでるよ」と言いながらも過去の言語とは違っている

バカにするのは不当でしょう。

自分の中で確立した理論や考察があり、その上で技術者として相手の主張に対して技術面での理由をつけて全否定することがあるのは仕方ありません。その主張自体が正しいかどうかは第三者、あるいはその後の議論で決まることですから、あとで「ごめんなさい、間違っていました」と言うなら立派です。しかし、主張に対して否定できるのはその主張自体までであって、「老害」とか人格まで否定出来るものではないし、ましてや自分なりの理解もないのに「流行りの理論に従って強者につく」のは技術者のやることではありません。

オブジェクト指向プログラミング -- 1兆ドル規模の大失敗 という記事があります。そうなんでしょうか?に対するDaisuke Sawadaさんの回答

私も昔はOOP信者でした。this(self)って素晴らしい、継承考えた人天才、パターンしっかり覚えなくっちゃ、でした。GUIプログラミングと相性が良かったからというのもあるでしょう。でもその当時の私はクロージャも高階関数も理解していませんでした。staticでないと結局困る場面も体感します。OOPって実際は生産性や品質向上に対して強力無比な手法だったのだろうか?と再考し始めます。そして新しいプログラミング言語、たとえばSwiftやRustあたりを知ると、「OOPもパラダイムとして取り込んでるよ」と言いながらも過去の言語とは違っていることに気付きます。

あとになって「今の俺はこう考えています」と堂々と言うためには、常に自分が納得していることを主張しておくことです。

Yuji Kobayashiさんのプロフィール写真

そちらの事案についてはあまり歴史的な経緯をちゃんと追っていない素人なので、不当だったかどうかを語れる立場ではないですが、まあ炎上案件というものは概ね過剰で不当な傾向があるかと思います。

一方で、今拝見したところ、件の記事では冒頭に以下のような記載があります。

> staticを理解していない人のコードを見ると、いちいちインスタンス宣言しているので笑ってしまう。

内容の是非はともかく、ご本人が他人をバカにしているように見える書き方をされているので、そんなことを言われたらカチンとくる人がいるのは容易に想像できます。

もちろんだからといって周りがバカにしていいという訳ではないですが、上の一文がなくもう少し謙虚な書き方ができれば、また歴史は違うものになったのではないでしょうか。

Naoki Fujitaさんのプロフィール写真

プログラムに「スズメ」や「カラス」「ハト」が登場します。

「鳥」クラスが「飛ぶ」メソッドを持つとします、この仮定は一見自然で人間にもわかりやすい「リーダブル」な表明です。この前提を用いて継承を用いプログラムを構成することにしました。

さて3カ月後クライアントが「ペンギン」と「ニワトリ」を登場させるように仕様改定を要求してきました。残念なことにペンギンやニワトリは「跳ぶ」ことはあれど「飛ぶ」ことはできません。わずか2つの「反例」が追加されたがために、あなたは次の3つの選択をしなければなりません。

  1. ペンギン、ニワトリをプログラムに登場させないようにクライアントに交渉する
  2. ペンギン、ニワトリは鳥であるが、鳥クラスは継承しないことにした
  3. ペンギン、ニワトリは鳥クラスを継承し、flyを呼んでも「何もしないようにする」

1を選んだ場合、「なんて馬鹿げた話だ」とクライアントは怒り始めました。継承という実装手法を選んだがために仕様の拡張ができないなんて言うもんだから、このプロダクトの賞味期限をいたずらに短くしただけだというわけです。(私がクライアント、あるいはプロダクトマネージャの職責を負い、プロダクトに本気だとすればキレること間違いなしでしょう。)

わざわざ怒られにいったり交渉事も苦手なので1の結末を予測した並行世界のあなたは2を選ぶことにしました。2カ月後中途入社で物おじしないBさんが「なんでペンギンは鳥で

プログラムに「スズメ」や「カラス」「ハト」が登場します。

「鳥」クラスが「飛ぶ」メソッドを持つとします、この仮定は一見自然で人間にもわかりやすい「リーダブル」な表明です。この前提を用いて継承を用いプログラムを構成することにしました。

さて3カ月後クライアントが「ペンギン」と「ニワトリ」を登場させるように仕様改定を要求してきました。残念なことにペンギンやニワトリは「跳ぶ」ことはあれど「飛ぶ」ことはできません。わずか2つの「反例」が追加されたがために、あなたは次の3つの選択をしなければなりません。

  1. ペンギン、ニワトリをプログラムに登場させないようにクライアントに交渉する
  2. ペンギン、ニワトリは鳥であるが、鳥クラスは継承しないことにした
  3. ペンギン、ニワトリは鳥クラスを継承し、flyを呼んでも「何もしないようにする」

1を選んだ場合、「なんて馬鹿げた話だ」とクライアントは怒り始めました。継承という実装手法を選んだがために仕様の拡張ができないなんて言うもんだから、このプロダクトの賞味期限をいたずらに短くしただけだというわけです。(私がクライアント、あるいはプロダクトマネージャの職責を負い、プロダクトに本気だとすればキレること間違いなしでしょう。)

わざわざ怒られにいったり交渉事も苦手なので1の結末を予測した並行世界のあなたは2を選ぶことにしました。2カ月後中途入社で物おじしないBさんが「なんでペンギンは鳥ではないんですか?」と聞いてきます。あなたはかくかくしかじかこういう経緯があってペンギンは鳥ではないことになっていると説明しました。Bさんは経験があるので「あーそういうことね」と追体験するわけですが、同時に(・・・わざわざ鳥クラスという名前を与えた意味って何だったん?)と早くもテンション低下気味です。

「ペンギンは鳥ではない」という非直観的な実装を同僚やメンバーに説明して回るなんてしたくないあなたは、結局3の結論に行き着きます。これならペンギンは鳥のままであるし、一応関係各所に申し開きができます。

こうして現実的には3の選択肢しか取れないゆえにプログラムのインタフェースが開発を進めるごとにどんどん腐っていきます。(これじゃ設計するどころかその真逆だ)

ペンギンが「飛べない」からといって一方的に「飛ぶ」メソッドを拒絶しつつ鳥クラスを継承するだなんて型の「切り抜き加工」はできないわけです。

鳥クラスに「飛ぶ」クラスをつけたのは無知で愚かだった、次はうまくやれるといい始め、次は窓クラスの設計に挑むことになりました。窓は開け閉めするものだからと「開ける」メソッドと「閉める」メソッドをとりつけたら今度はFIX窓なる固定窓が現れて・・・(以下無限ループ)

  1. を選べば、実装が仕様を必要以上に制約することになっている
    1. そんな実装手法を選ぶなよとしかいいようがない
    2. 実装手法がプロダクトの方向性を妨げる意味とは
  2. を選べば、非自明な実装が決定仕様として表れ始める
    1. 皆が忌み嫌う「レガシー」とはこうした非自明、非直観な実装のあつまりのことではなかったか
  3. を選べば、インタフェースが腐る
    1. ペンギンは飛ばないけど、飛ぶメソッドがなぜかついている
    2. 押して開ける扉なのに取っ手がついているようなもの
      1. それはデザインの基本的原則にすら反する
シグニファイア - Wikipedia
出典: フリー百科事典『ウィキペディア(Wikipedia)』 シグニファイア (signifier)とは、対象物と人間との間の インタラクション の可能性を示唆する手掛かりのことである。デザイン用語としては、 アメリカ合衆国 の認知科学者 ドナルド・ノーマン によって提唱された [ 1 ] 。 俗に アフォーダンス とも称するが、本来アフォーダンスとは「対象物と人間との間のインタラクションの可能性」自体を指し、「対象物と人間との間のインタラクションの可能性を示唆する手掛かり」を指すわけではない [ 2 ] [ 3 ] 。 1988年、提唱者のノーマンは心理学者 ジェームズ・ギブソン の定義したアフォーダンスという用語を著書『誰のためのデザイン? ―認知科学者のデザイン原論』(原題: The Design of Everyday Things)において紹介し、対象物と人間との間のインタラクションの可能性をデザインによって示すことの重要性を説いた。この観点はデザイン領域に多大な影響を与えたが、このとき紹介されたアフォーダンスという用語が本来とは異なる意味で濫用されるようになってしまった [ 3 ] 。 そこでノーマンは、 スイス の記号学者 フェルディナン・ド・ソシュール によって定義された記号学用語 シニフィアン (英語でsignifier、シグニファイア)を借り、俗用されるアフォーダンスへの代替語としてデザイン領域に導入した。ノーマンが2013年に再執筆した『誰のためのデザイン? 増補・改訂版 ―認知科学者のデザイン原論』(原題: The Design of Everyday Things: Revised and Expanded Edition) [ 4 ] やその他の著者によるデザイン書籍 [ 5 ] においても、アフォーダンスとシグニファイアは異なる概念として説明されている。 ゴミ箱に付与されたシグニファイア シグニファイアは利用者に対してアフォーダンスやデザイナの意図を示すものであり、多くの場合、デザイナによって設計対象物に付与されるものである。主に 工業製品 、 建築 、 ウェブページ 、 アプリケーションソフトウェア などの設計に応用される。 たとえば、駅や公共施設に設置されるゴミ箱のデザインには、投入すべきゴミの種類に応じてゴミ投入口の形状を変えているものがある。丸い投入口は空き缶やビンを投入することを示すシグニファイアであり、細長い四角の投入口は雑誌や新聞を投入することを示すシグニファイアである。これらのシグニファイアがゴミ箱に付与されていることにより、ゴミ箱の利用者はより直感的にゴミの分別をすることができる。 一般的には、デザイナは対象物の用途や仕様に沿うようなシグニファイアを設計すべきであるとされるが [ 6 ] [ 7 ] 、目的によっては、あえて対象物の用途や仕様に反するようなシグニファイアが設計されることもある [ 8 ] 。また、偶然発生した現象がシグニファイアとして働く場合もあるほか、一部のシグニファイアはデザイナが意図しなかった解釈を利用者に与えることがある。なお、シグニファイアは必ずしも 視覚 によって知覚される特徴のみを指すわけではない。 聴覚 や 触覚 などによって知覚される特徴もまたシグニファイアとなりうる。 以下にシグニファイアの例を示す。 扉の取手部分に取り付けられた平たい板 扉の向こう側へ行こうとする人間にとって、この平たい板は「押し開ける」という行為のシグニファイアである。同時に、この平たい板は「押し開ける」というアフォーダンスを扉の利用者に想起させる。 はんだごての形状 はんだごて を持とうとする人間にとって、ペンと共通したその形状は「ペンのように持つ」という行為を想起させるシグニファイアである。ただし、はんだごてを「ペンのように持つ」というアフォーダンスは使用者に対して有用ではない。 ウェブページ上に設置されたボタン状のリンク あるウェブページ上に設置されたボタン状のリンクは「この範囲をクリックしてページ遷移する」ことを示したいデザイナーの意図により設置されたシグニファイアである。 駅のホームで電車を待つ人々 電車に乗ろうとする人間にとって、駅のホームで電車を待つ人々は「まだ電車は来ていない」という意味を想起させるシグニファイアである。これは意図的でない現象が偶然シグニファイアとして働いた例である。 アフォーダンスはあくまで「対象物と人間との間のインタラクションの可能性」であり、たとえ人間がそれを知覚しない場合であっても常に存在している [ 2 ] 。 一方、シグニファイアは対象物のもつアフォーダンスやデザイナの設計意図などを顕在化させる役割を担うものであり、常に知覚可能であることが前提となっている。 上述のとおり、アフォーダンスとシグニファイアは異なる概念であるが、歴史的経緯からアフォーダンスという用語がシグニファイアと同義に使用されることも少なくなく、適宜読み替えが必要になることがある。また、シグニファイアの概念および呼称を認識していながら、あえて旧来のアフォーダンスという呼称を使用するデザイナや認知科学者もいる [ 9 ] [ 10 ] [ 11 ] 。 ソシュールの定義したシニフィアンは、ある特定の概念を想起させるような記号表現のことであり、ノーマンがデザイン領域に導入したシグニファイアもまた抽象的観点からみればその定義を踏襲しているといえるが、両者は使用される場面や文脈が異なる。 ソシュールのシニフィアンは言語学的側面に重点が置かれており、記号と概念との関係性を明らかにするために、必ず シニフィエ という語とともに使用される [ 12 ] 。たとえば、海という漢字(シニフィアン)は海の概念(シニフィエ)を想起させるが、この関係性を議論するときにシニフィアンおよびシニフィエ [ 注釈 1 ] の語が用いられる。 一方、ノーマンのシグニファイアは工学的側面に重点が置かれており、何らかの表現を用いて特定の行為や意味を想起させることにより、人間に好ましい行動選択を促したり対象物の操作方法を説明したりする目的で使用される。たとえば、ボタンの形状(シグニファイア)は押すという行為(概念、 メンタルモデル )を想起させるが、この行為の誘導性を議論するときにシグニファイアの語が用いられる。記号学用語としてのシニフィアンはシニフィエとともに語られるのに対し、人間工学用語としてのシグニファイアは(たとえばシグニファイドのような)文法的に対称関係となるような術語を特に持たず、通例、単独の語として紹介される。 ^ この2つの語は文法的に能動態と受動態の関係になっており、英語ではそれぞれsignifier、signifiedという。 ^ ドナルド・ノーマン(著)、伊賀聡一郎、岡本明、安村通晃(訳)『複雑さと共に暮らす ―デザインの挑戦』新曜社、100頁 ^ a b 佐々木正人『新版 アフォーダンス』岩波書店、73頁 ^ a b 黒須正明『人間中心設計の基礎』近代科学社、179頁 ^ ドナルド・ノーマン(著)、岡本明、安村通晃、伊賀聡一郎、野島久雄(

データ構造を継承するということは、その2つのデータ構造は「変性」を持つということになります。

共変性と反変性 (計算機科学) - Wikipedia
( コンピュータプログラミング の 型システム での) 共変性 と 反変性 (きょうへんせいとはんぺんせい、covariance and contravariance)とは、データ コンテナ のサブタイプ関係が、そのデータ要素のサブタイプ関係に連動して定義されるという概念を指す。また、関数の型のサブタイプ関係での、引数型と返り値型の汎化特化の制約を定義する概念でもある。 ジェネリック な データ構造 、関数の型、 クラス の メソッド 、 ジェネリック な クラス 、ジェネリック関数などに適用されている。 共変性と反変性は、 圏論 由来の用語である。この用語には以下の概念がある。 共変 (covariant)は、 派生 <: 基底 とすると、 B <: A ならば I<B> <: I<A> になる。 反変 (contravariant)は、共変のリバースであり、 B <: A ならば I<A> <: I<B> になる。 双変 (bivariant)は、互いに適用可能になり、 B <: A ならば I<B> ≡ I<A> になる。 変性 (variant)は、共変・反変・双変のどれかであることを指す。 非変 (invariant, nonvariant)は、共変・反変・双変のどれでもないことを指す。 総称化 データ構造 での共変性と反変性は、総称化されたデータ要素のサブタイプ関係を、その コンテナ であるデータ構造のサブタイプ関係にどのように反映させるのかを定義するものである。総称化データ構造は、 ジェネリック クラス として実装されることが多い。List・Set・Mapなどが代表である。 総称化コンテナは Container<Element> のように書式される。ここで Cat を Animal の サブタイプ とすると、 List<Cat> と List<Animal> のサブタイプ関係は、以下のようになる。 非変 (nonvariant)は、要素型のサブタイプ関係をコンテナに反映しない。 List<Cat> と List<Animal> は別系統のクラスになる。従って List<Animal> 型の変数に、 List<Cat> 型のインスタンスを代入する サブタイピング などは出来ない。 共変 (covariant)は、要素型のサブタイプ関係をそのまま(正方向で)コンテナに反映させる。 List<Cat> は List<Animal> のサブタイプになる。これは List<Animal> 型の変数に、 List<Cat> 型の インスタンス を 型安全 に代入したい時などに使う。 反変 (contravariant)は、要素型のサブタイプ関係を逆方向にしてコンテナに反映させる。 List<Animal> は List<Cat> のサブタイプになるが、これは単に型安全でなくなるだけである。反変でのデータ要素は 写像 ( 第一級関数 )にされることが多く、写像の 定義域 の型の反変がコンテナに反映される。特化された定義域の写像コンテナを、汎化された定義域の写像コンテナで置き換えたい時などに使う。 双変 (bivariant)は、要素型のサブタイプ関係を双方向にしてコンテナに反映させる。反変と同様にそのデータ要素は 写像 にされることが多い。双変は例えば、特化された定義域の写像コンテナと、汎化された定義域の写像コンテナを相互に置き換え可能にしたい時などに使われ、その写像の 値域 は通常invariantなので List<特化> ≡ List<汎化> になる。 関数の型 ( 英語版 ) での共変性と反変性は、そのサブタイプでのパラメータ型とリターン型の汎化特化を制約して、サブタイピングの 型安全性 を実現するための概念になる。 本節では幾つかの例から説明する。関数の型は パラメータ型 -> リターン型 と書式される。記号 <: は、 派生 <: 基底 を表わす。基底側の関数を派生側の関数で安全に代替できることを、関数の型の 型安全性 と言う。 ここで型 Cat <: Animal とすると、関数 Animal->Animal への関数 Animal->Cat の代入は、その反対よりも安全なので、 (Animal->Cat) <: (Animal->Animal) が推奨される。パラメータ型が同一ならば、リターン型のサブタイプ関係をそのまま関数の型のサブタイプ関係に反映できる。これは共変である。 パラメータ型の方は事情が異なり、関数 Animal->Animal と関数 Cat->Animal の、どちらを代入先の基底型にするべきかという疑問が提起されていた。 ジョン・レイナルド ( 英語版 ) [ 1 ] と ルカ・カルデリ ( 英語版 ) [ 2 ] によって、 (Animal->Animal) <: (Cat->Animal) の方が型安全と結論付けられている。これは反変である。 パラメータ型とリターン型のコンビはやや複雑になる。ここでパラメータ型を Cat <: Animal とし、リターン型を 獣人 <: 動物 とすると、その関数の型では、 (Cat->獣人) <: (Animal->動物) よりも、 (Animal->獣人) <: (Cat->動物) の方が、型安全という結論になっている。この辺りは 代数学 からの考え方になっている。 これはジェネリック関数でも用いられて、 S func[-T, +S] (T x, T y) { ... } のように構文化される。 - は反変記号、 + は共変記号である。関数 func の各インスタンスは、型引数を反映したサブタイプ関係で結ばれる。 一般的な規則は以下となる。 ( P 1 → R 1 ) ≤ ( P 2 → R 2 ) {\displaystyle (P_{1}\rightarrow R_{1})\leq (P_{2}\rightarrow R_{2})} if P 1 ≥ P 2 {\displaystyle P_{1}\geq P_{2}} and R 1 ≤ R 2 {\displaystyle R_{1}\leq R_{2}} . 推論規則 の記法を使うと以下のように書ける。 P 1 ≥ P 2 R 1 ≤ R 2 ( P 1 → R 1 ) ≤ ( P 2 → R 2 ) {\displaystyle {\frac {P_{1}\geq P_{2}\quad R_{1}\leq R_{2}}{(P_{1}\rightarrow R_{1})\leq (P_{2}\rightarrow R_{2})}}} 共変性と反変性は クラス の 継承 でよく用いられる。 ジェネリック クラスの継承の共変性反変性は、総称化データ構造の共変性反変性と似た用法になる。クラスの メソッド の継承の共変性反変性は、関数の型の共変性反変性と似た用法になる。 共変性反変性で枠組みされたクラスのメソッドの継承の 型安全性 を、 バーバラ・リスコフ は 振る舞いサブタイピング ( 英語版 ) の概念で説明している。 歴代 オブジェクト指向言語 でのメソ

結局のところ、継承というのはクラス構造にこの長期的に作用する厄介な制約を十分にドメイン知識が獲得できていない開発初期段階でわざわざ実装に持ち込むということです。そして仕様が歪んだり、非自明性を持ち込むことになるのは、上記事例で示した通りです。

(数学的に真であるなど、絶対に変更しえない仕様がある場合はこの制約が有益なこともあると一応示唆しておきます。)

ようするに、継承を用いて完全な設計をすることは、「仕様、ドメイン知識について全知であり不確実性が一切ない」という前提下においてワークします。そしてそうした技法をサービス開発で適用するのは「無意味」どころか「有害」です。どちらかといえば仕様やドメイン知識を獲得しそれを反映するという作業こそが開発、あるいはプロダクトデザインという作業の本質だからです。

あなたが真に設計者ならば、まずこの問いを立てなければなりません。

「なぜ物事が実体化する前に先んじて意思決定しなければならないのか?」

このことについて解像度高く明晰に語れないのであれば、実際のところ設計はしていない、必要ないと考えたほうがよいです。

3つの問題はことごとく「開発者が継承に価値を見出し、必要以上の制約をわざわざ実装にもたらしたから発生している」ことで、クライアントが意地悪で先にその仕様拡張を言ってこなかったからとか、その開発者が無知蒙昧だったからとかではありません。(もしそう思ってるなら上で語ったミスを生涯にわたり繰り返すことでしょう。)

継承という技法は何の設計支援もしてくれないどころか、最終的には3のような「押して開ける扉なのに取っ手がついている」状況にわざわざ陥るように力強く支援をしてくれます。

私はかつて「デザイン」と「設計」と名のつく部署にいて、働いていたので設計とは何かを理解して、設計には何が必要で、何が必要でないかを理解しているつもりです。1のような無駄な制約をたかが実装手法がもたらすというのはデザイナーが最も忌み嫌うことですが、語られるようであまり語られない真実でしょう。

もちろんマテリアルの特性や、どのような設計が有益なプロダクトを構成しうるのかについて「無知蒙昧」であるならばよい設計にはなれませんが、継承に対しては「無知」であったほうがよい設計になれるのではないかと思います。設計行為に対する感受性(センス)を腐らせうる、それが継承の最も根源的な問題です。

大島 芳樹さんのプロフィール写真

質問の中で、まず「オブジェクト指向という言葉」に関して言えば、ある研究者がすでに存在していた種々の技術をまとめて子供のようなエンドユーザーでも使えるものにしようとしていたときに、懐疑的な人から「一体何をしようとしているんだ」と聞かれ、勢いに任せて「オブジェクト指向だよ」と言った、というのが発端です(1970年ごろのことだと思います)。

「オブジェクト」という言葉は、OSの分野などではメモリに割り当てられた複合的なデータ、というものを指す言葉として「オブジェクト指向」以前から使われていたのですが、そのものそのものが自律的なものとして扱うのだ、という意図が込められていたわけです。

プログラムをより良いやり方で組織立って書こうという努力は、コンピューターの性能が上がってきてからようやく人々が考えられるようになった、というようなものではなく、もちろんプログラミングというものが始まった時から常に考えられてきたことです。オブジェクト指向に関して言えば、その大きなインスピレーションの一つに、1961年以前から米空軍で発明されて使われていたデータの格納方式があります。

あちこちの基地の間をテープでやり取りしていたデータがどんどん複雑化していくという問題が当時からあり、今では名前を残していない誰かが、「テープの先頭から決まったオフセットのところに、後に続く読み出しや書き込みのためのプロシージャへのポインタ

質問の中で、まず「オブジェクト指向という言葉」に関して言えば、ある研究者がすでに存在していた種々の技術をまとめて子供のようなエンドユーザーでも使えるものにしようとしていたときに、懐疑的な人から「一体何をしようとしているんだ」と聞かれ、勢いに任せて「オブジェクト指向だよ」と言った、というのが発端です(1970年ごろのことだと思います)。

「オブジェクト」という言葉は、OSの分野などではメモリに割り当てられた複合的なデータ、というものを指す言葉として「オブジェクト指向」以前から使われていたのですが、そのものそのものが自律的なものとして扱うのだ、という意図が込められていたわけです。

プログラムをより良いやり方で組織立って書こうという努力は、コンピューターの性能が上がってきてからようやく人々が考えられるようになった、というようなものではなく、もちろんプログラミングというものが始まった時から常に考えられてきたことです。オブジェクト指向に関して言えば、その大きなインスピレーションの一つに、1961年以前から米空軍で発明されて使われていたデータの格納方式があります。

あちこちの基地の間をテープでやり取りしていたデータがどんどん複雑化していくという問題が当時からあり、今では名前を残していない誰かが、「テープの先頭から決まったオフセットのところに、後に続く読み出しや書き込みのためのプロシージャへのポインターを入れ、そして一緒に送っているデータを読み書きするためのプロシージャを同じテープの中に格納して送れば良いじゃないか」というアイディアを実現して広めたわけです。このようなテープは、データとそれを扱うためのプロシージャが文字通りひとまとめにされており、共通したインターフェイスからそのプロシージャにアクセスできたわけですね。つまり今で言うようなオブジェクト指向のオブジェクトの片鱗を持っていたわけエス。

このようなアイディアは、聞かされてみれば「誰でも自然に思いつく」と思われるかもしれませんが、発明としてはよくある話で、最初に思いついて実装するには何年もかかったものです。

オブジェクト指向という言葉が生まれる他のインスピレーションとしては、インターネットがありました。それは遠隔地にあるコンピュータ同士がメッセージを送り合うことにより動作するというものですが、こちらはネットワークというのはどこかが故障したりするものだから、効率重視をして上手くいくときに動けば良い、という仕組みとは正反対のポリシーで作るべきであるという考えに基づいていました。今では当たり前かもしれませんが、当時の一般的な考えとは逆行していたわけです。でも、この設計のおかげで、インターネットは五十年以上前に立ち上がった時から一度も全体をリブートして立ち上げ直す、というようなことをする必要なく、10^10個というようなノードが参加したネットワークに有機的に拡大したわけです。これこそは本当に「当たり前ではないけれども天才たちが見事に発明したもの」の例だと思います。

オブジェクト指向を謳ったプログラミング言語も、元々はこのようなスケーリングの可能性を持たせ、例えば一部の機能が不全を起こしても全体としてはクラッシュしない、というような特徴を持っているべきだとされていたわけです。もしお使いの言語がそのような特性を持っていないのだとすれば、それは十分安全ではないわけですね。そして、そのような特性を持たせるためには、システムを作るときにそれなりのビジョンと努力が必要となります。

Simulaという、オブジェクト指向という言葉が生まれる前から存在した言語もあります。こちらも素晴らしい発明でしたし、オブジェクト指向という言葉を生んだ言語に影響を与えましたが、やはりいろいろと先行する技術要素についても知っており、かつビジョンもある、というところでようやく生まれた概念であるとは思います。

というわけで、名前としてはある研究者がふと思いついていった言葉、というのが理由ですが、ただその裏にはかなり深いビジョンと歴史の積み重ねとひらめきがあり、なかなか当たり前のように思いつくものではないように思います。

あなたの返答は公開されません
読む価値のある回答でしたか?
この情報はQuoraがページ上の回答を並べ替えるのに役立ちます。
そう思わない
そう思う
sumimさんのプロフィール写真

オブジェクト指向とは何ですか?で書いた“メッセージングの「オブジェクト指向プログラミング」”について回答します。これはSmalltalkが(不完全ながらも)もっともよくサポートするパラダイムがゆえに、主に「Smalltalkでのプログラミングの好きなところ」になってしまっていますが、その点はどうぞあしからず。^^;

さて。好きなところは、要求されている仕様や思いついた段取りをそのまま素直に書き下せて、それを即座に実行して想定したとおりに動作するのかを確かめる、そんなインタラクティブなコーディングスタイルを許容してくれるところです(もとよりそれには、処理系や環境、ひいてはそれらの設計者や実装者の「メッセージングのオブジェクト指向」に対する深い理解と協力が不可欠です)。

シンプルな例を挙げると、たとえば、

  1. xが3で割り切れるときは'Fizz'を返し、
  2. xが5で割り切れるときは'Buzz'を返し、
  3. xが15で割り切れるときは'FizzBuzz'を返し、
  4. いずれにも当てはまらないときはxを返す。

という仕様が与えられたときに、

  1. xにfizzというメッセージを送ると3で割り切れるときは'Fizz'をそうでないときは''を返し、
  2. xにbuzzというメッセージを送ると5で割り切れるときは'Buzz'をそうでないときは''を返すようにしておけば、
  3. 1.と2.の返値を結合させることで)xが15で割り切れるときは'Fi

オブジェクト指向とは何ですか?で書いた“メッセージングの「オブジェクト指向プログラミング」”について回答します。これはSmalltalkが(不完全ながらも)もっともよくサポートするパラダイムがゆえに、主に「Smalltalkでのプログラミングの好きなところ」になってしまっていますが、その点はどうぞあしからず。^^;

さて。好きなところは、要求されている仕様や思いついた段取りをそのまま素直に書き下せて、それを即座に実行して想定したとおりに動作するのかを確かめる、そんなインタラクティブなコーディングスタイルを許容してくれるところです(もとよりそれには、処理系や環境、ひいてはそれらの設計者や実装者の「メッセージングのオブジェクト指向」に対する深い理解と協力が不可欠です)。

シンプルな例を挙げると、たとえば、

  1. xが3で割り切れるときは'Fizz'を返し、
  2. xが5で割り切れるときは'Buzz'を返し、
  3. xが15で割り切れるときは'FizzBuzz'を返し、
  4. いずれにも当てはまらないときはxを返す。

という仕様が与えられたときに、

  1. xにfizzというメッセージを送ると3で割り切れるときは'Fizz'をそうでないときは''を返し、
  2. xにbuzzというメッセージを送ると5で割り切れるときは'Buzz'をそうでないときは''を返すようにしておけば、
  3. 1.と2.の返値を結合させることで)xが15で割り切れるときは'FizzzBuzz'を返させることができ、
  4. いずれにも当てはまらないときは''になるのでその時はxを返せばよい。

という段取りを思いついたとして、即座に、Smalltalk環境でなら、

  1. | x | 
  2. x := 1. 
  3. x fizz, x buzz ifEmpty: x 

比較のため、同じくメッセージングのオブジェクト指向を限定的にサポートするRuby(のirb)でなら、

  1. x = 1 
  2. (x.fizz + x.buzz).tap{ |it| break x if it.empty? } 

というように、その仕様(この場合は思いついた処理)のとおりに書き下して実行し、検証ができるそんなところです。

残念ながら、オブジェクト指向(具体的には、そのキモである「決定の遅延」)のサポートにSmalltalkほど重きを置いていないRubyでは、あらかじめInteger#fizz、同#buzzを次のように(これもやはり仕様のとおり書き下すことで)定義しておく必要がありますが…

  1. class Integer 
  2. def fizz; self % 3 == 0 ? "Fizz" : "" end 
  3. def buzz; self % 5 == 0 ? "Buzz" : "" end 
  4. end 

ともあれ、1..15のいずれについても期待通りの結果が得られるはずです。

  1. (1..15).collect{ |x| (x.fizz + x.buzz).tap{ |it| break x if it.empty? } 
  2. #=> [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"] 

ちなみに、Smalltalkの場合はどうかと言うと、とりあえず最初のコードを実行(print it)してみて「△△は○○なんてメッセージは知らない…(MessageNotUnderstood: △△>>○○)」というノーティファイアが現れたら(つまり、実行が中断してしまったら)、おもむろにそのノーティファイアのCreateボタンを押して

fizzについてなら

  1. Integer >> fizz 
  2. ^ (self isDivisibleBy: 3) ifTrue: 'Fizz' ifFalse: '' 

buzzについてなら

  1. Integer >> buzz 
  2. ^ (self isDivisibleBy: 5) ifTrue: 'Buzz' ifFalse: '' 

と指摘された都度に必要なメソッドを定義して処理を続行(Proceed)してやることで、

期待される結果の1を得ることが可能です。(Createによるメソッド自動生成機構のないSmalltalk処理系もありますが、その場合はノーティファイアはそのままにして別途クラスブラウザ等でInteger>>fizzあるいはbuzzを定義してからノーティファイアに戻ってProceedすれば同じことができます。念のため)

もちろん、1から15についてもRubyと同様で問題なく動きます。

  1. (1 to: 15) collect: [:x | x fizz, x buzz ifEmpty: x]. 
  2. "=> #(1 2 'Fizz' 4 'Buzz' 'Fizz' 7 8 'Fizz' 'Buzz' 11 'Fizz' 13 14 'FizzBuzz') " 

こうしたメッセージング(あるいはメッセージを記述する行為)を介し「決定の遅延」を意識する、つまるところ「オブジェクト指向」のコーディングの流れは、思考を妨げることが少なく、とても快適で気に入っています。

ついでに、メソッド(非オブジェクト指向であれば関数や手続き)のコールを記述するというよりも、オブジェクトや読み手へのメッセージを記述することを意識したり、それがむしろ推奨されることもとても気に入っていることのひとつです。

たとえば、前述のInteger>>fizzの記述では「 3で割り切れるときは'Fizz'をそうでないときは''を返し…」という仕様を、(self isDivisibleBy: 3) ifTrue: 'Fizz' ifFalse: '' などとストレートに記述できるといった具合です(もし、Number>>isDivisibleBy: が定義されていない処理系なら、fizzやbuzz同様、指摘されたらそのタイミングで期待される振る舞いを記述したメソッドを追加すればよいのです!)

一方で、「嫌い」とまではいきませんが、このメッセージングの考え方でのコーディングを推し進めたとき、処理系にもう一工夫あると便利なのにな…としばしば思うのは、同じメッセージを送って同じ結果が返ると分かっている場合は、前の結果を保持してそれを返して欲しいところです。もっと踏み込むと、我々がメッセージを送るだけで適切なアルゴリズムを選択してよきに計らってくれる、そんなシステムや処理系が理想です。

そこまでいかずとも、関数型でいうところの参照透明性のような利便性があると、メッセージングの記述をもっと読み下しやすく仕様に近づけられるのではないかななどと想像します。

Yoh Osakiさんのプロフィール写真

誰がオブジェクト指向を理解するために、動物>哺乳類>犬のようなアナロジーを持ちこんだのでしょうね?

こういったアナロジーはまったく理解の役に立たなかったのではないかと思います。

私はオブジェクト指向に出会う前は、C言語でプログラミングをしていました。C言語でプログラミングをしていると、大抵は問題領域に合わせて構造体を作り、それを関数に渡して内部のデータを操作するというようなことが起ります。そして関数間でそのデータを引き回すのです。

嘗て、「データ構造+アルゴリズム=プログラム」という古典的な名著がありました。そう言われるくらいにデータ構造とアルゴリズムというのはプログラムにおいて切っても切れない関係にあるわけです。

データ構造とアルゴリズムをひとつのパッケージに入れてしまおうというのがオブジェクト指向の発端だと想像しています。

そうすることの利点は、あるコードの集合体を一箇所にまとめて何に関心のあるコードなのか明確することができる。また、それに対して名前をつけることができる。ということです。こうしてコードが表わすある概念に名前をつけて整理するわけですね。これがクラスです。

そうなんです。名前を付けられることはとても重要なことなんです。

名前をつけることで概念を具現化するわけです。そしてもう一歩先に進むと概念同士の関係を構造化することができます。

それが最初に出てきた、動物 < 哺乳類 < 犬のよう

誰がオブジェクト指向を理解するために、動物>哺乳類>犬のようなアナロジーを持ちこんだのでしょうね?

こういったアナロジーはまったく理解の役に立たなかったのではないかと思います。

私はオブジェクト指向に出会う前は、C言語でプログラミングをしていました。C言語でプログラミングをしていると、大抵は問題領域に合わせて構造体を作り、それを関数に渡して内部のデータを操作するというようなことが起ります。そして関数間でそのデータを引き回すのです。

嘗て、「データ構造+アルゴリズム=プログラム」という古典的な名著がありました。そう言われるくらいにデータ構造とアルゴリズムというのはプログラムにおいて切っても切れない関係にあるわけです。

データ構造とアルゴリズムをひとつのパッケージに入れてしまおうというのがオブジェクト指向の発端だと想像しています。

そうすることの利点は、あるコードの集合体を一箇所にまとめて何に関心のあるコードなのか明確することができる。また、それに対して名前をつけることができる。ということです。こうしてコードが表わすある概念に名前をつけて整理するわけですね。これがクラスです。

そうなんです。名前を付けられることはとても重要なことなんです。

名前をつけることで概念を具現化するわけです。そしてもう一歩先に進むと概念同士の関係を構造化することができます。

それが最初に出てきた、動物 < 哺乳類 < 犬のような関係です。動物で例えると分らなくなるので、プログラミングの概念で考えると解りやすいでしょう。

たとえば、プロトコル < TCPとかプロトコル < HTTPとか(え?HTTPとTCPではレイヤが違うんじゃないの?というつっこみが入りそうですが、プロトコルの抽象化のしかたによってはこういうのもありますよ。)

TCPはプロトコルの一種とかHTTPはプロトコルの一種というように言うことができるのです。これを多態性といいます。

TCPであってもHTTPであってもプロトコルとして扱う分には同じように扱えるわけです。こうした考えかたを使えば、例えばある通信ソフトウェアにおいてプロトコルのレイヤを差しかえて使うことができるようになるわけです。

プロトコルの話は一例ですが、このように概念に名前をつけて概念を構造化するのがクラスです。

Niwa Yosukeさんのプロフィール写真

似たような質問への回答をここに書きました。

これはオブジェクト指向でプログラミングする必要がある!と思う場合は、どんなアルゴリズムを作ろうとした時でしょうか?に対するNiwa Yosukeさんの回答

オブジェクト指向は階層構造による情報整理手法です。

整理するほど情報がないなら別にオブジェクト指向を持ち出さなくてもいいです。しかし、コードを数百行ほど書いたら普通は整理したくなります。

オブジェクト指向が必要なのは、階層構造を作らないとどこに何があるかわかりにくくなるからです。大規模開発でコードが数万行にもなるケースでは、オブジェクト指向がないと管理できず破綻します。


C++ / Java / C# などのオブジェクト指向言語における class は C 言語の struct に端を発しています。struct はプリミティブなデータ型 (int, long, double 等) をいくつか集めてひとまとめにパックしたものです。

C 言語においては、struct を単位としてネストさせ、struct を含む struct を含む struct … と階層構造にすることで複雑なデータセットを構成でき、structA.structB.structC というように "." を連ねることで階層内の目標とする対象へ直感的にたどり着ける便利なアクセス記法を用いる仕様になっています。

パックしたデータはそれぞれ意

似たような質問への回答をここに書きました。

これはオブジェクト指向でプログラミングする必要がある!と思う場合は、どんなアルゴリズムを作ろうとした時でしょうか?に対するNiwa Yosukeさんの回答

オブジェクト指向は階層構造による情報整理手法です。

整理するほど情報がないなら別にオブジェクト指向を持ち出さなくてもいいです。しかし、コードを数百行ほど書いたら普通は整理したくなります。

オブジェクト指向が必要なのは、階層構造を作らないとどこに何があるかわかりにくくなるからです。大規模開発でコードが数万行にもなるケースでは、オブジェクト指向がないと管理できず破綻します。


C++ / Java / C# などのオブジェクト指向言語における class は C 言語の struct に端を発しています。struct はプリミティブなデータ型 (int, long, double 等) をいくつか集めてひとまとめにパックしたものです。

C 言語においては、struct を単位としてネストさせ、struct を含む struct を含む struct … と階層構造にすることで複雑なデータセットを構成でき、structA.structB.structC というように "." を連ねることで階層内の目標とする対象へ直感的にたどり着ける便利なアクセス記法を用いる仕様になっています。

パックしたデータはそれぞれ意味を持っている (= 意味単位にデータをパックする) ため、そのデータセットに適用する関数も抱き合わせで近くに置いておきましょうという発想で、データセット/変数群だけでなく関数をもパック単位に包含するように struct を拡張したのが C++ 以降の class です。

その上で、せっかくデータをわかりやすくパックしたのだから、C 言語のようにフラットに置かれた関数にこまごまとした複数のプリミティブ型データを適用するのではなく、パックしたデータセットの方へそのデータセットに相応しい関数を適用する、というように発想を転換します。データと関数の主客を逆転させ、まとまりとして意味を持つデータセット (= オブジェクト) の方を主軸に据えるため「オブジェクト指向」と呼ばれます。


オブジェクト指向プログラミングには「継承・多態性 (ポリモーフィズム)」「is-a (派生) 関係と has-a (内包) 関係 」「契約と実装の分離」「オーバーライドとオーバーロード」「差分プログラミング」等々の概念やテクニックがありますし、凝り始めるとデザインパターンなどの難しい抽象化にハマっていくことになりますが、そのような難しく抽象的な側面から説明を始めるから、初心者は混乱してオブジェクト指向の目的や意味を見失うのだと私は思います。

オブジェクト指向の本質は単なる「階層構造による整理」であり、加えて「各階層やその中身にアクセス権を割り振る」ということをするだけです。ファイル管理とまったく同じです。

大量のファイルを扱う場合、意味単位別にフォルダを掘って、その中にファイルを格納し、アクセス権を設定する。深い階層には Layer01/Layer02/Layer03 という記法でアクセスする。

それと同様に、大量の変数や関数を扱う場合、意味単位別にクラスを掘って、その中にメンバー(変数、関数)を格納し、アクセス権を設定する。深い階層には Layer01.Layer02.Layer03 という記法でアクセスする。

ただそれだけのことです。

sumimさんのプロフィール写真

英語では「object-oriented」で「OO」と略され、1960~1980年代のプログラミング手法(OOP)から始まり、その応用としてソフトウエアの設計・分析の手法(OOD/OOA)、近年はユーザーインターフェース・エクスペリエンスのデザイン(OOUI/OOUX)、オブジェクト指向存在論(OOO)なる哲学分野にまで、広く使われる用語です。ここではOOPについて説明を試みます。

オブジェクト指向の「オブジェクト」は、1967年に発表されたSimula 67

というプログラミング言語に組み込まれた当時としては新しい同名の言語機能(あるいはそれに準ずる概念)を指し、それは端的に自らに関連した操作を記述した関数(もしくはプロシージャ。総じて「手続き」)を自分の中に持っている特殊なデータを表します。またSimula 67には、このオブジェクトの仕様の定義と実体化(インスタンス化)のための「クラス」という言語機能があり、これは既存のクラスを継承し差分を書き足すだけで類似の新しいクラスを定義できる機構を備えていました。

このSimula 67の「オブジェクト」と「クラス」という2つの言語機能やその振る舞いをヒントにしたり応用することで、次の2つのアイデアが提案されました。

①「メッセージング(メッセージ送信)による決定の遅延の徹底」…アラン・ケイは、オブジェクトを生物の細胞やインターネット(当

脚注

英語では「object-oriented」で「OO」と略され、1960~1980年代のプログラミング手法(OOP)から始まり、その応用としてソフトウエアの設計・分析の手法(OOD/OOA)、近年はユーザーインターフェース・エクスペリエンスのデザイン(OOUI/OOUX)、オブジェクト指向存在論(OOO)なる哲学分野にまで、広く使われる用語です。ここではOOPについて説明を試みます。

オブジェクト指向の「オブジェクト」は、1967年に発表されたSimula 67

というプログラミング言語に組み込まれた当時としては新しい同名の言語機能(あるいはそれに準ずる概念)を指し、それは端的に自らに関連した操作を記述した関数(もしくはプロシージャ。総じて「手続き」)を自分の中に持っている特殊なデータを表します。またSimula 67には、このオブジェクトの仕様の定義と実体化(インスタンス化)のための「クラス」という言語機能があり、これは既存のクラスを継承し差分を書き足すだけで類似の新しいクラスを定義できる機構を備えていました。

このSimula 67の「オブジェクト」と「クラス」という2つの言語機能やその振る舞いをヒントにしたり応用することで、次の2つのアイデアが提案されました。

①「メッセージング(メッセージ送信)による決定の遅延の徹底」…アラン・ケイは、オブジェクトを生物の細胞やインターネット(当時はその前身のARPAネット)を構成するコンピューターに見立て、それらが互いにメッセージを送りあうメタファー(あるいは実際に送るのでもよい)でプログラムを書いたり動かしたりすることで、堅牢で変化に柔軟に対応できる長寿命のソフトウエアを構築できないかと考え、Smalltalkという言語処理系

でその有効性を検証しました

②「クラスによる抽象データ型の実現(カプセル化・継承・多態性)」…ビャーネ・ストラウストラップは、Algolへのクラス(とオブジェクト)の拡張であるSimula 67を踏襲し、Cに対しても同じようにクラスを追加することで、C with Classesを経てC++を設計しました

。この過程で彼は、クラスをユーザー定義のデータ型(広い意味でのバーバラ・リスコフの抽象データ型 )と見做すことが可能であることに気がつき、クラスの継承により、部分型を定義したり、静的型チェックによる安全なプログラミングが可能であると考えました 。なおこのアイデアについては、クラスの発案者であるSimula 67の設計者たち や抽象データ型の発案者であるリスコフ(ただし自ら設計のCLUはパフォーマンス優先でクラスは用いずに抽象データ型を実現している)、Eiffelの設計者であるバートランド・メイヤー ら、複数のグループが時を前後して同様の“気づき”におそらくは自力で到達しています(ここでは知名度とそれに伴う他言語への影響の大きさを鑑み、C++のビャーネ・ストラウストラップを代表格として選びました。あしからず)。

①も②も同じように「オブジェクト指向プログラミング」を名乗ったため、OOPというコンセプトは現在に至るまで混乱を極めています。これは私見ですが、もし、①が最初にオブジェクト指向などと名乗らず「メッセージ指向」と、and/or 続く②が「クラス指向」等々と名乗ってくれていたならば、今日のような混乱や「どちらが真のオブジェクト指向か?」などという不毛な論争の多くは避けられていたはずです。

ともあれOOPは今、特に前置きや断りもなく現れて文脈によって①なのか②なのかを聞き手・読み手側が判断しなければならなかったり、話し手・書き手の理解不足により両者の混同があったり、はたまた両者が相反しない範囲(例えば、①は可能な限りの遅延結合、②は比較的早期の結合を是とする点で相容れないので、そこはあまり突き詰めない立ち位置)で互いの要素を取り入れた混成として扱われることが多く、実に学習者泣かせの難解な用語となってしまっています。

基本的に、メジャーなプログラミング言語においては②のアイデアに基づくスタイルを主にサポートするOOPがほとんどですが、一部ではRubyのようにSmalltalkの少なからぬ影響下にある言語においてその仕様やメソッド名に①のコンセプトである「メッセージ」を意識した説明が必須であったり、昨今はアクターやそれに準ずる機能を前面に押し出した言語やフレームワークの台頭で、①に軸足を置いてOOPが語られる機会も多く、①のOOPも完全には排除できません。

以下は関連する補足です。

  • [2023–09–20 追記] アラン・ケイが、ビルディングブロックとしての「オブジェクト」の存在とその可能性に気がついたのは 1966年ごろ、まだ「クラス」や「オブジェクト」という言語機能を持たない(同等の機能をそれぞれ「アクティビティ」「プロセス」と呼んでいた)SimulaⅠの処理系のソースコードを読んでいたときのことなので、実際の話の流れはもう少し複雑です^^;
  • Smalltalkのごく初期の実装(Smalltalk-72 )では、クラスは継承機構を持たず、メソッドも独立した関数ではなく、非同期ではないながらもコンセプトに従い、実際にオブジェクトにメッセージを送る機構を採用していました 。しかし速度面の問題などから、その後のSmalltalk-76 以降の実装 で はSimula 67スタイルのクラス(つまり、継承機構を有し、メソッドを独立した関数としてクラスに内包する)を採用して、メソッド(つまりメンバー関数)の動的呼び出しをもってメッセージ・パッシングと称する省コスト版(呼び出すメソッドが見つからない時だけメッセージはハンドリングできる)の機構に置き換えられました。以来このため①と②それぞれが目指すところを知らずに、例えばSmalltalkとC++とで比較して単に動的型か静的型かといった程度の違いしか見分けられず、しょせんは同じOOP向け言語だろう…といった誤解が生じやすくなってしまった経緯も混乱に拍車をかけてしまっています。加えて、Simula 67スタイルのクラスを採用したことで、Smalltalkは本来の目的である①に反しない範囲で(あるいはそれを敢えて放棄することで)極めて限定的ながらも②のOOPもサポート可能になったことにも注意を払う必要があるでしょう。
  • ①の実践の場であるSmalltalkに対して批判的に派生して考案されたのが「プロトタイプベースのオブジェクト指向 です。当初はメッセージングを重視していましたが、シンプリシティを追求するうちにメッセージングというメタファーすら無用と排除され、オブジェクトに相当する「フレーム」と、それに値(関数を含む)の格納場所に相当する「スロット」(存在しないスロットに対するアクセスをプロトタイプ等別のフレームに委譲するための特殊なスロットを含む)だけがあればすべては事足りるという、①からはほぼ独立したアイデアやそれを基にしたスタイルとして認知されています。
  • メッセージといえばカール・ヒューイットのアクター理論 を思い浮かべる人が多いかと思いますが、こちらは、アラン・ケイらのSmalltalk-72のメッセージングによるコンピューティングをヒントに、並行・並列の非同期処理にかかわる問題の解決に役立てようと考案され定式化されたものです。なおヒューイットは、彼が影響をうけたSmalltalk-72と、彼のPLANNER に影響をうけて一部仕様のみ策定され実装には至らなかった別言語のSmalltalk-71とを混同した記述が多い ため、彼によるアクターとSmalltalkの関係についての言及やそれに基づく記述は注意深く読み解く必要があります。
  • 余談ですが、このカール・ヒューイットのアクター理論を読み解くために作られたのがLispの方言の最大派閥ともいえるSchmeです(※読むにはエンコードを Shift-JIS に変更できるブラウザもしくは拡張機能が必要です )。
  • Erlang のメッセージングはヒューイットの考えるアクターの要件を満たしておらず 、また、Smalltalkで実践されたケイのメッセージングのアイデアとは無関係に成立したもののようです(少なくとも論文では両者への言及は無いようなので…)。ところが同言語の設計者であるジョー・アームストロングは晩年、①の真の担い手がErlangであるかのように受け取れる発言 をしていて大変興味深いです。
  • ②の三要素は、カプセル化=抽象データ型の特徴、継承=クラスの特徴、多態性(ポリモーフィズム)=主に継承(≒クラスを使いこなすこと)により部分型多相が可能になること、つまり②の本質である「クラスによる抽象データ型の実現」を端的に表します。
  • ②の継承については、Eiffelなど一部の実装ではクラスをデータ型として、それを継承した派生クラスを部分型として扱うことに問題が生じることが比較的早い時期に指摘されていて 、クラスやその継承でではなく新たに考案された言語機能としてのインターフェース を用いることが定着しています。
  • 抽象データ型というのは、組み込み型の「具象」に対する「抽象」という意味で、前述の通り簡単には「ユーザー定義のデータ型」と言い換えられます。実装面では、データと手続きをセットにして、データの内部情報はそのセットにした手続きでしか変更できないようにする機構(カプセル化、あるいは情報隠蔽・アクセスコントロール)を「型」という視点で管理するアイデアです。クラスが提供する機能と区別がつきにくい(②がどんなアイデアだったかを思い起こせば、ある意味当然…)ですが、Simula 67に立ち返りそもそも同言語のクラスは当初、カプセル化のしくみを欠いていた(つまりクラスは元々は抽象データ型の要件を満たすべく考案されたものではない)ことや、また、抽象データ型はデータと手続きをセットにはするが、手続きを型に内包する必要はかならずしもないこと(例:CLUのクラスター。狭義、あるいは言語機能としての「抽象データ型」)などを知ると両者の区別がつきやすくなるかと思います。
  • 抽象データ型や部分型多相をクラス(や、その機能である継承)を使って実現するアイデアが②のOOPなので、「抽象データ型やポリモーフィズムはOOP以外でもできる」とか「それらはOOPには必須ではない」という指摘は本末を転倒しています。

脚注

Shinya Kitamuraさんのプロフィール写真

今日、A君はB君の家に遊びに行く予定です。

A「そろそろ行くか。先にB君に連絡を入れておこう」

B「・・・」

「二足歩行で行くね」とは言いません。なぜなら二足歩行は特別でもないし、普段からしていることだからです。

要するに言わんでもわかるやろということです。

では、こちらの犬はどのように説明しますか?

「犬という役割に飽いてしまった犬」または「二足歩行している犬」ですね。これを見て、「犬が歩いてる」と説明しても、聞き手は四足歩行を想像するでしょう。

では、「歩く」という言葉の装飾として「人が四足歩行する」は不適切で、「犬が二足歩行する」が適しているのは何故なのでしょうか。

我々は無意識に、会話する相手との間にある、共通認識について考えながら会話をしています。

部屋に入って来るなり,妹がぼくに話しかけてきた。①ふり返って、妹を見た。

①は主語が省略されています。さて、①の主語は何でしょうか?

正解は「僕」です。

例文から想像する図としてはこうですね。

これを次のように想像する方は「誰が振り返ったんだ?」と思うかもしれません。

厳密には、部屋の中に兄や友達などが居るかもしれませんが、むしろそれこそ省略せず説明すべき事項だと、人は無意識に考えるわけです。

要するに「なんの説明も無ぇってことは自分が振り返ったんだろうな」ということです。

つまり極端な話、B君の反応にもある通り、普段二足歩行している人が「四足歩行で行く

今日、A君はB君の家に遊びに行く予定です。

A「そろそろ行くか。先にB君に連絡を入れておこう」

B「・・・」

「二足歩行で行くね」とは言いません。なぜなら二足歩行は特別でもないし、普段からしていることだからです。

要するに言わんでもわかるやろということです。

では、こちらの犬はどのように説明しますか?

「犬という役割に飽いてしまった犬」または「二足歩行している犬」ですね。これを見て、「犬が歩いてる」と説明しても、聞き手は四足歩行を想像するでしょう。

では、「歩く」という言葉の装飾として「人が四足歩行する」は不適切で、「犬が二足歩行する」が適しているのは何故なのでしょうか。

我々は無意識に、会話する相手との間にある、共通認識について考えながら会話をしています。

部屋に入って来るなり,妹がぼくに話しかけてきた。①ふり返って、妹を見た。

①は主語が省略されています。さて、①の主語は何でしょうか?

正解は「僕」です。

例文から想像する図としてはこうですね。

これを次のように想像する方は「誰が振り返ったんだ?」と思うかもしれません。

厳密には、部屋の中に兄や友達などが居るかもしれませんが、むしろそれこそ省略せず説明すべき事項だと、人は無意識に考えるわけです。

要するに「なんの説明も無ぇってことは自分が振り返ったんだろうな」ということです。

つまり極端な話、B君の反応にもある通り、普段二足歩行している人が「四足歩行で行くわ」と言うのは適していると言うことですね。

ですので、例えば次のような「オブジェクト指向以外があるかもしれない」場合には「オブジェクト指向」という言葉を用いて会話することになるでしょう。

  • 普段手続き形プログラミングをすることが多いベンダーに対して指示をするとき。
  • 手続き形プログラムで組まれることが多いシステムを、オブジェクト指向で組んで欲しいとき。

ということで、ご質問への回答は、

選択肢としてオブジェクト指向以外の何かがある時に、差別化のために便利な言葉です。

としたいと思います。

例文出典:

主語の見つけ方| 小学生の国語質問ひろば | 進研ゼミ小学講座
【主語】主語の見つけ方 文章を読んでいて,主語がどこなのかがわからなくなることがあります。どのようにしたら主語を正しくとらえることができますか。 主語をとらえるには、述語にあたる言葉と、「〜は」「〜が」「〜も」がついている言葉に着目し、それらの意味がつながるかを確認するようにしましょう。 文は、「だれが(は)」「何が(は)」にあたる言葉(主語)、「どうした(どうする)」にあたる言葉(述語)、「だれに」「何を」「どのように」などのくわしく説明する言葉(修飾語)で成り立っています。 主語は文のはじめだけではなく、文の途中に出てくる場合もあります。 また、次のように文中に主語がない場合もあります。 [例] 部屋に入って来るなり,妹がぼくに話しかけてきた。①ふり返って、妹を見た。 ②いつになく目をキラキラとかがやかせている。ぼくはたずねた。 「何かいいことでもあった?」 ①と②の文は主語が省略されています。 このような場合には、前後の文の内容から状況を正しくとらえ、その文の主語がだれ(何)なのかを考えることが大切です。 ①の場合、「妹を見た」とあるので、その動作の主は「ぼく」で、主語は「ぼくは」だとわかります。 また②の場合、「ぼく」が見た「妹」の様子が書かれているので、主語は「妹は」だとわかります。 このように、主語と述語のつながりを意識して読めるようになると、長文の物語の内容も正しく読み取ることができます。
Toru Hisaiさんのプロフィール写真

まず、オブジェクト指向は object-oriented の訳語で、この orient という言葉は「方向付ける」というような意味ですが、元は日が昇る方向、すなわち東を意味する言葉だったようです。今でもオリエンタルといえば東洋的という意味になります。

object-oriented programming をそのまま解釈すると、「オブジェクトによって方向付けられたプログラミング」という意味になります。言い換えるとオブジェクトがプログラミングを方向付けているのです。オブジェクトの方を向いているという意味ではありません。しかし日本語の「オブジェクト指向」という言葉には、何が何を方向づけているのかという関係性が抜け落ちているように感じます。

ここで object は名詞です。ぼくは今までてっきり objective だと形容詞っぽいから object なんだろうと思っていましたが、改めて辞書を見てみると、そんなに簡単な話でもなさそうです。

辞書を見ると object にも objective にも名詞として「目標」とか「目的」という意味があるようです。できれば英語を母語とする人に印象を聞いてみたいところですが、オンラインの辞書ではこの二つの単語は微妙に違った定義がされています。

まず object ですが、これはみんなが知っているモノとしての意味「見たり触ったりできるもので比較的安定した形のもの

まず、オブジェクト指向は object-oriented の訳語で、この orient という言葉は「方向付ける」というような意味ですが、元は日が昇る方向、すなわち東を意味する言葉だったようです。今でもオリエンタルといえば東洋的という意味になります。

object-oriented programming をそのまま解釈すると、「オブジェクトによって方向付けられたプログラミング」という意味になります。言い換えるとオブジェクトがプログラミングを方向付けているのです。オブジェクトの方を向いているという意味ではありません。しかし日本語の「オブジェクト指向」という言葉には、何が何を方向づけているのかという関係性が抜け落ちているように感じます。

ここで object は名詞です。ぼくは今までてっきり objective だと形容詞っぽいから object なんだろうと思っていましたが、改めて辞書を見てみると、そんなに簡単な話でもなさそうです。

辞書を見ると object にも objective にも名詞として「目標」とか「目的」という意味があるようです。できれば英語を母語とする人に印象を聞いてみたいところですが、オンラインの辞書ではこの二つの単語は微妙に違った定義がされています。

まず object ですが、これはみんなが知っているモノとしての意味「見たり触ったりできるもので比較的安定した形のもの (anything that is visible or tangible and is relatively stable in form)」が先に来ます。そして次に「何らかの行動や考えが向かう先にある物、人、事 (a thing, person, or matter to which thought or action is directed)」という説明がされています。(Dictionary.com より。以下も同じ。)

次に objective ですが、これはもう少し具体的に「努力や行動によって得られる、または達成できる事が意図されている何らかの物。目的、ゴール、目標 (something that one's efforts or actions are intended to attain or accomplish; purpose; goal; target)」という風に書かれています。

上の説明を信じるならば、objective という言葉には行動を伴うような少し重い意味が含まれるようです。実際 objectives-oriented という言葉も存在するようですが、これはビジネスでの成果目標や成果主義のような考え方を指すようです。(いま Google で検索して初めて知りました。)

上で見たように、object という言葉にも目的とか対象物のような意味がありますので、一概に「モノ」か「目的」かのどちらかに決めてしまうことはできないように思います。それに、単なるモノなら thing や body でもいいはずです。

ところでもう一つ忘れてはいけない、コンピュータプログラミングに関わる重要な「オブジェクト」が存在します。それは「オブジェクトコード」です。

最近の開発環境ではあまり意識することがないかもしれませんが、C や C++ でプログラミングすると、ビルドの際に「コンパイル」と「リンク」の 2 段階を経ています。

オブジェクトコードとはコンパイルの際に「コンパイラが出力する何か」です。他方、コンパイラに対する入力は何かと言うと「ソースコード」です。source とは何かが出てくる源です。ということはその反対側にあるオブジェクトコードは最終的な目的とする物、という雰囲気が自然に感じられますよね。

ではもう一度オブジェクト指向に話を戻すと、オブジェクトとは「メッセージの受け手(レシーバー)」と考えることもできます。Smalltalk という言語ではデータにまつわるあらゆる操作は全て「メッセージ式」という形で表されています。(Objective-C はこの Smalltalk の考え方を C の上に乗っけたものです。なお、Objective-C の Objective は名詞ではなく形容詞だと解釈するのが自然だと思います。)

もちろんそこに何らかの実体としてのデータがあることを前提としていますが、単なるモノというよりも目標、対象という意味が含まれているように感じないでしょうか。

もっというと、C++ のオブジェクトは必ずしもメモリ上に一定のデータを保持する必要すらありません。C++ のメソッド呼び出しは、実行時まで型が決定されない場合を除けば、コンパイル時にオブジェクトの型とメソッドによって実行されるコード(関数)が決定され、必ずしも this ポインタが何かを指しているとは限りません。

デバッガでプログラムを追ってみると、this ポインタが見当たらなくて悩んだことがある人も多いのではないでしょうか。そうなると、メソッドを呼ぶ対象としてのオブジェクトは、実体はなく方向付けだけをしていると考えることもできそうな気がしますが、どうでしょうか?

ある言葉を別の言葉に翻訳する時、ある訳語が正しくて別の訳語が間違っていると言い切ることはできない事が多いように感じます。例えば「すみません」を英語で言う時、“I’m sorry” か “Excuse me” と言います。これらは少し意味合いが違うので、英語で話す時は使い分けますが、日本語で「すみません」というとき「これは謝罪の意味です」「これは注意を喚起する意味です」といちいち考えませんよね?

英語の “I’m sorry” は「ごめんなさい」の意味と「お気の毒に」の意味があります。日本人には全然違う意味のことを同じ言葉で言っているように思えますが、英語話者にとってはそれなりに近い意味に感じるからこそ同じ音葉が使われるのだと思います。

コンピュータのプログラムである以上、あらゆるモノはただ存在するだけではなく、常に何らかの操作の対象であるはずです。(操作の対象にならない、またはなり得ないメモリはゴミと呼ばれます。)仮にオブジェクトに目的という意味があったからと言って、モノというのは誤訳だ、なんて言わなくていいと思います。もっと気楽に構えた上で、問題の本質に向き合う事が大事です。

Yukihiro Matsumotoさんのプロフィール写真

この件については、すでに18年も前に Matzにっき(2003-08-06) で書いたことが全てだと思います。

つまり、継承をisa関係にしか使わないという原則に従っていれば問題はほぼなく、逆に一見使い勝手が良さそうに思えるからと言って、isa関係でない機能取り込みに継承を使うと痛い目に遭うということではないでしょうか。

Javaのような(C++からの流れで)抽象データ型から生まれたオブジェクト指向プログラミング言語では、クラスが単なるモジュールに見え、継承が他のモジュールからの機能取り込みに見えるのかもしれません。その延長で単なる機能取り込みに継承を使い、コードが複雑化してしまった上で、「羹(あつもの)に懲りて膾(なます)を吹く」状態が上記の継承忌避なのだと推測します。

継承を使うべきときには継承を使い、そうでない時にはそれ以外の手段(コンポジションとか)を使うのが正しい態度だと思います。

山本 聡さんのプロフィール写真

以前書いた投稿があります。

オブジェクト指向のプログラミングの問題点は何ですか?に対する山本 聡 (Satoshi Yamamoto)さんの回答

が、その回答は長ったらすぎるので、短めに書くと、

「オブジェクト指向のプログラムは難しいと言われる理由」は、

やれることがいろいろあり、また、それによって起きる制限もいろいろありすぎて、わけがわからない、から、だと思います。

継承だのインターフェースだの多態だのなんだのかんだのといろいろありすぎです。

そしてこれら全てが、どうでもいい概念です。
ifの分岐やforのループや、処理共通化、データを得て、表示を行う。プログラムの重要な概念ってこういうものだけですし、これで多くのアプリケーションの機能を作り出すことができるはずですが、

オブジェクト指向の概念というか、それらの機能はアプリケーションを作るという本質とはかけ離れた機能なので、どう使ったらいいのか本当のところが誰にもわからない、のでアレコレの派閥があってアレコレの議論が行われるので、決まった道がないために学ぶ人は苦労するということになったりします。

自転車置き場の議論として知られる『原子力発電所の建設ミーティングのときに、より重要なことは難しくて議論の対象にはならず発電所の自転車置き場の屋根の色はどうしようかと議論に花が咲く』という例え話がありますが、

オブジェクト指向であーだのこーだの言っているのは

以前書いた投稿があります。

オブジェクト指向のプログラミングの問題点は何ですか?に対する山本 聡 (Satoshi Yamamoto)さんの回答

が、その回答は長ったらすぎるので、短めに書くと、

「オブジェクト指向のプログラムは難しいと言われる理由」は、

やれることがいろいろあり、また、それによって起きる制限もいろいろありすぎて、わけがわからない、から、だと思います。

継承だのインターフェースだの多態だのなんだのかんだのといろいろありすぎです。

そしてこれら全てが、どうでもいい概念です。
ifの分岐やforのループや、処理共通化、データを得て、表示を行う。プログラムの重要な概念ってこういうものだけですし、これで多くのアプリケーションの機能を作り出すことができるはずですが、

オブジェクト指向の概念というか、それらの機能はアプリケーションを作るという本質とはかけ離れた機能なので、どう使ったらいいのか本当のところが誰にもわからない、のでアレコレの派閥があってアレコレの議論が行われるので、決まった道がないために学ぶ人は苦労するということになったりします。

自転車置き場の議論として知られる『原子力発電所の建設ミーティングのときに、より重要なことは難しくて議論の対象にはならず発電所の自転車置き場の屋根の色はどうしようかと議論に花が咲く』という例え話がありますが、

オブジェクト指向であーだのこーだの言っているのはこの自転車置き場の議論に近いことが起きます。誰もが大好きな議論ですが、そこにはソフトウェアの機能としての本質はなく、正解もないのでいつまでたっても議論が終わりません。

「クラスごとに纏まっていて見やすいように思えます」というのは

何に対して見やすいのか、ということですが、クラスにまとまるのはグローバルに散らばっているのよりかはマシなのですが、クラスメソッドを書くという時点で、クラスは小さなグローバル領域になっているだけなので、

グローバル変数とかグローバル関数とかだけでアプリケーションを作るのがなかなか困難なのと同じように

アプリケーションが複雑化してクラスがどうしても肥大化しなければいけない時に、一気に複雑さがまして、他人のコードを読むことが困難になるのが、オブジェクト指向です。

クラスの責務がなんたらかんたらと言い出す人もいるのですが、まあ終わらない議論に巻き込まれるだけになりますね。

ということで、副作用がない(あるいは少ない)、参照透過性が高くてテストしやすい関数型プログラミングが流行ってきているようです。

こちらは小さなグローバル領域などは作らないのでアプリケーションの肥大化に対して非常に強く複雑さを隠蔽する仕組みになります。

関数型プログラミングというのも、それなりにあやふやな概念なので議論を呼ぶみたいですが。そのあたりは詳しくないので、どこか他のQuoraで答えをみつけてください。

関数型というか昔ながらの構造化プログラミングですね。

オブジェクト指向を捨てた所に正解があるとは思ってもみませんでした。私、業界20年くらいというかプログラミング歴20年くらいですが、ようやく気が付きました。

ありがたいことに気がついてからは、オブジェクト指向で複雑にからまった状態のプログラムであっても、リファクタリングしたりしてもより簡単なコードをどんどん書けるようになって楽ですよ。

Tomoya Kitayamaさんのプロフィール写真

逆風というわけではないのですが、オブジェクト指向プログラミングが不要なケースが明確になったのだと思います。

  • Webというステートレスな処理を扱う場合、オブジェクト指向で実装する意義が若干下がった。
  • 分散処理などを書く場合、オブジェクト指向的な実装より関数型プログラミング的な指向の方がマッチしている(ことが多い)。単一のプログラムでもコア数の増加とともに、分散処理で実装する意義が増えてきている。
  • DBの性能向上・ORMの機能の多様化でオブジェクト指向が以前は担当していた領域がDBレイヤーにいったことで相対的に価値が下がった。

オブジェクト指向はステートフルな処理系を書く場合に情報をカプセル化することで、堅牢なプログラムを実装するベストプラクティスです。まだまだ、この実装方法がマッチするシステムはたくさんあります。

一方、オブジェクト指向で実装しないほうが良いケースも見えてきました。特に分散処理では、In/Out以外の依存性を持たないストリーム指向がマッチするため、関数型で実装した方がきれいに実装できるケースが多いと思います。

プログラマーであれば、基本すべてツールですから、いろんな方法で実装できるようにしたいものです。

Quora Userさんのプロフィール写真

まず実装の継承は嫌いです。一般にも批判が多く、使われなくなくなってきています。新しい言語では特に。

次に、クラスが嫌いです。役割りを負いすぎています。

インスタンスメソッドが嫌いです。継承を使わない前提ではvtableは無用です。thisへの暗黙の依存を伴う、リファクタリングや関数合成をしにくくする扱いにくい存在です。

すべてがオブジェクトという考え方は嫌いです。「メソッド後置の統一」という見た目の効果を求めているなら、それならなぜ中置オペレータがあるのか。中置オペレータにしたならしたで、それは算術演算として交換法則にしたがうべきなのにメソッド選択権限がレシーバのみにあるのが嫌い(マルチメソッド/ダブルディスパッチが簡潔に表現できてるなら良し)。またどうせ静的型のジェネリクスや型変数はオブジェクトではあつかいきれない。(コンパイルタイムメッセージパッシング、てあるかな)

可変のインスタンス変数は嫌いです。オブジェクトが状態もつということを、あまりにも軽率に可能にしてしまいます。

モックとか嫌いです。滅んでほしいです。DIはもっと嫌いです。面倒くさいからです。

単一責任原則は嫌いです。早すぎる最適化であり、未来の「ひとつの理由」に常に対応できるという傲慢さを感じるからです。「マイクロサービス細かく分けすぎる厨」に通じるものを感じます。

開放封鎖原則は、クラス継承を使わない場合に何を意味するのだろう

まず実装の継承は嫌いです。一般にも批判が多く、使われなくなくなってきています。新しい言語では特に。

次に、クラスが嫌いです。役割りを負いすぎています。

インスタンスメソッドが嫌いです。継承を使わない前提ではvtableは無用です。thisへの暗黙の依存を伴う、リファクタリングや関数合成をしにくくする扱いにくい存在です。

すべてがオブジェクトという考え方は嫌いです。「メソッド後置の統一」という見た目の効果を求めているなら、それならなぜ中置オペレータがあるのか。中置オペレータにしたならしたで、それは算術演算として交換法則にしたがうべきなのにメソッド選択権限がレシーバのみにあるのが嫌い(マルチメソッド/ダブルディスパッチが簡潔に表現できてるなら良し)。またどうせ静的型のジェネリクスや型変数はオブジェクトではあつかいきれない。(コンパイルタイムメッセージパッシング、てあるかな)

可変のインスタンス変数は嫌いです。オブジェクトが状態もつということを、あまりにも軽率に可能にしてしまいます。

モックとか嫌いです。滅んでほしいです。DIはもっと嫌いです。面倒くさいからです。

単一責任原則は嫌いです。早すぎる最適化であり、未来の「ひとつの理由」に常に対応できるという傲慢さを感じるからです。「マイクロサービス細かく分けすぎる厨」に通じるものを感じます。

開放封鎖原則は、クラス継承を使わない場合に何を意味するのだろうと思っちゃいます。

その他、オブジェクト指向設計原則のそれぞれは、オブジェクト指向を使わなければもともと難なく解決できる場合も多いと感じます。

好きなところは…上のような知見を10年ぐらいかけて生みだせたところでしょうか。

大島 芳樹さんのプロフィール写真

Ueharaさんがなぜjavaは純粋なオブジェクト指向プログラミング言語ではないですか?に対するQuoraユーザーさんの回答で書いている通りに、純粋なオブジェクト指向言語ならいくらでもあるので、Javaが純粋でないというのは、「やり方さえ知っていれば純粋なオブジェクト指向言語にできたはずなのに、ちょっと間違った意思決定をしてしまって、オブジェクトでないものをシステムや言語の中にいれてしまったから」ということが理由です。

純粋だと言われるSmalltalkの場合、Booleanがオブジェクトなのはもちろんですが、「コンピューター全体」がオブジェクトの集合として表現されているわけです。「クラス」、「メソッド」、「メタクラス」と言ったものももちろん、クラスの集合を格納しておく「名前空間」と言ったものも当然ながらオブジェクトで、それらは実行環境の中で直接操作することができます。さらには、その実行環境もまたオブジェクトの集合なのです実行状態を表す「スタックフレーム」もオブジェクトであり、例えば「継続」を実装したければ、スタックをコピーして保持しておき、後でreturn:やresume:といったメソッドを呼び出して復帰したりもできます。

このことから導き出される最も重要な要素は、「論理的にはシステムの実行ということそのものが自分自身で記述されている」ということです。あるオブジェクトにメッセージ・オ

Ueharaさんがなぜjavaは純粋なオブジェクト指向プログラミング言語ではないですか?に対するQuoraユーザーさんの回答で書いている通りに、純粋なオブジェクト指向言語ならいくらでもあるので、Javaが純粋でないというのは、「やり方さえ知っていれば純粋なオブジェクト指向言語にできたはずなのに、ちょっと間違った意思決定をしてしまって、オブジェクトでないものをシステムや言語の中にいれてしまったから」ということが理由です。

純粋だと言われるSmalltalkの場合、Booleanがオブジェクトなのはもちろんですが、「コンピューター全体」がオブジェクトの集合として表現されているわけです。「クラス」、「メソッド」、「メタクラス」と言ったものももちろん、クラスの集合を格納しておく「名前空間」と言ったものも当然ながらオブジェクトで、それらは実行環境の中で直接操作することができます。さらには、その実行環境もまたオブジェクトの集合なのです実行状態を表す「スタックフレーム」もオブジェクトであり、例えば「継続」を実装したければ、スタックをコピーして保持しておき、後でreturn:やresume:といったメソッドを呼び出して復帰したりもできます。

このことから導き出される最も重要な要素は、「論理的にはシステムの実行ということそのものが自分自身で記述されている」ということです。あるオブジェクトにメッセージ・オブジェクトが送られた時に、対応するメソッド・オブジェクトを探し出し、それを起動するためにスタックフレーム・オブジェクトを割り当てて、メソッドが指定する動作に応じてそのフレームに値を書き込んだり読みだしたりすることが言語自身で記述されたプログラムとして行われているかのようになっているわけです。

ただ、もし本当にこのように動作しているのだとすれば、他の人の回答にある「遅いのではないか」という想像に基づく懸念も理解できるのですが、実用的な速度で動かすために、実際には上記の動作をより高速に行うためのインタープリター「も」用意されています。実行されているコードが上記のようなメタ機能を司るオブジェクトにアクセスしない限りは、それらのオブジェクトを実際に生成する必要はなく、インタープリターは諸処の工夫で高速化することができます。結果的には、一般的に使われているSmalltalkの処理系はPythonやRubyのものよりも何倍も高速です。もともとグラフィックスやリアルタイム音声処理をしようとしていた人々が作っていた言語ですので、それらをこなせるようになっているわけです。

別の言い方をすれば、他の言語で文字列処理やデータ処理のためにネイティブ・メソッドを書き、言語自体で書かれたコードの代わりに外部のものを呼び出すことによって同じ機能をより高速に提供できる、というところで、Smalltalkはそのようなデータ処理だけではなく言語実行全体をネイティブメソッドのように高速化したものも用意されていると言っても良いかもしれません。

tracing JITを用いたような実装系であれば、全てがオブジェクトであり、Cで書かれたようなプリミティブをなるべく必要としないということが速度向上の役に立ちます。近年流行っている技術からの類推で言えば、ウェブブラウザのDOMオブジェクトの動作はC++で書かれていて、もしJavaScriptからそれらのオブジェクトのプロパティに値を代入すると、 C++で書かれたコードが内部で動作するというようになっていますよね。React.jsなどが使っているvirtual DOMは、その部分を仮想化してJavaScriptで書いているというわけなのですが、このテクニックが効果的である一つの理由は、C++で書かれたDOMオブジェクト操作の部分を通らないJavaScriptの「実行トレース」を長くすることができるために、JITコンパイラがより効果的になるから、という面があります。Smalltalkですべてをオブジェクトにするというのは、わざわざvirtual DOMのようなものをあえて実装しなくてももともと全部がvirtual DOMみたいなものだったと言えるわけですし、tracing JITでなくても少ない機能の高速化に注力するだけで全体が早くなるという効果もあります。

システムとしては、「マウス」、「ディスプレイ」というものもオブジェクトであり、「ディスプレイ」のピクセル値を書き換えることにより画面を描画しますし、何が表示されているのかもピクセルを読み出すことにより確認できます。こちらもBitBltと呼ばれる高速化ルーチンがあります。理屈の上ではユーザーは何も気にしないでコードを書けばいつのまにか、Cで書いたのと同じようなパフォーマンスのグラフィックス操作ができる、という建前ですが、この辺はさすがに実際にどのようになっているのか知らないと速度は出ませんが。

別の方の回答にはメモリを使うのではないかというまた想像に基づいた回答がありますが、こちらもまた事実とは違います。もともと128kバイトしかメモリがなかったAltoコンピューターで動いていたということを忘れないでください。2019年現在でも、開発環境も全て含めたものでも50MBもあればゆうゆう動きます。大事なのは非常に小さなVMを使い、Arrayなどのメソッドも全てコンパクトなバイトコードとして保持できているということで、マシンコードとして保持するよりもメモリ効率が良いということです。2000年ごろにはDisneyがテーマパークでお客さん向けの携帯電子機器を使ったガイドを作ろうと実験していた時も、Smalltalkで書かれたプログラムだったからこそ動かすことができた、というような話もありました。「すべてがオブジェクト」だからこそメモリ効率がよくなっているわけですし、以下の論文に書かれているようにメモリーチップに不具合があり端末上で予期せぬエラーが発生した時にも、クラッシュせずにインタラクティブなデバッガーがエラーをハンドルできたために端末上でデバッグできた、というようなエピソードもあります。

http://www.vpri.org/pdf/tr2003002_parkspda.pdf

まとめると、もともとの質問である「なぜJavaは純粋なオブジェクト指向言語でないのか」といえば、もし純粋なオブジェクト指向言語というのであればせめて上記のようなレベルくらいにはなっていてほしいというところ、Javaではオブジェクトとして表現されていない要素があるから、ということになるでしょう。

Hideki Ikemotoさんのプロフィール写真

オブジェクト指向プログラミングにおいて一番難しいのが継承の使い方です。安易に使うと、コードを読みにくくし、保守性を悪くします

本当は継承はない方がいいくらいなのですが、ほとんどの言語では継承がサポートされているため、継承に向き合う必要があります(特にPython)。

フレームワークが提供するクラスを継承して使う場合ではなく、他の人に継承して使ってもらう基底クラスを設計する場合、自分は次の2つを心がけてます。

  • 目的を明確化し、やれることを制限する。
  • 階層を深くしない(多段継承を避ける)。

まず、基底クラスを作るときは、目的を明確にし、使える場面と使えない場面を明確化します。「基底クラスは大体使えるけど一部はそのまま使えないから継承して差分を実装する(オーバーライド)」のは非常に危険です。

「大体使えるけど一部はそのまま使えないから継承して差分を実装する」のはほとんどの場合場当たりのつぎはぎです。本来は、基底クラスを改善して、つぎはぎではなく、きちっと差分を定義して、Templateパターンを使うのがいいでしょう。

Template Method パターン - Wikipedia
abstract class StringLister { protected abstract String formatHeader (); protected abstract String formatItem ( String item ); protected abstract String formatFooter (); public final String display ( String [] items ) { StringBuilder result = new StringBuilder ( this . formatHeader ()); for ( String item : items ) { result . append ( this . formatItem ( item )); } result . append ( this . formatFooter ()); return result . toString (); } } class PlainTextStringLister extends StringLister { protected String formatHeader () { return "" ; } protected String formatItem ( String item ) { return " - " + item + "\r\n" ; } protected String formatFooter () { return "" ; } } class HtmlStringLister extends StringLister { protected String formatHeader () { return "<ul>\r\n" ; } protected String formatItem ( String item ) { return " <li>" + item + "</li>\r\n" ; } protected String formatFooter () { return "<ul>\r\n" ; } } public class TemplateMethodTest { public static void main ( String [] argv ) { String [] items = { "First" , "Second" , "Third" }; StringLister l1 = new PlainTextStringLister (); StringLister l2 = new HtmlStringLister (); System . out . println ( l1 . display ( items )); System . out . println ( l2 . display ( items )); } } - First - Second - Third <ul> <li>First</li> <li>Second</li> <li>Third</li> </ul>

しかし現実的には非常に難しいです。なので、そういうときは他の人に「基底クラスを継承せずに別のクラスを作れ」と言っています。

もちろんロジックが重複しますが、重複したコードの方が解決が容易です。また経験上「たまたま同じ実

オブジェクト指向プログラミングにおいて一番難しいのが継承の使い方です。安易に使うと、コードを読みにくくし、保守性を悪くします

本当は継承はない方がいいくらいなのですが、ほとんどの言語では継承がサポートされているため、継承に向き合う必要があります(特にPython)。

フレームワークが提供するクラスを継承して使う場合ではなく、他の人に継承して使ってもらう基底クラスを設計する場合、自分は次の2つを心がけてます。

  • 目的を明確化し、やれることを制限する。
  • 階層を深くしない(多段継承を避ける)。

まず、基底クラスを作るときは、目的を明確にし、使える場面と使えない場面を明確化します。「基底クラスは大体使えるけど一部はそのまま使えないから継承して差分を実装する(オーバーライド)」のは非常に危険です。

「大体使えるけど一部はそのまま使えないから継承して差分を実装する」のはほとんどの場合場当たりのつぎはぎです。本来は、基底クラスを改善して、つぎはぎではなく、きちっと差分を定義して、Templateパターンを使うのがいいでしょう。

Template Method パターン - Wikipedia
abstract class StringLister { protected abstract String formatHeader (); protected abstract String formatItem ( String item ); protected abstract String formatFooter (); public final String display ( String [] items ) { StringBuilder result = new StringBuilder ( this . formatHeader ()); for ( String item : items ) { result . append ( this . formatItem ( item )); } result . append ( this . formatFooter ()); return result . toString (); } } class PlainTextStringLister extends StringLister { protected String formatHeader () { return "" ; } protected String formatItem ( String item ) { return " - " + item + "\r\n" ; } protected String formatFooter () { return "" ; } } class HtmlStringLister extends StringLister { protected String formatHeader () { return "<ul>\r\n" ; } protected String formatItem ( String item ) { return " <li>" + item + "</li>\r\n" ; } protected String formatFooter () { return "<ul>\r\n" ; } } public class TemplateMethodTest { public static void main ( String [] argv ) { String [] items = { "First" , "Second" , "Third" }; StringLister l1 = new PlainTextStringLister (); StringLister l2 = new HtmlStringLister (); System . out . println ( l1 . display ( items )); System . out . println ( l2 . display ( items )); } } - First - Second - Third <ul> <li>First</li> <li>Second</li> <li>Third</li> </ul>

しかし現実的には非常に難しいです。なので、そういうときは他の人に「基底クラスを継承せずに別のクラスを作れ」と言っています。

もちろんロジックが重複しますが、重複したコードの方が解決が容易です。また経験上「たまたま同じ実装だった」だけで、後から変わるケースも多いです。

継承が破綻する原因の多くは、継承が深くなり、ロジックが分散してしまうからです。なので自分は次の3階層までに制限しています。

  1. ルートクラス、あるいはフレームワークが提供する、安定したクラス
  2. 自前で実装する基底クラス
  3. 基底クラスから派生するクラス

理想的な例はJavaのDate and Time APIです。これほとんど継承を使っておらず、多段継承は全くしていません。finalを付けているので、これらのクラスからの継承が不可能です(ただし例外クラスは例外的に多段継承が必要です)。

JavaのDate and Time APIの設計思想が面白い - Qiita
これまでJava SE 8から入ったDate and Time API(JSR 310)は使ってこなかったのですが(Joda-Timeで特に不満はなかったので)、先日調べてみたところ、設計思想が面白…

多重継承は忌むべきものとされていますが、多段継承に比べるとまだマシです。インタフェースの継承か、mixinでいいです。mixinの例はRubyのEnumerableで、全てeachを用いて実装されています。

module Enumerable
繰り返しを行なうクラスのための Mix-in。このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。 all? -> bool [ permalink ][ rdoc ][ edit ] all? {|item| ... } -> bool all?(pattern) -> bool すべての要素が真である場合に true を返します。偽である要素があれば、ただちに false を返します。 ブロックを伴う場合は、各要素に対してブロックを評価し、すべての結果が真である場合に true を返します。ブロックが偽を返した時点で、ただちに false を返します。 自身に要素が存在しない場合は true を返します。 [PARAM] pattern: ブロックの代わりに各要素に対して pattern === item を評価します。 例 require 'set' # すべて正の数か? p Set [ 5 , 6 , 7 ] . all? { | v | v > 0 } # => true p Set [ 5 , - 1 , 7 ] . all? { | v | v > 0 } # => false p Set [ ] . all? { | v | v > 0 } # => true p Set [ 'ant' , 'bear' , 'cat' ] . all? ( /t/ ) # => false [SEE_ALSO] Array#all? any? -> bool [ permalink ][ rdoc ][ edit ] any? {|item| ... } -> bool any?(pattern) -> bool すべての要素が偽である場合に false を返します。真である要素があれば、ただちに true を返します。 ブロックを伴う場合は、各要素に対してブロックを評価し、すべての結果が偽である場合に false を返します。ブロックが真を返した時点で、ただちに true を返します。 自身に要素が存在しない場合は false を返します。 [PARAM] pattern: ブロックの代わりに各要素に対して pattern === item を評価します。 例 require 'set' p Set [ 1 , 2 , 3 ] . any? { | v | v > 3 } # => false p Set [ 1 , 2 , 3 ] . any? { | v | v > 1 } # => true p Set [ ] . any? { | v | v > 0 } # => false p Set [ 'ant' , 'bear' , 'cat' ] . any? ( /d/ ) # => false p Set [ nil , true , 99 ] . any? ( Integer ) # => true p Set [ nil , true , 99 ] . any? # => true p Set [ ] . any? # => false [SEE_ALSO] Array#any? chain(*enums) -> Enumerator::Chain [ permalink ][ rdoc ][ edit ] 自身と enums 引数を続けて繰り返す Enumerator::Chain を返します。 例 e = ( 1 .. 3 ) . chain ( [ 4 , 5 ] ) e . to_a #=> [1, 2, 3, 4, 5] [SEE_ALSO] Enumerator#+ chunk {|elt| ... } -> Enumerator [ permalink ][ rdoc ][ edit ] 要素を前から順にブロックで評価し、その結果によって要素をチャンクに分けた(グループ化した)要素を持つ Enumerator を返します。 ブロックの評価値が同じ値が続くものを一つのチャンクとして取り扱います。すなわち、ブロックの評価値が一つ前と異なる所でチャンクが区切られます。 返り値の Enumerator は各チャンクのブロック評価値と各チャンクの要素を持つ配列のペアを各要素とします。そのため、eachだと以下のようになります。 enum . chunk { | elt | key } . each { | key, ary | do_something } 例として、整数列を連続する奇数/偶数に分ける例を見てみます。「n.even?」の値が切り替わるところで区切られているのがわかるでしょう。 例 [ 3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 , 5 , 3 , 5 ] . chunk { | n | n . even? } . each { | even, ary | p [ even, ary ] } # => [false, [3, 1]] # [true, [4]] # [false, [1, 5, 9]] # [true, [2, 6]] # [false, [5, 3, 5]] このメソッドは各要素が既にソートされている場合に便利です。 以下の例では、テキスト辞書ファイルに含まれる単語の頭文字の頻度を調べています。このファイルは、Linux や macOS などで、ソートされた英語(など)の単語のリストを改行で区切って収めたものです。大文字/小文字の違いを無視するため upcase しています。 例 # ファイルのエンコーディングは実際のファイルに合わせてください。 open ( " /usr/share/dict/words " , " r:iso-8859-1 " ) { | f | f . chunk { | line | line [ 0 ] . upcase } . each { | ch, lines | p [ ch, lines . length ] } } # => ["A", 17096] # ["B", 11070] # ["C", 19901] # ["D", 10896] # ... さらにこのメソッドは以下の値を特別扱いします。 ブロックの評価値が nil もしくは :_separator であった場合、 その要素を捨てます。チャンクはこの前後で区切られます。 ブロックの評価値 :_alone であった場合はその要素は 単独のチャンクをなすものと解釈されます。 それ以外のアンダースコアで始まるシンボルを指定した場合は例外が発生します。 例 [ 1 , 2 ] . chunk { | item | :_underscore } . to_a # => RuntimeError: symbols beginning with an underscore are reserved # 「.to_a」無しだと Enumerator を返すのみで例外は発生しない nil、 :_separa

ダイヤモンド継承問題というのもあるので、これも知っておいてください。

菱形継承問題 - Wikipedia
菱形継承問題 (ひしがたけいしょうもんだい、 英 : diamond problem )は、 多重継承 を伴う オブジェクト指向 プログラミング言語 において、 クラス A を2つのクラス B と C が 継承 し、B と C の両方をクラス D が継承する際に発生するあいまいさを指す用語である。たとえば、クラス D にある メソッド が A で定義された(かつ D において オーバーライド されていない)メソッドを呼び出すとしたとき、B と C がそのメソッドを異なった形でオーバーライドしていたら、D は B と C のどちらのメソッドを継承するのか、という問題がある [ 1 ] 。 菱形継承の概念図 例えば、クラス Button は クラス Rectangle(見た目のため)と Mouse(マウスイベントのため)を継承し、Rectangle も Mouse も Object クラスを継承しているとする。ここで Button オブジェクトが equals メソッドを呼び出し、Button クラス自体にはそのメソッドは定義されていないとする。Rectangle と Mouse にはオーバーライドされた equals メソッドがそれぞれ定義されているとしたら、どちらを呼び出すべきか? これが「菱形; diamond」問題と呼ばれるのは、クラス継承図の形状が菱形になるためである。クラス A が頂上にあり、B と C がそれぞれそこから枝分かれし、D がその2つの枝を再び1つにすることで、全体として菱形を形成する。 いかにも難問のような雰囲気があるが、継承によるオブジェクト指向設計ではごくあたりまえに考えられうる形である。たとえば、 ドローソフト における各種の図形を扱うクラスを設計している、としよう。「図形」→「四角形」→「平行四辺形」と派生させ、「平行四辺形」から「長方形」や「菱形」を派生させる。ここで「正方形」は長方形であると同時に菱形でもある、という形で菱形継承があらわれる(なお、ここでいう図形の菱形は、英: rhombus であり「ダイヤモンド」ではなく、日本語の偶然である [ 疑問点 – ノート ] )。また ストリーム などでも、「読み出しストリーム」「書き込みストリーム」の両方を継承した「読み書きストリーム」といった形であらわれる。 プログラミング言語ごとにこの問題への対処法は異なる。 C++ では、デフォルトでは個々の継承経路を独立して扱う。従って D オブジェクトには実際には2つの独立した A オブジェクトが内包され、 A のメンバの使用は適切に行われる。 A から B への継承と A から C への継承が共に " virtual "(例えば " class B : virtual public A ")である場合、C++ はこれを特別に扱い、1つの A オブジェクトだけを生成し、 A のメンバは正しく動作する。 仮想継承 と仮想でない継承が混在した場合、唯一の仮想の A と個々の仮想でない継承経路ごとの A が存在することになる。 Common Lisp では、合理的なデフォルトの動作とそれをオーバーライドする能力を提供する。デフォルトでは、引数のクラス指定が最も具体的なメソッドが選択され、 サブクラス の定義内で スーパークラス が指定された順番に従う。しかし、プログラマはこれをオーバーライドでき、メソッドごとの解決順序を指定したり、メソッド結合規則を指定したりできる。 Eiffel では、ディレクティブを改名して選択することでこの問題を回避する。すなわち、上位クラスのメソッドを下位オブジェクトが使うときは明示的に指定する。これによって基底クラスのメソッド群がサブクラス間で共有でき、個々のクラスが基底クラスの個別のコピーを持っているように見なせる。 Perl や Io では、継承するクラス群を順序リストで指定することで対処する。上述の例で言えば、クラス B の上位の方がクラス C の上位の前にチェックされるので、 A のメソッドは B を通してのみ継承される。 2.1 以前の Python では、多重継承に対し、深さ優先-左から右の順でクラスのリストを生成する。Python 2.2 で導入され Python 3 では統一された、新スタイルクラス [ 2 ] では、全てのクラスは共通の基底クラス object から派生させるため、菱形継承への対処が重要になった。この時に同時に導入された順序は 2.2 でのみの採用にとどまったためここでは説明しない [ 3 ] 。Python 2.3 以降および Python 3 では C3( w:C3 linearization )が採用された [ 4 ] 。 クラスの多重継承ができない言語のうち、( Objective-C 、 PHP 、 C# 、 Java など)実装を持たないインタフェースのみを多重継承可能にしている言語がある(Objective-C ではプロトコルと呼ぶ)。実装を持たないため、インタフェースを多重継承しても、特定のメソッドやメンバ変数には常に1つの実装しかないので、あいまいさは発生しない。 Ruby は次のような Mixin アプローチにより菱形問題を回避している。クラスはクラスを単一継承し、クラスを多重継承することはできない。Rubyにはクラスの他にモジュールがあり、クラスはモジュールを多重継承することができる。モジュールには継承関係が無いので、菱形問題は発生しない。なお、クラスのクラス「Class」はモジュールのクラス「Module」のサブクラスである。 菱形問題は継承に限ったことではない。A、B、C、D という ヘッダファイル が互いに菱形を形成するように "#include" されている場合、同様の問題が発生しうる。プリプロセッサで処理された結果、A にあった宣言が B と C で異なった形に変えられ、"#ifdef" が適切に機能しないという状況がありうる。同様に、 ミドルウェア スタックでも似たような問題が発生する。A が データベース 、B と C がその キャッシュ だとした場合、D が B と C に トランザクション の コミット を要求すると、A にはコミット要求が重複して届いてしまう。 ^ 他にもたとえば実装の観点からは、 vtbl の設計が難しくなるという問題などもある。 ^ http://www.python.org/doc/newstyle/ ^ 詳細は https://python-history.blogspot.com/2010/06/method-resolution-order.html を参照のこと ^ http://www.python.org/download/releases/2.3/mro/

多重継承の問題はこれくらいです。注意は必要ですが、避けるのは難しくありません。それよりも、多段継承に注意してください。

大島 芳樹さんのプロフィール写真

他の回答にもあるように、両者は排他的なものではないので苦に合わせるやり方も色々あります。

ただ、関数型プログラミングの「副作用がない」ことを突き詰めたとしても、もし「コンピューター全体」を自分のプログラムで記述したのだとすれば、どこかではデータの書き換えを行うことにはなります。マウスポインターの位置を順次読み込んで画面に絵を描く、というようなプログラムを作っているとして、新しいマウスポインターの位置が取得されたからといって、その位置を表す新しいイミュータブルなメモリ素子を作ったり、画面の画素を毎回新しく作ったりはできませんからね。

となると、コンピューター上のどこかではデータの書き換えが起こっているわけなのですが、そのような書き換えの相互作用がなるべく起こらないようにモジュール化する技法として、オブジェクトというものがあって、書き換えがあるとしてもそのオブジェクトの責任として行うように記述するという手法はとても有用です。

最初のオブジェクト指向言語と言われている、50年以上前に作られ始めたSmalltalkというシステムでは、入力デバイスや画面といったものもオブジェクトとして表現されていました。そのシステムの設計者たちはLisp的な関数型言語にも通暁していたので、「よくデザインされたオブジェクト指向プログラミングであれば、なるべく多くのメソッドは、メソッドの引数とインスタンス変数の現在の

他の回答にもあるように、両者は排他的なものではないので苦に合わせるやり方も色々あります。

ただ、関数型プログラミングの「副作用がない」ことを突き詰めたとしても、もし「コンピューター全体」を自分のプログラムで記述したのだとすれば、どこかではデータの書き換えを行うことにはなります。マウスポインターの位置を順次読み込んで画面に絵を描く、というようなプログラムを作っているとして、新しいマウスポインターの位置が取得されたからといって、その位置を表す新しいイミュータブルなメモリ素子を作ったり、画面の画素を毎回新しく作ったりはできませんからね。

となると、コンピューター上のどこかではデータの書き換えが起こっているわけなのですが、そのような書き換えの相互作用がなるべく起こらないようにモジュール化する技法として、オブジェクトというものがあって、書き換えがあるとしてもそのオブジェクトの責任として行うように記述するという手法はとても有用です。

最初のオブジェクト指向言語と言われている、50年以上前に作られ始めたSmalltalkというシステムでは、入力デバイスや画面といったものもオブジェクトとして表現されていました。そのシステムの設計者たちはLisp的な関数型言語にも通暁していたので、「よくデザインされたオブジェクト指向プログラミングであれば、なるべく多くのメソッドは、メソッドの引数とインスタンス変数の現在の値を使って関数的に値を計算するだけとし、メッセージを受け取ってインスタンス変数を書き換えるときも、最後のところで計算された結果を一括してインスタンス変数に書くようにするのが良い」という知見は持っていました。そのようなある意味当たり前の"best practice"が伝わらず、一つの値だけを直接変更するような"setter"が後々よく書かれてしまい、外からそのように書き換えが行えるようなひとまとめにしたデータでしかないものが「オブジェクト」と呼ばれるようになったために混乱をきたした、という経緯があります。

関数型プログラミングのメリットのひとつとしては「高階関数」、つまり関数そのものを受け渡してプログラムを書けるようにするというものがあります。高階関数ももちろんSmalltalkのかなり初期バージョンに近いものから存在しており、mapやreduceやfilterに該当する機能も当たり前に準備されていました。ただ、こちらも引数として渡す「ブロック」や関数は、本来インスタンス変数の書き換えをするようなものを渡すべきではない、という知見もありました。それはオブジェクトが自分の内臓を切り取って「好きにしてくれ」と他のオブジェクトに渡しているようなものである、というような喩えもしばしば使われていました。ただ、このようなスタイルもあまり深く考えられることなく、値の書き換えをするようなブロックを他のオブジェクトに渡すようなパターンもしばしば使われていましたが、これもあまり望ましくないスタイルだと言えます。

ちょっと長くなってしまいましたが、結論としてはオブジェクト指向言語という肩書きがついた言語が作られていた初期から、その前から存在していた関数型プログラミングとの良いところどりをしてプログラムを書く、という実装は存在した、ということになります。そして、ちゃんとした原則に従ってプログラムを書けばとても強力なプログラムが簡潔に書ける、ということでもあります。

Kengo Nakajimaさんのプロフィール写真

嫌いというか問題と思うのは、さじ加減を越えて使うと害のほうが大きくなるのに、やり過ぎかどうかをわかるための指針を思想の中に含んでいないところです。ほかの「なんとか志向」もだいたいそうだと思いますが、オブジェクト指向は特に広く普及したので、害が大きくなっていると思います。さじ加減がわからないプログラミングの経験が少ないエンジニアは、常に多いので。

最近は、オブジェクト指向を多用しはじめて30年ぐらいたって、ようやく、副作用がだいぶはっきりしてきたかなと思います。 もうすぐ、副作用を避けながらうまく使う手段が言語化でき、共有可能になるんじゃないかな、と思ってます。そうなれば、経験が少ない人も、誤用を減らせる、、といいなあ。

近いうちに、といってもあと10年20年かかるでしょうけど、この壮大な実験の結果が総括されるのだと思います。

好きなところは、さじ加減を間違わずに設計できたら、すごく綺麗に決まることですね。 Java、Ruby、C#、JavaScriptなどを使った開発で、いい感じにAPIを整理できたときはとても嬉しいです。

Quora Userさんのプロフィール写真

オブジェクト指向プログラミングの構成要素ごとの有用性が再吟味され、良し悪しもわかってきて、是是非非で悪いところは適宜置きかえていった方がいいのでは、となってきたとき、「オブジェクト指向プログラミング」という言葉で指す総称的概念として扱うことの意義が侵食されてきているのではないでしょうか。「熟成して普及し、名前を呼ぶ必要がなくなった」というような状況に向かっているのかもしれません。

たとえば、昨今、「構造化プログラミング」ってわざわざ言いませんよね。「アスペクト指向プログラミング」という鳴り物入りででてきたやつが、今は「AspectJ使ってます」「DI使ってます」ぐらいで済むことになっています。DIコンテナなんて言うこともなくなりましたかね。Spring Boot使ってます、でおしまいです。

なので、クラスならクラス、継承なら継承、オブジェクトならオブジェクトという言語機能名称を、パーツとしてそれぞれ使うときにそう呼べば良いような気がします。「オブジェクト指向プログラミング」と総称して言う必要って、あるのかな、と。あるとしてそれって実体として何だっけかなと。

たとえば、自分では、おおむねクラスライブラリを呼びだしたりはするし(String#toUpperCase()とか)、ときおり自前のデータ構造のためにクラス定義もします。まれに継承もします。でも関数型でmapとfilterを連ねる方がよ

オブジェクト指向プログラミングの構成要素ごとの有用性が再吟味され、良し悪しもわかってきて、是是非非で悪いところは適宜置きかえていった方がいいのでは、となってきたとき、「オブジェクト指向プログラミング」という言葉で指す総称的概念として扱うことの意義が侵食されてきているのではないでしょうか。「熟成して普及し、名前を呼ぶ必要がなくなった」というような状況に向かっているのかもしれません。

たとえば、昨今、「構造化プログラミング」ってわざわざ言いませんよね。「アスペクト指向プログラミング」という鳴り物入りででてきたやつが、今は「AspectJ使ってます」「DI使ってます」ぐらいで済むことになっています。DIコンテナなんて言うこともなくなりましたかね。Spring Boot使ってます、でおしまいです。

なので、クラスならクラス、継承なら継承、オブジェクトならオブジェクトという言語機能名称を、パーツとしてそれぞれ使うときにそう呼べば良いような気がします。「オブジェクト指向プログラミング」と総称して言う必要って、あるのかな、と。あるとしてそれって実体として何だっけかなと。

たとえば、自分では、おおむねクラスライブラリを呼びだしたりはするし(String#toUpperCase()とか)、ときおり自前のデータ構造のためにクラス定義もします。まれに継承もします。でも関数型でmapとfilterを連ねる方がよっぽどロジックの中核だったりするので、「自分、今オブジェクト指向プログラミングしてるんだっけ?」と思います。ひょっとすると「たまにクラス定義してるだけ」じゃないか? と。

山本 聡さんのプロフィール写真

時間の問題、とまでは言えなさそうですが、じわじわとそうなっていくように思います。
将来的には、おそらく。

オブジェクト指向の中でも特に複雑なクラスベースの継承と多態、それがまずなくなっていくでしょう。
(これがなくなればもはやオブジェクト指向プログラミングとは呼べない気もします。)

継承がない言語がいくつかメジャー言語で出てきているので、継承や多態を使ったコードは移植性が悪くなってしまいます。また、移植性は除いても継承は全てのフィールドメソッドを継承してしまうという辛さがある機能で代替手段もあるため、書かれなくなっていくでしょう。

継承がない言語が登場して広く使われているのも、継承が必須ではない機能だったからです。

言語特性やライブラリ特性があるのですが、JavaScriptやTypeScriptでReactとか使っている開発者はどんどん増えていますが、それらを使っていると、クラスベースのオブジェクト指向(継承、多態)は、使う場面がまずないので使わないです。

使わないでも普通にアプリとか作れるもんですよ。

ECサイトエンジンとか、Webホワイトボードお絵かきツールとか、あと、何作ったっけ、いろいろ作ってますが、もうここ5年くらいは、クラスってのは使ってない気がします。thisの微妙すぎる挙動とか、以前は必死で学んだ気がしますが、使わないのでもう忘れました。

他の人がクラスベースで作ったコードを修

時間の問題、とまでは言えなさそうですが、じわじわとそうなっていくように思います。
将来的には、おそらく。

オブジェクト指向の中でも特に複雑なクラスベースの継承と多態、それがまずなくなっていくでしょう。
(これがなくなればもはやオブジェクト指向プログラミングとは呼べない気もします。)

継承がない言語がいくつかメジャー言語で出てきているので、継承や多態を使ったコードは移植性が悪くなってしまいます。また、移植性は除いても継承は全てのフィールドメソッドを継承してしまうという辛さがある機能で代替手段もあるため、書かれなくなっていくでしょう。

継承がない言語が登場して広く使われているのも、継承が必須ではない機能だったからです。

言語特性やライブラリ特性があるのですが、JavaScriptやTypeScriptでReactとか使っている開発者はどんどん増えていますが、それらを使っていると、クラスベースのオブジェクト指向(継承、多態)は、使う場面がまずないので使わないです。

使わないでも普通にアプリとか作れるもんですよ。

ECサイトエンジンとか、Webホワイトボードお絵かきツールとか、あと、何作ったっけ、いろいろ作ってますが、もうここ5年くらいは、クラスってのは使ってない気がします。thisの微妙すぎる挙動とか、以前は必死で学んだ気がしますが、使わないのでもう忘れました。

他の人がクラスベースで作ったコードを修正するときは仕方なく作る場合があったりするくらいです。


継承がなくなったあとはオブジェクト内のフィールドを使ったメソッドがなくなっていってほしいです。

メソッドは副作用があるためにコードの読み取りしにくくなり、テストも書きにくいので、

状態は全部引数として渡す形式の関数にしてメソッドを減らしていくと、もっとプログラムはシンプルになり、より生産性が高まります。

なので、私が書くプログラムは超読みやすいです。でもそういうプログラム書くと、誰にでも容易に引き継ぎしてもらえるので技術がないと思われるので、なかなか世渡りは難しいでんな。

メソッドありきのプライベート変数とかも減っていくとヨシです。

こうなるとますますオブジェクト指向から遠ざかり、オブジェクトは単なる階層データ構造になり状態の保存復帰も単純でやりやすくなります。


自分がクラスベースのオブジェクト指向言語を使っていたときの事をよーく思い出して、改めて考えてみたのですが、

何らかのツリー型のデータ構造を作るときにオブジェクト内にオブジェクトを持たせるのが普通で、連想配列内に連想配列をもたせたとしても、連想配列自体がDictionaryクラスだったりするので、どうしてもオブジェクトを意識する必要があるので、なかなか、オブジェクト指向を使わないといっても「それは無理!」となるんだとは思うのですが、

その階層型のデータ構造を維持する仕組みは、オブジェクト指向とは区分けして考えてもいいんじゃないかと思います。
構造体で階層データ構造をつくれなくはなさそうですが、その方が厄介ですよね。なので、階層型データ構造にクラスとかオブジェクトはつかわざるおえない。

オブジェクト指向が厄介なのは、
1. 継承(と多態)
2. オブジェクトがメソッドを持てる
3. 階層型データ構造
これらが全部オブジェクト指向に含まれるという所があって、

1.2.を使うのをやめていくと、オブジェクト指向から脱することができるかな、と考えています。
3.を捨てるには、階層データをオブジェクト以外で表現できたらいいですが、それは困難なので、まあ、そこは既存のままでいいだろうという所かなと。

JSがプログラミング言語としてよかったのは、オブジェクトの生成が「{}」だけでできて、ゆえにJSONが即座に階層データとしてオブジェクトになるということ、そしてGCでオブジェクトの破棄など全く何も考えずに済むので、階層データを即時に定義したりして破棄など考えずにコード組めるところもよかったのだろうな、と改めて思いました。


平野 雄一 さんからコメント頂いたので追記します。

ゲームのキャラクターや弾はインスタンス?

ということだったのですが、私はゲーム分野のプログラマーではないのですが、かなりシンプルなゲームの実装としてこれは参考になります。

純粋な JavaScript を使ったブロック崩しゲーム - ゲーム開発 | MDN

純粋な JavaScript を使ったブロック崩しゲーム - ゲーム開発 | MDN
このステップバイステップのチュートリアルでは、すべて JavaScript だけで書かれた、 HTML5 の で表示できる簡単な MDN ブロック崩しゲームを作ります。

単純なフラグと当たり判定になっています。

でも、別にこれがブロックごとにオブジェクトのインスタンスと球がインスタンスでも、それは複雑になりようがないので、問題ないです。

オブジェクト指向のオブジェクトの生成(インスタンス)とか、とインスタンスがインスタンスを所有して、そのインスタンスも複数のインタンスからできている、というデータの階層構造的なオブジェクトの使い方は普通で、それは素直なプログラミングだと思います。

ツリー型のデータ構造としてのオブジェクトの利用については複雑なことはないので、有害だとは思わないです。

Hideki Ikemotoさんのプロフィール写真

「オブジェクト指向」自体が様々な解釈をされ、その指すものが各自バラバラなのでなんとも言い難いところがありますが、自分なりの答えを。

ちなみに自分は言語的にはObjective-C、Java、Rubyあたりを意識していて(あとSmalltalkは使ったことないけど先駆者として)、C++には否定的です。

質問の答えを一言でいうと、構造化プログラミングだけでは不十分だからです。

構造化プログラミングと、オブジェクト指向プログラミングの一番の違いは、ここで書いたように、メソッドを動的に解決するか否かだと考えています。C++はデフォルトが静的であることが他の言語と大きく違う点です。

この違いは呼び出しメソッドを「静的」に解決するか、それとも「動的」に解決するかの違いです。静的に解決するなら単にポインタの加減算で済みますが、動的に解決する場合は仮想関数テーブルの検索が必要です。

もし動的かどうかではなく、単にデータと紐づくメソッドの存在だけなら、抽象データ型という概念になります。

この抽象データ型は文法こそオブジェクト指向プログラミング言語と同様ですし、重要な要素ではありますが、これだけなら概念的には構造化プログラミングの範囲に留まります。

抽象データ型 - Wikipedia
抽象データ型 (ちゅうしょうデータがた、 英 : abstract data type 、ADT)とは、データ構造とその操作手続きを定義した データ型 、またはデータ抽象 [ 注釈 1 ] の方法の1つ。通常のデータ型であれば変数宣言で変数に束縛されるものは値であるが、抽象データ型の世界において値に相当するものはデータ構造とその操作 [ 注釈 2 ] のまとまり [ 注釈 3 ] である。 抽象データ型を用いない場合、データ構造またはデータの操作手続きのアルゴリズムの変更を行うとソースコード中にその変更部分が散在してしまい規模によっては修正困難となるが、データとその操作がひとまとめに記載されることになる抽象データ型においては、型の定義における実装部分を変更するだけで修正が完了する。 1960年代の 構造化プログラミング の時点で既に、(よく知られている)段階的詳細化といった手法や 制御構造 の利用の励行などの他、データ構造とコードを連携させ、データ構造の変更を行うと変更部分がソースコード中に散在してしまうというそれ以前のプログラミングにおける問題点への対処にもなる、データ抽象に関する議論は現れている(『構造化プログラミング』(日本版はサイエンス社、1975年)の、全体の約半分は ダイクストラ による考察だが、残り約半分は ホーア と ダール ( Simula の設計者)によるデータ論である)。抽象データ型はデータ抽象の具体的手法として1974年に バーバラ・リスコフ の提案した言語 CLU において提案された。 プログラムが実装されたとき、抽象データ型は実装を隠蔽するインタフェースを表す。実装は将来において変更されうるので、抽象データ型のユーザーは実装ではなくインタフェースに関心がある。 抽象データ型の強みはユーザーから実装が隠蔽されていることである。インタフェースのみが公開されるのである。このことは、抽象データ型がいろいろな方法で実装されうることを意味するが、インタフェースに忠実な限りユーザープログラムは影響を受けないのである。 例えば、二分探索木抽象データ型はいくつかの方法で実装できる。例えば、 二分木 、 AVL木 、 赤黒木 、配列である。しかし実装に関わらず二分探索木は「挿入」「削除」「検索」といった同じ操作が可能である。 抽象データ構造 とは、実際の データ構造 による実装に関わらず、操作の集合とその 計算量 により定義されるデータのための抽象的な領域のことである。 具体的なデータ構造の選択がアルゴリズムの効率的な実装にとって重要である一方、抽象データ構造の選択は効率的なアルゴリズムの設計とその計算量の推定にとって極めて重要である。 この概念はプログラミング言語の理論で用いられる抽象データ型の概念に非常に近い。多くの抽象データ構造や抽象データ型の名前は具体的なデータ構造の名前と一致する。 有理数 は計算機においてはそのまま扱うことはできない。有理数の抽象データ型は以下のように定義できる。 コンストラクタ : 2つの整数 a {\displaystyle a} 、 b {\displaystyle b} を用いて有理数の抽象データ型のインスタンスを作成する。ここで a {\displaystyle a} は分子で b {\displaystyle b} は分母である。 関数 : 加算,減算,乗算,除算,指数計算,比較,約分,実数への変換 完全な記述にするためには、それぞれの関数はデータに言及して記述されるべきである。例えば、有理数 a / b {\displaystyle a/b} と c / d {\displaystyle c/d} を乗算するときには結果は a c / b d {\displaystyle ac/bd} と定義される。通常は入力、出力、実行前条件、実行後条件、抽象データ型に対する仮定も記述される。 ^ データ抽象( 英 : data abstraction )とは、データ型の詳細定義とその操作に関する手続きを情報の局所性が高まるようにソースコード中の一カ所にまとめて記述するための記法のことを言う。情報が一カ所に局所的にまとめて記載されているため、非公開(private)部分の変更であればその定義部分の詳細を変更するだけでソースコード全体の修正が完了する。 データ型の詳細定義とその操作手続きの局所的記述を実現する方法は複数あり、抽象データ型はその一例である。 ^ 言語に応じて名称が異なり、 C++ であればメンバ関数(member function)、 Java であればメソッド(method)と呼ばれる ^ データ型の値に相当するこのまとまりのことを抽象データ型の実体(インスタンス , instance)と呼ぶ。 落水 浩一郎『ソフトウェア工学実践の基礎 -分析・設計・プログラミング』日科技連出版社、1993年。 飯泉 純子, 大槻 繁『 ずっと受けたかったソフトウェア設計の授業 』翔泳社 。 https://books.google.co.jp/books?id=gJhtVWzM4BsC&printsec=frontcover&hl=ja&sa=X&ei=Z4ubUf_NL43mkgWD1YGoDQ&ved=0CDwQ6AEwAA*v=onepage&q&f=false 。 D.L.Parnas (1975), Use of the concept of transparency in the design of hierarchically structured systems , http://ivizlab.sfu.ca/arya/Papers/SW/TranspDesignHierSys.pdf D.L.Parnas (1971), Information Distribution Aspects of Design Methodology , http://cseweb.ucsd.edu/~wgg/CSE218/Parnas-IFIP71-information-distribution.PDF B.H.Liskov, S.N.Zilles (1974), Programming with Abstract Data Type , http://www.znu.ac.ir/members/afsharchim/lectures/p50-liskov.pdf Niklaus Wirth (1971), Program Development by Stepwise Refinement , Communications of the ACM, Vol. 14, No. 4, April 1971, pp. 221-227 , http://sunnyday.mit.edu/16.355/wirth-refinement.html N.Gehani (1980), Program Development by Stepwise Refinement and Related Topics , http://www3.alcatel-lucent.c

そしてもし、ひたすらトップダ

「オブジェクト指向」自体が様々な解釈をされ、その指すものが各自バラバラなのでなんとも言い難いところがありますが、自分なりの答えを。

ちなみに自分は言語的にはObjective-C、Java、Rubyあたりを意識していて(あとSmalltalkは使ったことないけど先駆者として)、C++には否定的です。

質問の答えを一言でいうと、構造化プログラミングだけでは不十分だからです。

構造化プログラミングと、オブジェクト指向プログラミングの一番の違いは、ここで書いたように、メソッドを動的に解決するか否かだと考えています。C++はデフォルトが静的であることが他の言語と大きく違う点です。

この違いは呼び出しメソッドを「静的」に解決するか、それとも「動的」に解決するかの違いです。静的に解決するなら単にポインタの加減算で済みますが、動的に解決する場合は仮想関数テーブルの検索が必要です。

もし動的かどうかではなく、単にデータと紐づくメソッドの存在だけなら、抽象データ型という概念になります。

この抽象データ型は文法こそオブジェクト指向プログラミング言語と同様ですし、重要な要素ではありますが、これだけなら概念的には構造化プログラミングの範囲に留まります。

抽象データ型 - Wikipedia
抽象データ型 (ちゅうしょうデータがた、 英 : abstract data type 、ADT)とは、データ構造とその操作手続きを定義した データ型 、またはデータ抽象 [ 注釈 1 ] の方法の1つ。通常のデータ型であれば変数宣言で変数に束縛されるものは値であるが、抽象データ型の世界において値に相当するものはデータ構造とその操作 [ 注釈 2 ] のまとまり [ 注釈 3 ] である。 抽象データ型を用いない場合、データ構造またはデータの操作手続きのアルゴリズムの変更を行うとソースコード中にその変更部分が散在してしまい規模によっては修正困難となるが、データとその操作がひとまとめに記載されることになる抽象データ型においては、型の定義における実装部分を変更するだけで修正が完了する。 1960年代の 構造化プログラミング の時点で既に、(よく知られている)段階的詳細化といった手法や 制御構造 の利用の励行などの他、データ構造とコードを連携させ、データ構造の変更を行うと変更部分がソースコード中に散在してしまうというそれ以前のプログラミングにおける問題点への対処にもなる、データ抽象に関する議論は現れている(『構造化プログラミング』(日本版はサイエンス社、1975年)の、全体の約半分は ダイクストラ による考察だが、残り約半分は ホーア と ダール ( Simula の設計者)によるデータ論である)。抽象データ型はデータ抽象の具体的手法として1974年に バーバラ・リスコフ の提案した言語 CLU において提案された。 プログラムが実装されたとき、抽象データ型は実装を隠蔽するインタフェースを表す。実装は将来において変更されうるので、抽象データ型のユーザーは実装ではなくインタフェースに関心がある。 抽象データ型の強みはユーザーから実装が隠蔽されていることである。インタフェースのみが公開されるのである。このことは、抽象データ型がいろいろな方法で実装されうることを意味するが、インタフェースに忠実な限りユーザープログラムは影響を受けないのである。 例えば、二分探索木抽象データ型はいくつかの方法で実装できる。例えば、 二分木 、 AVL木 、 赤黒木 、配列である。しかし実装に関わらず二分探索木は「挿入」「削除」「検索」といった同じ操作が可能である。 抽象データ構造 とは、実際の データ構造 による実装に関わらず、操作の集合とその 計算量 により定義されるデータのための抽象的な領域のことである。 具体的なデータ構造の選択がアルゴリズムの効率的な実装にとって重要である一方、抽象データ構造の選択は効率的なアルゴリズムの設計とその計算量の推定にとって極めて重要である。 この概念はプログラミング言語の理論で用いられる抽象データ型の概念に非常に近い。多くの抽象データ構造や抽象データ型の名前は具体的なデータ構造の名前と一致する。 有理数 は計算機においてはそのまま扱うことはできない。有理数の抽象データ型は以下のように定義できる。 コンストラクタ : 2つの整数 a {\displaystyle a} 、 b {\displaystyle b} を用いて有理数の抽象データ型のインスタンスを作成する。ここで a {\displaystyle a} は分子で b {\displaystyle b} は分母である。 関数 : 加算,減算,乗算,除算,指数計算,比較,約分,実数への変換 完全な記述にするためには、それぞれの関数はデータに言及して記述されるべきである。例えば、有理数 a / b {\displaystyle a/b} と c / d {\displaystyle c/d} を乗算するときには結果は a c / b d {\displaystyle ac/bd} と定義される。通常は入力、出力、実行前条件、実行後条件、抽象データ型に対する仮定も記述される。 ^ データ抽象( 英 : data abstraction )とは、データ型の詳細定義とその操作に関する手続きを情報の局所性が高まるようにソースコード中の一カ所にまとめて記述するための記法のことを言う。情報が一カ所に局所的にまとめて記載されているため、非公開(private)部分の変更であればその定義部分の詳細を変更するだけでソースコード全体の修正が完了する。 データ型の詳細定義とその操作手続きの局所的記述を実現する方法は複数あり、抽象データ型はその一例である。 ^ 言語に応じて名称が異なり、 C++ であればメンバ関数(member function)、 Java であればメソッド(method)と呼ばれる ^ データ型の値に相当するこのまとまりのことを抽象データ型の実体(インスタンス , instance)と呼ぶ。 落水 浩一郎『ソフトウェア工学実践の基礎 -分析・設計・プログラミング』日科技連出版社、1993年。 飯泉 純子, 大槻 繁『 ずっと受けたかったソフトウェア設計の授業 』翔泳社 。 https://books.google.co.jp/books?id=gJhtVWzM4BsC&printsec=frontcover&hl=ja&sa=X&ei=Z4ubUf_NL43mkgWD1YGoDQ&ved=0CDwQ6AEwAA*v=onepage&q&f=false 。 D.L.Parnas (1975), Use of the concept of transparency in the design of hierarchically structured systems , http://ivizlab.sfu.ca/arya/Papers/SW/TranspDesignHierSys.pdf D.L.Parnas (1971), Information Distribution Aspects of Design Methodology , http://cseweb.ucsd.edu/~wgg/CSE218/Parnas-IFIP71-information-distribution.PDF B.H.Liskov, S.N.Zilles (1974), Programming with Abstract Data Type , http://www.znu.ac.ir/members/afsharchim/lectures/p50-liskov.pdf Niklaus Wirth (1971), Program Development by Stepwise Refinement , Communications of the ACM, Vol. 14, No. 4, April 1971, pp. 221-227 , http://sunnyday.mit.edu/16.355/wirth-refinement.html N.Gehani (1980), Program Development by Stepwise Refinement and Related Topics , http://www3.alcatel-lucent.c

そしてもし、ひたすらトップダウンで分割を繰り返すことだけで正しいプログラムが作れるなら、構造化プログラミングだけで十分です。

しかし分割だけでは不十分なことが分かっています。

その理由としてプログラムの規模が大きくなっているのも1つですが、これはあくまで量の問題です。問題は「非同期(あるいはマルチスレッド)」、そしてそれに伴う「競合」、あるいは「例外処理」です。

昔と比べると非同期処理、マルチスレッドの範囲が増えています。バッチ処理はオフライン処理もありましたが、今は基本的にオンラインで並列に処理が走るようになっています。GUIは非同期処理でないと成立しません。HTTPも2から非同期になっています。

そして、分割した詳細の1つに非同期処理が入ると、全体に波及します。JavaScriptで言えばasync関数の呼び出し元はasyncであるか、Promiseをthen, catchで「例外処理」するかどちらかです。

また、分割した詳細で例外処理が必要な場合も、呼び出し元で適切に処理するか、トップで処理するか、どちらにしても全体に波及します。

そして「分割を繰り返すことだけ」で設計をすると、細部で全体に影響が出る問題が出たときに取り返しがつかなくなります。これがウォーターフォール開発が炎上しがちな理由です。

となると、「分割を繰り返すことだけ」のトップダウン設計手法には問題があり、全体と細部を同時に見る、トップダウンとボトムアップの組み合わせでの設計が必要です。

ボトムアップの設計によって全体に影響する問題がどのようなものかを早めに明らかにし、トップダウンの設計でその全体に影響する問題を仕組みで解決します。

トップダウンの設計は、例えばコアロジックを非同期処理も例外設計も不要な形で分離する場合もあれば、イベントソーシングのように競合が起きづらいアーキテクチャを選定する手もあります。

そのトップダウンとボトムアップの組み合わせでの設計に適しているのがメソッドを動的に解決する、オブジェクト指向プログラミングです。小さな部品から作り始めることも、部品ができたものと仮定して全体を作って動かしてみることもできます。

今のところはそんな感じで考えています。

Yukihiro Matsumotoさんのプロフィール写真

仮にあなたがオブジェクト指向という考え方も名前も存在しない世界に転生したとして、効率と安全を考えて自然にあの形を自分で思いつくかというとかなり疑問です。あなたが自然(かつ唯一)だと思っているのも、誰かが思いついたものを学んで受け入れたのでそう考えるだけでしょう。

またその世界に、あの形以外のプログラミングの形が存在しえないと考えるのも不自然です。複数のプログラミングの形が存在する場合、それらを区別するためにも名前を付けるのは重要ではないかと思います。

昔々、デザインパターンという考え方が流行した時、その最大の功績はプログラミングにおいて度々登場するパターンに名前を付けたことで、みながそのパターンの存在を認識し、共通の認識で議論できるようになったことでした。それと同じようにオブジェクト指向という名前を付けることによって、みながそのパラダイムを認識できるようになり議論できるようになったのだと思います。

ただ、オブジェクト指向の場合、細部の定義が曖昧なままだったので、議論がすれ違いになりがちという問題があったのですが、それはそれとして。

Quora Userさんのプロフィール写真

関数型プログラミングは、言語はあまり関係なくて、以下のような考え方、スタイルなんですよ。

  • 可変状態はバグの巣窟で、可読性・理解性・予測性・再利用性・スケーラビリティを損う、多くの害悪の故郷。無ければないほどよい。避けられないなら識別弁別して管理する。かろうじてそうできた時のみ存在を許す。
  • 純粋関数及びその合成は上記を達成するための手段の一つとして使い勝手が良い
  • それを達成しようとした時に「オブジェクト指向」と論理的な理由や必然性なく、何故かまとめられて総称して呼ばれる言語機能群のうちの多くが、あるいはそう名乗られて行われてきたアプローチの多くが邪魔になる、もしくは不要
    • 例えば、クラス名の文脈の元でのメソッド名を考える時間が無駄。あるいはメソッドをまとめる単位として意味のあるクラス名を考える時間が無駄。誤解を生む名前をつけるわけには行かないのでプログラミングの難易度が上がってしまっている。果てはSRP原則とか言って際限なくクラス粒度が細かくなったり、苦肉の策としてデザパタなるものを生み出した。
    • (クラスベースオブジェクト指向に関して)オブジェクト万能信仰から生まれたクラスの機能過剰さ。型、モジュール単位、ファイル分割単位、メソッドとデータの結合、抽象型、インターフェース、vtable保持、差分プログラミング、ユニットテスト単位を兼ね備えるなど、ないわー。

良い悪いはなくて、上のような考え方に沿お

関数型プログラミングは、言語はあまり関係なくて、以下のような考え方、スタイルなんですよ。

  • 可変状態はバグの巣窟で、可読性・理解性・予測性・再利用性・スケーラビリティを損う、多くの害悪の故郷。無ければないほどよい。避けられないなら識別弁別して管理する。かろうじてそうできた時のみ存在を許す。
  • 純粋関数及びその合成は上記を達成するための手段の一つとして使い勝手が良い
  • それを達成しようとした時に「オブジェクト指向」と論理的な理由や必然性なく、何故かまとめられて総称して呼ばれる言語機能群のうちの多くが、あるいはそう名乗られて行われてきたアプローチの多くが邪魔になる、もしくは不要
    • 例えば、クラス名の文脈の元でのメソッド名を考える時間が無駄。あるいはメソッドをまとめる単位として意味のあるクラス名を考える時間が無駄。誤解を生む名前をつけるわけには行かないのでプログラミングの難易度が上がってしまっている。果てはSRP原則とか言って際限なくクラス粒度が細かくなったり、苦肉の策としてデザパタなるものを生み出した。
    • (クラスベースオブジェクト指向に関して)オブジェクト万能信仰から生まれたクラスの機能過剰さ。型、モジュール単位、ファイル分割単位、メソッドとデータの結合、抽象型、インターフェース、vtable保持、差分プログラミング、ユニットテスト単位を兼ね備えるなど、ないわー。

良い悪いはなくて、上のような考え方に沿おうとするかどうかです。

参考

Quora Userさんのプロフィール写真

もちろん可能です。両者は互いに直行する概念で同時に利用することができます。Scalaという言語なんてまさに関数型オブジェクト指向言語を謳っています。

オブジェクト指向プログラミングの一番重要なポイントはインターフェースと実装の分離です。関数を定義するときには引数には実際のクラスではなくて抽象クラス、interface、traitなどを渡すように指定します。このようにして利用者はそのクラスの内部実装を気にすることなく、インターフェース、つまり入出力の形式のみに気を付ければよく、また別の実装に切り替えることも容易です。

関数型プログラミングの最も重要な概念は副作用の分離であり、変数は上書きせず、極力純粋関数を使っていくというスタイルです。大雑把に言えば

  1. y = f(x) 
  2. z = g(y) 
  3. w = h(z) 
  4. ... 

のように式を書き続けていくことでプログラムを作るということです。

では両者を両立するにはどうすればいいのでしょうか?答えは簡単で、クラスは基本的にはコンストラクタで各要素に値をセットしたら以降は変更しないということに注意すればいいです。そしてクラスが実装するインターフェースも基本的には純粋関数のみです。こうすることで値の不変性を保ちつつも、クラスを用いて複数の変数をまとめ、同時に内部実装は隠蔽してインターフェースに集中するような開発をすることができます。

そしてこの方式を最もやりやすい言語は

脚注

もちろん可能です。両者は互いに直行する概念で同時に利用することができます。Scalaという言語なんてまさに関数型オブジェクト指向言語を謳っています。

オブジェクト指向プログラミングの一番重要なポイントはインターフェースと実装の分離です。関数を定義するときには引数には実際のクラスではなくて抽象クラス、interface、traitなどを渡すように指定します。このようにして利用者はそのクラスの内部実装を気にすることなく、インターフェース、つまり入出力の形式のみに気を付ければよく、また別の実装に切り替えることも容易です。

関数型プログラミングの最も重要な概念は副作用の分離であり、変数は上書きせず、極力純粋関数を使っていくというスタイルです。大雑把に言えば

  1. y = f(x) 
  2. z = g(y) 
  3. w = h(z) 
  4. ... 

のように式を書き続けていくことでプログラムを作るということです。

では両者を両立するにはどうすればいいのでしょうか?答えは簡単で、クラスは基本的にはコンストラクタで各要素に値をセットしたら以降は変更しないということに注意すればいいです。そしてクラスが実装するインターフェースも基本的には純粋関数のみです。こうすることで値の不変性を保ちつつも、クラスを用いて複数の変数をまとめ、同時に内部実装は隠蔽してインターフェースに集中するような開発をすることができます。

そしてこの方式を最もやりやすい言語はもちろんRustです!私はいつもRustで関数型オブジェクト指向プログラミングを実践しています。Rustは変数を宣言するときにmutを付けなければデフォルトで不変な変数として扱われます。また、関数に渡すときにも可変参照(&mut T)を極力を極力封印すれば関数型的な書き方を達成できます。またRustのTraitはJavaのInterfaceをより強力にしたものであり、特に関数の引数が複数のTraitを実装することを要請することもできます。

The Rust Programming Language 日本語版
第2章で触れた通り、変数は標準で不変になります。これは、 Rustが提供する安全性や簡便な並行性の利点を享受する形でコードを書くための選択の1つです。 ところが、まだ変数を可変にするという選択肢も残されています。 どのように、そしてなぜRustは不変性を推奨するのか、さらには、なぜそれとは違う道を選びたくなることがあるのか見ていきましょう。 変数が不変であると、値が一旦名前に束縛されたら、その値を変えることができません。 これを具体的に説明するために、 projects ディレクトリに cargo new --bin variables コマンドを使って、 variables という名前のプロジェクトを生成しましょう。 それから、新規作成した variables ディレクトリで、 src/main.rs ファイルを開き、 その中身を以下のコードに置き換えましょう。このコードはまだコンパイルできません: ファイル名: src/main.rs fn main() { let x = 5; println!("The value of x is: {}", x); // xの値は{}です x = 6; println!("The value of x is: {}", x); } これを保存し、 cargo run コマンドでプログラムを走らせてください。次の出力に示されているようなエラーメッセージを受け取るはずです: $ cargo run Compiling variables v0.1.0 (file:///projects/variables) error[E0384]: cannot assign twice to immutable variable `x` (不変変数`x`に2回代入できません) --> src/main.rs:4:5 | 2 | let x = 5; | - | | | first assignment to `x` | (`x`への最初の代入) | help: consider making this binding mutable: `mut x` 3 | println!("The value of x is: {}", x); 4 | x = 6; | ^^^^^ cannot assign twice to immutable variable For more information about this error, try `rustc --explain E0384`. error: could not compile `variables` due to previous error この例では、コンパイラがプログラムに潜むエラーを見つけ出す手助けをしてくれることが示されています。 コンパイルエラーは、イライラすることもあるものですが、まだプログラムにしてほしいことを安全に行えていないだけということなのです。 エラーが出るからといって、あなたがいいプログラマではないという意味ではあり ません ! 経験豊富なRustaceanでも、コンパイルエラーを出すことはあります。 このエラーは、エラーの原因が 不変変数xに2回代入できない であると示しています。不変な x という変数に別の値を代入しようとしたからです。 以前に不変と指定された値を変えようとした時に、コンパイルエラーが出るのは重要なことです。 なぜなら、この状況はまさしく、バグに繋がるからです。コードのある部分は、 値が変わることはないという前提のもとに処理を行い、別の部分がその値を変更していたら、 最初の部分が目論見通りに動いていない可能性があるのです。このようなバグは、発生してしまってからでは原因が追いかけづらいものです。 特に第2のコード片が、値を 時々 しか変えない場合、尚更です。 Rustでは、値が不変であると宣言したら、本当に変わらないことをコンパイラが担保してくれます。 つまり、コードを読み書きする際に、どこでどうやって値が変化しているかを追いかける必要がなくなります。 故にコードを通して正しいことを確認するのが簡単になるのです。 しかし、可変性は時として非常に有益なこともあります。変数は、標準でのみ、不変です。つまり、 第2章のように変数名の前に mut キーワードを付けることで、可変にできるわけです。この値が変化できるようにするとともに、 mut により、未来の読者に対してコードの別の部分がこの変数の値を変える可能性を示すことで、その意図を汲ませることができるのです。 例として、 src/main.rs ファイルを以下のように書き換えてください: ファイル名: src/main.rs fn main() { let mut x = 5; println!("The value of x is: {}", x); x = 6; println!("The value of x is: {}", x); } 今、このプログラムを走らせると、以下のような出力が得られます: $ cargo run Compiling variables v0.1.0 (file:///projects/variables) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/variables` The value of x is: 5 (xの値は5です) The value of x is: 6 mut キーワードが使われると、 x が束縛している値を 5 から 6 に変更できます。 変数を可変にする方が、不変変数だけがあるよりも書きやすくなるので、変数を可変にしたくなることもあるでしょう。 考えるべきトレードオフはバグの予防以外にも、いくつかあります。例えば、大きなデータ構造を使う場合などです。 インスタンスを可変にして変更できるようにする方が、いちいちインスタンスをコピーして新しくメモリ割り当てされたインスタンスを返すよりも速くなります。 小規模なデータ構造なら、新規インスタンスを生成して、もっと関数型っぽいコードを書く方が通して考えやすくなるため、 低パフォーマンスは、その簡潔性を得るのに足りうるペナルティになるかもしれません。 変数の値を変更できないようにするといえば、他の多くの言語も持っている別のプログラミング概念を思い浮かべるかもしれません: 定数 です。不変変数のように、定数は名前に束縛され、変更することが叶わない値のことですが、 定数と変数の間にはいくつかの違いがあります。 まず、定数には mut キーワードは使えません: 定数は標準で不変であるだけでなく、常に不変なのです。 定数は let キーワ
The Rust Programming Language 日本語版
オブジェクト指向プログラミング(OOP)は、プログラムをモデル化する手段です。オブジェクトは、 1960年代のSimulaに端緒を発しています。このオブジェクトは、 お互いにメッセージを渡し合うというアラン・ケイ(Alan Kay)のプログラミングアーキテクチャに影響を及ぼしました。 彼は、このアーキテクチャを解説するために、 オブジェクト指向プログラミング という用語を造語しました。 多くの競合する定義がOOPが何かを解説しています; Rustをオブジェクト指向と区分する定義もありますし、 しない定義もあります。この章では、広くオブジェクト指向と捉えられる特定の特徴と、 それらの特徴がこなれたRustでどう表現されるかを探究します。それからオブジェクト指向のデザインパターンをRustで実装する方法を示し、 そうすることとRustの強みを活用して代わりの解決策を実装する方法の代償を議論します。

この回答を読んでいるあなた、今からRustを始めようではないか!

脚注

sumimさんのプロフィール写真

※質問者の意図が明かされたので内容を改めました。

可能かもしれません。が、困難な割にそれに見合う効果(“オブジェクト指向”を正しく理解できた!と納得させれる…とか?)があるとは思えません。むしろそうした試みは、これまで修正されずに複製・伝播してしまった“オブジェクト指向”の誤ったミームの強化を助け、本来それぞれのアイデアが持つ真のパワーを十分引き出せないまま埋もれさせてしまう危険性を個人的には危惧します。

繰り返しになりますが、すでに オブジェクト指向とは何ですか?に対する回答 にも書いたとおり、これら3つのオブジェクト指向は同じ「オブジェクト指向」と称していても、互いに異なる方針を持った考え方です。

端的には次のようにまとめられます。

  • メッセージ指向のオブジェクト指向 …… メッセージング(メッセージ送信、メッセージパッシングとも)を通じて、決定の遅延の徹底するアイデア。メッセージを通じてどんなことをシミュレートするかが大事で、実働するオブジェクト(それを定義するクラス)それ自体やその中身には頓着しない(後で自由に変えられるようにする、それが可能であるように処理系などがサポートする)
  • クラス指向のオブジェクト指向 …… 「クラス」という言語機能による抽象データ型の実現(カプセル化・継承・多態性)を通じて、ユーザーによる型定義を可能にするアイデア。その上で型安全なプログラミングを目指す。
  • プロ

※質問者の意図が明かされたので内容を改めました。

可能かもしれません。が、困難な割にそれに見合う効果(“オブジェクト指向”を正しく理解できた!と納得させれる…とか?)があるとは思えません。むしろそうした試みは、これまで修正されずに複製・伝播してしまった“オブジェクト指向”の誤ったミームの強化を助け、本来それぞれのアイデアが持つ真のパワーを十分引き出せないまま埋もれさせてしまう危険性を個人的には危惧します。

繰り返しになりますが、すでに オブジェクト指向とは何ですか?に対する回答 にも書いたとおり、これら3つのオブジェクト指向は同じ「オブジェクト指向」と称していても、互いに異なる方針を持った考え方です。

端的には次のようにまとめられます。

  • メッセージ指向のオブジェクト指向 …… メッセージング(メッセージ送信、メッセージパッシングとも)を通じて、決定の遅延の徹底するアイデア。メッセージを通じてどんなことをシミュレートするかが大事で、実働するオブジェクト(それを定義するクラス)それ自体やその中身には頓着しない(後で自由に変えられるようにする、それが可能であるように処理系などがサポートする)
  • クラス指向のオブジェクト指向 …… 「クラス」という言語機能による抽象データ型の実現(カプセル化・継承・多態性)を通じて、ユーザーによる型定義を可能にするアイデア。その上で型安全なプログラミングを目指す。
  • プロトタイプ指向のオブジェクト指向 …… メッセージ指向のオブジェクト指向のプログラミング(具体的にはSmalltalkのやり方)から、クラス(やメタクラス、そのメタクラス…)は言語機能には不要、オブジェクトはクラスのインスタンスである必要はなくすべてを他のオブジェクト(プロトタイプ。より語弊のない言い方ではペアレント)への委譲で済ませればよい、後にはメッセージすら不要、というように批判的に派生して構築された。Smalltalkと同じことをやるのには、オブジェクト(フレームとスロット。あと、プロトタイプ・ペアレントを接続する特殊なスロット)と関数(クロージャ)があれば十分シミュレートできるという考え方。

ここまで違うものを包摂する説明や定義に拘泥することはあまりよい結果をうまないことは、冒頭に記したとおりです。

それを承知の上で、あえて共通点を挙げるとすれば、同じ「オブジェクト」という言語機能(データの中に手続きを内包するのが特徴)を使用したプログラミングであることと、それゆえにお互いでお互いをシミュレート・模倣する、あるいはただ解釈を変える(例:クラス ⇄ クラス的な機能を組み込んだオブジェクト ⇄ 単にメソッドを括りだした委譲先オブジェクト、メンバー関数の動的なコール ⇄ メッセージ、など)ことを通じて制約はあるもののある程度似た考え方でのプログラミングが、オブジェクトを持たない言語よりはやりやすい(もちろん、享受できるメリットは限定されるが⸺)というくらいでしょうか。

Quora Userさんのプロフィール写真

これとはちょっと趣が違うかな?

STRUCTURED PROGRAMMING CONSIDERED HARMFUL

Sasaki Toshiakiさんのプロフィール写真

他の回答でも述べられていますが、プログラミングの経験が浅い人が、本や雑誌の記事などからトップダウンで、オブジェクト指向はこういう概念であり、こういう設計手法だから、それを適用してプログラムを作ろう、という形では理解できないと思います。

私の場合、プログラミングが向上する過程は、以下の様でした。

  1. 最初はめちゃくちゃなプログラムを作る。
  2. 重複したコードを1つの関数にまとめるようになる。
  3. 関連した変数を構造体にまとめるようになる。
  4. 関数をコードが重複しているかどうかに加えて意味(コンセプト)で作るようになる。構造体を引数とする関数を作るようになる。
  5. 構造体を引数とする関数を、構造体(クラス)のメソッドとして分類、整理するようになる(ここでオブジェクト指向に足を踏み入れる)。
  6. 異なるクラスのオブジェクト間で同じメソッド呼び出しを定義し、使用するようになる(ポリモーフィズムを使う)。

このようにプログラムについて考え、改良していった結果が自然とオブジェクト指向に近づくことになっていました。私はこのような経験からの裏づけがあって初めて、本当にオブジェクト指向を理解できるようになると思います。やがてオブジェクト指向のトップダウンの知識と、経験によるボトムアップのプログラミングが1つになれば、オブジェクト指向マスターだと思います。

そうなるには時間が必要です。普通は2年くらいかかるでしょうか。もっとかもしれません。

他の回答でも述べられていますが、プログラミングの経験が浅い人が、本や雑誌の記事などからトップダウンで、オブジェクト指向はこういう概念であり、こういう設計手法だから、それを適用してプログラムを作ろう、という形では理解できないと思います。

私の場合、プログラミングが向上する過程は、以下の様でした。

  1. 最初はめちゃくちゃなプログラムを作る。
  2. 重複したコードを1つの関数にまとめるようになる。
  3. 関連した変数を構造体にまとめるようになる。
  4. 関数をコードが重複しているかどうかに加えて意味(コンセプト)で作るようになる。構造体を引数とする関数を作るようになる。
  5. 構造体を引数とする関数を、構造体(クラス)のメソッドとして分類、整理するようになる(ここでオブジェクト指向に足を踏み入れる)。
  6. 異なるクラスのオブジェクト間で同じメソッド呼び出しを定義し、使用するようになる(ポリモーフィズムを使う)。

このようにプログラムについて考え、改良していった結果が自然とオブジェクト指向に近づくことになっていました。私はこのような経験からの裏づけがあって初めて、本当にオブジェクト指向を理解できるようになると思います。やがてオブジェクト指向のトップダウンの知識と、経験によるボトムアップのプログラミングが1つになれば、オブジェクト指向マスターだと思います。

そうなるには時間が必要です。普通は2年くらいかかるでしょうか。もっとかもしれません。もっとも、こういうことすべてをすぐに直感的に理解するような天才的な人はいるかもしれませんが。

大島 芳樹さんのプロフィール写真

もともとのオブジェクト指向が目指していたものは、「オブジェクトと名付けたモジュール同士の疎結合」を推し進めることにより、複数のオブジェクトによって構成されたシステム全体の柔軟性を増すことでした。

ただ、継承というのはそのようなモジュール同士の結合の度合いとしてはとても密結合なわけです。気の利いたオブジェクト指向であればクラスもまたオブジェクトですが、継承はそのようなオブジェクトの組があったとき、一つのオブジェクトへの変更が局所的なもので終わらない、とみることができます。

もちろん、一方で多数あるオブジェクトをグループにまとめ、また「オブジェクトBはだいたいオブジェクトAと同じだけど、少しだけ違う」という差分プログラムをしたい、という需要はあるわけです。その場合も「小クラスのインスタンスを親クラスのインスタンスの代替として使ってもシステムがちゃんと動く」という性質(Liskov substitution principle (LSP))を維持したいわけですが、現在のプログラミング言語では継承があまりにも簡単にできてしまうために、その性質を満たさない形での継承が行われてしまいがちです。

そのために、継承して親クラスのメソッドを上書きする時に、ある種の契約を満たすことを保証する方法などの研究もいろいろとなされていました。Cliftonらによる"method family"のアイディア、つまり字

もともとのオブジェクト指向が目指していたものは、「オブジェクトと名付けたモジュール同士の疎結合」を推し進めることにより、複数のオブジェクトによって構成されたシステム全体の柔軟性を増すことでした。

ただ、継承というのはそのようなモジュール同士の結合の度合いとしてはとても密結合なわけです。気の利いたオブジェクト指向であればクラスもまたオブジェクトですが、継承はそのようなオブジェクトの組があったとき、一つのオブジェクトへの変更が局所的なもので終わらない、とみることができます。

もちろん、一方で多数あるオブジェクトをグループにまとめ、また「オブジェクトBはだいたいオブジェクトAと同じだけど、少しだけ違う」という差分プログラムをしたい、という需要はあるわけです。その場合も「小クラスのインスタンスを親クラスのインスタンスの代替として使ってもシステムがちゃんと動く」という性質(Liskov substitution principle (LSP))を維持したいわけですが、現在のプログラミング言語では継承があまりにも簡単にできてしまうために、その性質を満たさない形での継承が行われてしまいがちです。

そのために、継承して親クラスのメソッドを上書きする時に、ある種の契約を満たすことを保証する方法などの研究もいろいろとなされていました。Cliftonらによる"method family"のアイディア、つまり字面上同じメソッドシグネチャになっているという判別だけではなく、意味として同じ継承関係にあるメソッドを扱うための仕組みや、種々のコントラクトベースの使用記述などもありました。

ただ、現状はそのような仕組みに頼るよりは、なるべく継承は避ける、ということで話は大体済んでいるような感じはありますね。

ちなみに、Smalltalkという、オブジェクト指向という言葉が発明されるきっかけとなった言語では、当初継承機能はありませんでした。ですので、オブジェクト指向では継承がなくてはならない、という決まりはないということは言及しておく価値があるでしょう。

Hideki Ikemotoさんのプロフィール写真

正しい使い方が非常に困難だからです。

例えば、Javaの標準クラスライブラリでも少なくとも2つ間違った継承の使い方があります。

上記のようなTimestampクラスとjava.util.Dateクラスの違いのため、Timestamp値はjava.util.Dateのインスタンスとして考えないでください。Timestampjava.util.Dateの継承関係は、型の継承ではなく、実装の継承を示します。

Timestamp (Java Platform SE 8)
JavaScriptがブラウザで無効になっています。 Java(tm) Platform Standard Edition 8 コンストラクタのサマリー コンストラクタ コンストラクタと説明 Timestamp (int year, int month, int date, int hour, int minute, int second, int nano) 非推奨。 代わりに、次のコンストラクタを使用してください: Timestamp(long millis) Timestamp (long time) ミリ秒の値を使用して、 Timestamp オブジェクトを構築します。 メソッドのサマリー クラス java.util. Date から継承されたメソッド after , before , clone , getDate , getDay , getHours , getMinutes , getMonth , getSeconds , getTimezoneOffset , getYear , parse , setDate , setHours , setMinutes , setMonth , setSeconds , setYear , toGMTString , toLocaleString , UTC コンストラクタの詳細 Timestamp @Deprecated public Timestamp(int year, int month, int date, int hour, int minute, int second, int nano) 非推奨。 代わりに、コンストラクタ Timestamp(long millis) を使用してください 指定された値で初期化された Timestamp オブジェクトを構築します。 パラメータ: year - 年から1900を引いたもの month - 0 - 11 date - 1 - 31 hour - 0 - 23 minute - 0 - 59 second - 0 - 59 nano - 0 - 999,999,999 例外: IllegalArgumentException - nano引数が範囲外にある場合 メソッドの詳細 equals public boolean equals( Object ts) この Timestamp オブジェクトが指定されたオブジェクトと等しいかどうかを判定します。このバージョンの equals メソッドは、 Timestamp.equals(Timestamp) の正しくないシグネチャの修正と、既存のクラス・ファイルとの下位互換を保持するために追加されました。注: このメソッドは、基底クラスの equals(Object) メソッドと対称ではありません。 オーバーライド: equals 、クラス: Date パラメータ: ts - 比較対象の Object 値 戻り値: 指定された Object が Timestamp のインスタンスで、この Timestamp オブジェクトに等しい場合は true 、それ以外の場合は false 関連項目: Date.getTime() Java(tm) Platform Standard Edition 8 バグまたは機能を送信 詳細なAPIリファレンスおよび開発者ドキュメントについては、 Java SEのドキュメント を参照してください。そのドキュメントには、概念的な概要、用語の定義、回避方法、有効なコード例などの、開発者を対象にしたより詳細な説明が含まれています。 Copyright © 1993, 2018, Oracle and/or its affiliates. All rights reserved. Use is subject to license terms . Documentation Redistribution Policy も参照してください。 このページのスクリプトはWebページのトラフィックを追跡するものであり、内容は変更されません。

PropertiesHashtableを継承するので、Propertiesオブジェクトに対してputメソッドおよびputAllメソッドを適用できます。しかし、これらのメソッドを使用することは推奨されません。これらのメソッドを使うと、呼出し側はキーまたは値がStringsではないエントリを挿入できるからです。setPropertyメソッドを代わりに使用してください。

Properties (Java Platform SE 8)
プロパティ・リスト(キーと要素のペア)を入力文字ストリームから単純な行指向形式で読み込みます。 Propertiesは行単位で処理されます。行には 自然行 と 論理行 の2種類があります。自然行は、行末記号( \n 、 \r 、または \r\n )のセットまたはストリームの末尾で区切られた1行の文字列として定義されます。自然行は、空白の行やコメント行であるか、キーと要素のペアの全部または一部を保持する場合があります。論理行は、キーと要素のペアの全データを保持します。バックスラッシュ文字 \ を使用して行末記号シーケンスをエスケープすることで、隣接する複数の自然行にまたがる場合があります。コメント行を、この方法で複数行にまたがらせることはできません。後述するように、コメントであるすべての自然行には、それぞれにコメント・インジケータが必要です。行は、ストリームの終わりに達するまで入力から読み込まれます。 空白文字だけを含む自然行は、空白と見なされて無視されます。コメント行には、ASCII '#' または '!' が最初の非空白文字として含まれます。コメント行も無視され、キーと要素の情報はエンコードされません。このフォーマットは、行終了文字に加え、文字スペース( ' ' 、 '\u0020' )、タブ( '\t' 、 '\u0009' )、およびフォーム・フィード( '\f' 、 '\u000C' )を空白と見なします。 論理行が複数の自然行にまたがる場合、行末記号シーケンスをエスケープするバックスラッシュ、行末記号シーケンス、および次の行の先頭の空白文字は、キーまたは要素の値に何の影響も及ぼしません。(ロード時の)キーと要素の構文解析に関する残りの記述では、行継続文字が削除されたあとで、キーと要素を構成するすべての文字が単一の自然行に表示されることを前提として説明します。行末記号がエスケープされているかどうかを判定する場合、行末記号シーケンスの前の文字を調べるだけでは十分では ありません 。行末記号がエスケープされるためには、連続した奇数のバックスラッシュが存在する必要があります。入力は左から右に処理されるため、行末記号の前(またはほかの場所)に連続したバックスラッシュが2 n (ゼロでない偶数)個存在する場合、エスケープ処理後に n 個のバックスラッシュがエンコードされます。 キーには最初の非空白文字から、最初のエスケープされていない '=' 、 ':' 、または空白文字の手前までの文字すべて(行末記号を除く)が含まれます。これらキーの終わりを示す文字はすべて、バックスラッシュを前に付けてエスケープすることでキーに含めることができます。たとえば、 \:\= には、2文字のキー ":=" が含まれます。行末記号は、エスケープ・シーケンス \r および \n を使用して含めることができます。キーのあとの空白はすべてスキップされます。キーに続く最初の非空白文字が '=' または ':' である場合、これは無視され、その後の空白文字もすべてスキップされます。行上の残りのすべての文字は、関連付けられた要素文字列の一部になります。残りの文字がない場合、その要素は空の文字列 "" です。キーと要素を構成する生の文字シーケンスが識別されると、エスケープ処理が前述の方法で実行されます。 たとえば、次の3行はそれぞれキー "Truth" と、関連した要素値 "Beauty" を表します。 Truth = Beauty Truth:Beauty Truth :Beauty また、次の3行は1つのプロパティを表します。 fruits apple, banana, pear, \ cantaloupe, watermelon, \ kiwi, mango キーは "fruits" で、次の要素に関連付けれられています。 "apple, banana, pear, cantaloupe, watermelon, kiwi, mango" 最終的な結果でカンマのあとに必ずスペースが表示されるように、各 \ の前にスペースがあります。行の終わりを示す \ と、継続行の先頭にある空白は破棄され、ほかの文字に 置換されません 。 また、次の3番目の例では、行: cheeses は、キーが "cheeses" で、関連付けられている要素が空の文字列 "" であることを表します。 キーと要素に含まれる文字は、文字リテラルや文字列リテラルで使用されるエスケープ・シーケンスに似たシーケンスで表現できます( 「Java(tm)言語仕様」 のセクション3.3と3.10.6を参照)。文字および文字列で使用される文字エスケープ・シーケンスやUnicodeエスケープとの違いは、次のとおりです。 8進数のエスケープは認識されない 文字シーケンス \b は、バックスペース文字を 表さない 。 このメソッドは、無効なエスケープ文字の前のバックスラッシュ文字 \ をエラーとして処理しない。バックスラッシュは自動的に削除される。たとえば、Java文字列にシーケンス "\z" が含まれていると、コンパイル時にエラーが発生する。これに対し、このメソッドは自動的にバックスラッシュを削除する。このため、このメソッドでは2文字のシーケンス "\b" は単一の文字 'b' と等価であると見なされる。 単一引用符および二重引用符では、エスケープは不要である。ただし、前述のルールに従って、バックスラッシュに続く単一および二重引用符文字は、それぞれ単一引用符文字と二重引用符文字として処理される。 Unicodeエスケープ・シーケンスで許可されるのは単一「u」文字のみ。 指定されたストリームは、このメソッドが復帰したあとも開いたままです。

このように継承したクラスが継承元のクラスと同じように扱えないと問題があります。例えば次のような不自然な挙動になります。

  1. System.out.println(new java.util.Date(10000).before(new java.sql.Timestamp(20000))); / 

正しい使い方が非常に困難だからです。

例えば、Javaの標準クラスライブラリでも少なくとも2つ間違った継承の使い方があります。

上記のようなTimestampクラスとjava.util.Dateクラスの違いのため、Timestamp値はjava.util.Dateのインスタンスとして考えないでください。Timestampjava.util.Dateの継承関係は、型の継承ではなく、実装の継承を示します。

Timestamp (Java Platform SE 8)
JavaScriptがブラウザで無効になっています。 Java(tm) Platform Standard Edition 8 コンストラクタのサマリー コンストラクタ コンストラクタと説明 Timestamp (int year, int month, int date, int hour, int minute, int second, int nano) 非推奨。 代わりに、次のコンストラクタを使用してください: Timestamp(long millis) Timestamp (long time) ミリ秒の値を使用して、 Timestamp オブジェクトを構築します。 メソッドのサマリー クラス java.util. Date から継承されたメソッド after , before , clone , getDate , getDay , getHours , getMinutes , getMonth , getSeconds , getTimezoneOffset , getYear , parse , setDate , setHours , setMinutes , setMonth , setSeconds , setYear , toGMTString , toLocaleString , UTC コンストラクタの詳細 Timestamp @Deprecated public Timestamp(int year, int month, int date, int hour, int minute, int second, int nano) 非推奨。 代わりに、コンストラクタ Timestamp(long millis) を使用してください 指定された値で初期化された Timestamp オブジェクトを構築します。 パラメータ: year - 年から1900を引いたもの month - 0 - 11 date - 1 - 31 hour - 0 - 23 minute - 0 - 59 second - 0 - 59 nano - 0 - 999,999,999 例外: IllegalArgumentException - nano引数が範囲外にある場合 メソッドの詳細 equals public boolean equals( Object ts) この Timestamp オブジェクトが指定されたオブジェクトと等しいかどうかを判定します。このバージョンの equals メソッドは、 Timestamp.equals(Timestamp) の正しくないシグネチャの修正と、既存のクラス・ファイルとの下位互換を保持するために追加されました。注: このメソッドは、基底クラスの equals(Object) メソッドと対称ではありません。 オーバーライド: equals 、クラス: Date パラメータ: ts - 比較対象の Object 値 戻り値: 指定された Object が Timestamp のインスタンスで、この Timestamp オブジェクトに等しい場合は true 、それ以外の場合は false 関連項目: Date.getTime() Java(tm) Platform Standard Edition 8 バグまたは機能を送信 詳細なAPIリファレンスおよび開発者ドキュメントについては、 Java SEのドキュメント を参照してください。そのドキュメントには、概念的な概要、用語の定義、回避方法、有効なコード例などの、開発者を対象にしたより詳細な説明が含まれています。 Copyright © 1993, 2018, Oracle and/or its affiliates. All rights reserved. Use is subject to license terms . Documentation Redistribution Policy も参照してください。 このページのスクリプトはWebページのトラフィックを追跡するものであり、内容は変更されません。

PropertiesHashtableを継承するので、Propertiesオブジェクトに対してputメソッドおよびputAllメソッドを適用できます。しかし、これらのメソッドを使用することは推奨されません。これらのメソッドを使うと、呼出し側はキーまたは値がStringsではないエントリを挿入できるからです。setPropertyメソッドを代わりに使用してください。

Properties (Java Platform SE 8)
プロパティ・リスト(キーと要素のペア)を入力文字ストリームから単純な行指向形式で読み込みます。 Propertiesは行単位で処理されます。行には 自然行 と 論理行 の2種類があります。自然行は、行末記号( \n 、 \r 、または \r\n )のセットまたはストリームの末尾で区切られた1行の文字列として定義されます。自然行は、空白の行やコメント行であるか、キーと要素のペアの全部または一部を保持する場合があります。論理行は、キーと要素のペアの全データを保持します。バックスラッシュ文字 \ を使用して行末記号シーケンスをエスケープすることで、隣接する複数の自然行にまたがる場合があります。コメント行を、この方法で複数行にまたがらせることはできません。後述するように、コメントであるすべての自然行には、それぞれにコメント・インジケータが必要です。行は、ストリームの終わりに達するまで入力から読み込まれます。 空白文字だけを含む自然行は、空白と見なされて無視されます。コメント行には、ASCII '#' または '!' が最初の非空白文字として含まれます。コメント行も無視され、キーと要素の情報はエンコードされません。このフォーマットは、行終了文字に加え、文字スペース( ' ' 、 '\u0020' )、タブ( '\t' 、 '\u0009' )、およびフォーム・フィード( '\f' 、 '\u000C' )を空白と見なします。 論理行が複数の自然行にまたがる場合、行末記号シーケンスをエスケープするバックスラッシュ、行末記号シーケンス、および次の行の先頭の空白文字は、キーまたは要素の値に何の影響も及ぼしません。(ロード時の)キーと要素の構文解析に関する残りの記述では、行継続文字が削除されたあとで、キーと要素を構成するすべての文字が単一の自然行に表示されることを前提として説明します。行末記号がエスケープされているかどうかを判定する場合、行末記号シーケンスの前の文字を調べるだけでは十分では ありません 。行末記号がエスケープされるためには、連続した奇数のバックスラッシュが存在する必要があります。入力は左から右に処理されるため、行末記号の前(またはほかの場所)に連続したバックスラッシュが2 n (ゼロでない偶数)個存在する場合、エスケープ処理後に n 個のバックスラッシュがエンコードされます。 キーには最初の非空白文字から、最初のエスケープされていない '=' 、 ':' 、または空白文字の手前までの文字すべて(行末記号を除く)が含まれます。これらキーの終わりを示す文字はすべて、バックスラッシュを前に付けてエスケープすることでキーに含めることができます。たとえば、 \:\= には、2文字のキー ":=" が含まれます。行末記号は、エスケープ・シーケンス \r および \n を使用して含めることができます。キーのあとの空白はすべてスキップされます。キーに続く最初の非空白文字が '=' または ':' である場合、これは無視され、その後の空白文字もすべてスキップされます。行上の残りのすべての文字は、関連付けられた要素文字列の一部になります。残りの文字がない場合、その要素は空の文字列 "" です。キーと要素を構成する生の文字シーケンスが識別されると、エスケープ処理が前述の方法で実行されます。 たとえば、次の3行はそれぞれキー "Truth" と、関連した要素値 "Beauty" を表します。 Truth = Beauty Truth:Beauty Truth :Beauty また、次の3行は1つのプロパティを表します。 fruits apple, banana, pear, \ cantaloupe, watermelon, \ kiwi, mango キーは "fruits" で、次の要素に関連付けれられています。 "apple, banana, pear, cantaloupe, watermelon, kiwi, mango" 最終的な結果でカンマのあとに必ずスペースが表示されるように、各 \ の前にスペースがあります。行の終わりを示す \ と、継続行の先頭にある空白は破棄され、ほかの文字に 置換されません 。 また、次の3番目の例では、行: cheeses は、キーが "cheeses" で、関連付けられている要素が空の文字列 "" であることを表します。 キーと要素に含まれる文字は、文字リテラルや文字列リテラルで使用されるエスケープ・シーケンスに似たシーケンスで表現できます( 「Java(tm)言語仕様」 のセクション3.3と3.10.6を参照)。文字および文字列で使用される文字エスケープ・シーケンスやUnicodeエスケープとの違いは、次のとおりです。 8進数のエスケープは認識されない 文字シーケンス \b は、バックスペース文字を 表さない 。 このメソッドは、無効なエスケープ文字の前のバックスラッシュ文字 \ をエラーとして処理しない。バックスラッシュは自動的に削除される。たとえば、Java文字列にシーケンス "\z" が含まれていると、コンパイル時にエラーが発生する。これに対し、このメソッドは自動的にバックスラッシュを削除する。このため、このメソッドでは2文字のシーケンス "\b" は単一の文字 'b' と等価であると見なされる。 単一引用符および二重引用符では、エスケープは不要である。ただし、前述のルールに従って、バックスラッシュに続く単一および二重引用符文字は、それぞれ単一引用符文字と二重引用符文字として処理される。 Unicodeエスケープ・シーケンスで許可されるのは単一「u」文字のみ。 指定されたストリームは、このメソッドが復帰したあとも開いたままです。

このように継承したクラスが継承元のクラスと同じように扱えないと問題があります。例えば次のような不自然な挙動になります。

  1. System.out.println(new java.util.Date(10000).before(new java.sql.Timestamp(20000))); // true 
  2. System.out.println(new java.util.Date(10000).before(new java.sql.Timestamp(10999))); // false 
java.util.Date.compareTo(java.sql.Timestamp time)は危ない。 - @katzchang.contexts
System.out.println(new java.util.Date(10000).before(new java.sql.Timestamp(20000))); System.out.println(new java.util.Date(10000).before(new java.sql.Timestamp(10999))); 上記1行目では"true"を返す。基準時から10000ミリ秒経過後の時間は基準時から20000ミリ秒経過後の時間よりも前なので、この挙動は正しい。 でも、2行目では"false"を返す、微妙な挙動がある。ってかバグだわ、こりゃ。そういうのを含めて、仕様だそうで…

継承したクラスが継承元のクラスと同じように扱うためにはいくつかの条件を満たす必要があります。これをリスコフの置換原則と言います。

リスコフの置換原則 - Wikipedia
リスコフの置換原則 (りすこふのちかんげんそく、 英 : Liskov substitution principle )は、 オブジェクト指向プログラミング において、 サブタイプ のオブジェクトはスーパータイプのオブジェクトの仕様に従わなければならない、という原則である。リスコフの置換原則を満たすサブタイプを behavioral subtype ( 英語版 ) と呼ぶ。 リスコフの置換原則の概念は、 バーバラ・リスコフ により初めて導入された。2010年に撮影された写真。 リスコフの置換原則の概念は最初、1987年10月の OOPSLA での バーバラ・リスコフ の基調講演 “Data abstraction and hierarchy” [ 1 ] にて、インフォーマルな形で紹介された。より形式的な定義は1994年のリスコフと ウィング ( 英語版 ) の共著論文 “ A Behavioral Notion of Subtyping ” [ 2 ] で与えられた。 リスコフの置換原則は Liskov 1988 において以下のように説明されている: The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype ) plus something extra. What is wanted here is something like the following substitution property: If for each object o 1 of type S there is an object o 2 of type T such that for all programs P defined in terms of T , the behavior of P is unchanged when o 1 is substituted for o 2 , then S is a subtype of T . (直感的な考えでは、「サブタイプ」のオブジェクトは別の型(「スーパータイプ」)のオブジェクトのすべての振る舞いと、更に別の何かを備えたものである。 ここで必要とされるものは、以下に示す置換の性質のようなものだろう:型 S の各オブジェクト o 1 に対し、型 T のオブジェクト o 2 が存在し、 T に関して定義されたすべてのプログラム P が o 1 を o 2 で置き換えても動作を変えない場合、 S は T のサブタイプである。) — B. Liskov、Data Abstraction and Hierarchy、 Liskov 1988 , p. 25, 3.3. Type Hierarchy より形式的な定義は Liskov & Wing 1994 で与えられており、以下のように要約されている: Let φ ( x ) be a property provable about objects x of type T . Then φ ( y ) should be true for objects y of type S where S is a subtype of T . ( φ ( x ) を型 T のオブジェクト x に関して証明可能な性質とする。このとき、 φ ( y ) は型 T のサブタイプ S のオブジェクト y について真でなければならない。) — B. Liskov and J. Wing、A Behavioral Notion of Subtyping、 Liskov & Wing 1994 , p. 1812, 1. INTRODUCTION, Subtype Requirement ここで性質 φ はオブジェクトの振る舞いを表す 述語 である。「振る舞い」はオブジェクトの操作(メソッド)が満たすべき事前条件と事後条件によって特徴付けられる。 リスコフの置換原則では、 ホーア論理 に準拠して、振る舞いの事前条件と事後条件に焦点を当てた オブジェクト の代入可能性が要点にされている。その目的は、 基底型 の変数に 派生型 の 値 を代入しても支障をきたさないこと、基底型オブジェクトを派生型オブジェクトで型安全に代替できることである。 リスコフの置換原則は、基底型からの派生型の定義に際して、以下の項目の順守を提唱している。 事前条件(preconditions)を、派生型で強めることはできない。派生型では同じか弱められる。 事後条件(postconditions)を、派生型で弱めることはできない。派生型では同じか強められる。 不変条件(invaritants)は、派生型でも保護されねばならない。派生型でそのまま維持される。 基底型の例外(exception)から派生した例外を除いては、派生型で独自の例外を投げてはならない。 条件を強めるとはプロセス上の状態の取り得る範囲を狭くすることであり、条件を弱めるとはプロセス上の状態の取り得る範囲を広くすることである。これは実装視点では様々な アサーション の複合になる。例えば型の汎化/特化、数値の範囲、データ構造の内容範囲、コードディスパッチ先の選択範囲といった様々な事柄を含む。 注意点として、リスコフの置換原則や振る舞いサブタイピングは、通常の言語の 型システム によってチェックできることではない。プログラムが型チェックに通ったからと言って「リスコフの置換原則を満たす」と言えるわけではない。 [ 独自研究? ]

継承を使うためにはリスコフの置換原則を理解して、継承元の全ての振る舞いを変えていないか検証する必要があります。

でも誰もやりませんよね。継承を使うときって大体が継承元のクラスでは不足しているからと「パッチ」を当てるために使われています。メソッドの追加だけならまだしも、メソッドを完全に置き換えたりします。

しかも継承したクラスをさらに継承してパッチを当てることを繰り返します。すると全く理解できないコードになります。Djangoのクラスベースビューがそんな感じで、読み解くのに苦労しました。

そしてほとんどのケースでは移譲やインタフェースの定義といった、継承をしないパターンで十分です。初心者に使わせるには問題が多すぎるんですね。継承は。

Kozo Nishioさんのプロフィール写真

私も継承はどちらかと言えば否定的ですね。

持論ですが継承には4つの使われ方があって

①機能の追加、②機能の変更、③インターフェース、④タスク管理

これらを区別なくまとめてしまっているのが「継承」の問題の一つに思います。

①機能の追加:継承の継承たる典型的な使い方。

②機能の変更:継承のさらなる高度な使い方。変更すると別の意図と目的を持ったクラスに生まれ変わる。改修作業で現状を触りたくないがために使われやすい。その場合本来必要のない継承が何重にも行われてしまいがち。

③インターフェース:継承とは本来別物。C++では純粋仮想関数を作って継承して実装するが、可読性を悪くする。複数のI/Fを持たせるのも容易ではない。

④タスク管理:継承とは本来別物。基底クラスを使ったタスク管理。仮想関数によって実行時解釈する機能をフルに使う。

もしこの4種類の継承が明確に分離されていたら、『継承』の意図が解るので継承を忌諱することもすくなくなると考えています。

  1. // 例 
  2. class A :add B {}; //機能追加 
  3. class A :update B {}; //機能変更 
  4. class A :add B, update B {}; //機能追加&変更 
  5. class A :interface B {} //インターフェースの追加 
  6. class A :taskof B {} //タスク管理の追加 
大島 芳樹さんのプロフィール写真

C++は、もともと複数の「方針」を混ぜて使えるようにということで、いろいろな機能を入れるというデザインであるため、オブジェクトではないものが言語のコンセプトの中にあるため、「完全なオブジェクト指向」ではないということが言えます。

純粋なオブジェクト指向だと言われるSmalltalkの場合、BooleanやIntegerがオブジェクトなのはもちろんですが、「コンピューター全体」がオブジェクトの集合として表現されています。「クラス」、「メソッド」、「メタクラス」と言ったものももちろん、クラスの集合を格納しておく「名前空間」と言ったものも当然ながらオブジェクトで、それらは実行環境の中で直接操作することができます。さらには、その実行環境もまたオブジェクトの集合なのです実行状態を表す「スタックフレーム」もオブジェクトであり、例えば「継続」を実装したければ、スタックをコピーして保持しておき、後でreturn:やresume:といったメソッドを呼び出して復帰したりもできます。

このことから導き出される最も重要な要素は、「論理的にはシステムの実行ということそのものが自分自身で記述されている」ということです。あるオブジェクトにメッセージ・オブジェクトが送られた時に、対応するメソッド・オブジェクトを探し出し、それを起動するためにスタックフレーム・オブジェクトを割り当てて、メソッドが指定する動作に応じてその

C++は、もともと複数の「方針」を混ぜて使えるようにということで、いろいろな機能を入れるというデザインであるため、オブジェクトではないものが言語のコンセプトの中にあるため、「完全なオブジェクト指向」ではないということが言えます。

純粋なオブジェクト指向だと言われるSmalltalkの場合、BooleanやIntegerがオブジェクトなのはもちろんですが、「コンピューター全体」がオブジェクトの集合として表現されています。「クラス」、「メソッド」、「メタクラス」と言ったものももちろん、クラスの集合を格納しておく「名前空間」と言ったものも当然ながらオブジェクトで、それらは実行環境の中で直接操作することができます。さらには、その実行環境もまたオブジェクトの集合なのです実行状態を表す「スタックフレーム」もオブジェクトであり、例えば「継続」を実装したければ、スタックをコピーして保持しておき、後でreturn:やresume:といったメソッドを呼び出して復帰したりもできます。

このことから導き出される最も重要な要素は、「論理的にはシステムの実行ということそのものが自分自身で記述されている」ということです。あるオブジェクトにメッセージ・オブジェクトが送られた時に、対応するメソッド・オブジェクトを探し出し、それを起動するためにスタックフレーム・オブジェクトを割り当てて、メソッドが指定する動作に応じてそのフレームに値を書き込んだり読みだしたりすることが言語自身で記述されたプログラムとして行われているかのようになっているわけです。

ただ、もし本当にこのように動作しているのだとすれば、他の人の回答にある「遅いのではないか」という想像に基づく懸念も理解できるのですが、実用的な速度で動かすために、実際には上記の動作をより高速に行うためのインタープリター「も」用意されています。実行されているコードが上記のようなメタ機能を司るオブジェクトにアクセスしない限りは、それらのオブジェクトを実際に生成する必要はなく、インタープリターは諸処の工夫で高速化することができます。結果的には、一般的に使われているSmalltalkの処理系はPythonやRubyのものよりも何倍も高速です。もともとグラフィックスやリアルタイム音声処理をしようとしていた人々が作っていた言語ですので、それらをこなせるようになっているわけです。

別の言い方をすれば、他の言語で文字列処理やデータ処理のためにネイティブ・メソッドを書き、言語自体で書かれたコードの代わりに外部のものを呼び出すことによって同じ機能をより高速に提供できる、というところで、Smalltalkはそのようなデータ処理だけではなく言語実行全体をネイティブメソッドのように高速化したものも用意されていると言っても良いかもしれません。

tracing JITを用いたような実装系であれば、全てがオブジェクトであり、Cで書かれたようなプリミティブをなるべく必要としないということが速度向上の役に立ちます。近年流行っている技術からの類推で言えば、ウェブブラウザのDOMオブジェクトの動作はC++で書かれていて、もしJavaScriptからそれらのオブジェクトのプロパティに値を代入すると、 C++で書かれたコードが内部で動作するというようになっていますよね。React.jsなどが使っているvirtual DOMは、その部分を仮想化してJavaScriptで書いているというわけなのですが、このテクニックが効果的である一つの理由は、C++で書かれたDOMオブジェクト操作の部分を通らないJavaScriptの「実行トレース」を長くすることができるために、JITコンパイラがより効果的になるから、という面があります。Smalltalkですべてをオブジェクトにするというのは、わざわざvirtual DOMのようなものをあえて実装しなくてももともと全部がvirtual DOMみたいなものだったと言えるわけですし、tracing JITでなくても少ない機能の高速化に注力するだけで全体が早くなるという効果もあります。

システムとしては、「マウス」、「ディスプレイ」というものもオブジェクトであり、「ディスプレイ」のピクセル値を書き換えることにより画面を描画しますし、何が表示されているのかもピクセルを読み出すことにより確認できます。こちらもBitBltと呼ばれる高速化ルーチンがあります。理屈の上ではユーザーは何も気にしないでコードを書けばいつのまにか、Cで書いたのと同じようなパフォーマンスのグラフィックス操作ができる、という建前ですが、この辺はさすがに実際にどのようになっているのか知らないと速度は出ませんが。

まとめると、もともとの質問である「なぜJavaは純粋なオブジェクト指向言語でないのか」といえば、もし純粋なオブジェクト指向言語というのであればせめて上記のようなレベルくらいにはなっていてほしいというところ、Javaではオブジェクトとして表現されていない要素があるから、ということになるでしょう。

(という回答は、実は同様な質問にたいする回答

なぜjavaは純粋なオブジェクト指向プログラミング言語ではないですか?に対する大島 芳樹 (Yoshiki Ohshima)さんの回答

からの抜粋です。よろしければそちらもご覧ください。)

あと、Ueharaさんの回答にある

C++が完全なオブジェクト指向じゃないのはなぜですか?に対するQuoraユーザーさんの回答

の中で、「syntaxはオブジェクトか、メッセージ送信の演算子はオブジェクトか、vsync割り込みや時間経過概念はオブジェクトか、などを問いたい。」というところがありますが、Smalltalkに関して言えば、シンタックスやメッセージ送信の演算子はオブジェクトとしての表現があり、それらにメッセージを送ってなんらかの結果を受け取ることはできます。vsync割り込みは普通の処理系では抽象化されて隠蔽されていますが、もしvsyncをハンドルする必要がある場合は、vsyncがオブジェクトして見えるようなprimitiveを書くことによって導入することになるでしょう。時間経過の概念を明示的に表現することには大いに賛成です。つまり論理時間に応じてシステムの状態が決まるようにする、ということで、Smalltalkは伝統的にはそういうことはしていませんが、FRPをSmalltalkで書き時間経過の概念をユーザーランドでオブジェクトとして実現すれば良いとも言えますね。ただ、この「あれはどうだ、これはどうだ」という問いは、ある時点で「完全」という言葉の言葉遊びとなるとも思いますし、自分でオブジェクトとして書けば良い、という万能の答えが待っているような気もします。

ihさんのプロフィール写真

クラスを用いて実現される疑似並列処理がプログラムの再利用にあたって便利だからです。プログラムの組み合わせの幅が広がるからと言い換えてもいいと思います。

例えば、ダイクストラの構造化プログラミングでは、プログラムはフローチャートに翻訳できるものに限定されます。つまり、計算プロセスが単線であるもの限定のプログラム技法なのですが、これの不便な点は、2つ検証済みの異なるプログラムA,Bが与えられたとき、頑張って両機能を提供する新たなプログラムCを実装しようとするときに、単純にプログラムA,Bを結合できない(単純に再利用することができない)というところにあります。

当たり前の話なので分かりにくいかもしれません。フローチャートを想像しながら読んでもらえれば幸いなのですが、あるフローチャートに翻訳できるプログラムAともう一つのフローチャートに翻訳できるプログラムBがあるとして、両機能を提供するプログラムCというのは、その2つのフローチャートを単純にくっつけたものでは「ない」のです。

変数名、関数名の衝突、if文の分岐の必要性などがあるので、整合性を保てるようにかならず手直しする必要があります。つまり、構造化プログラミングはあんまりモジュール性(カプセル化)がよろしくないので、純粋な手続とか関数以外は、分業した上で組み合わせるということがしづらいのです。

一方でクラスの場合は、一般にカプセル化と、呼び出し

クラスを用いて実現される疑似並列処理がプログラムの再利用にあたって便利だからです。プログラムの組み合わせの幅が広がるからと言い換えてもいいと思います。

例えば、ダイクストラの構造化プログラミングでは、プログラムはフローチャートに翻訳できるものに限定されます。つまり、計算プロセスが単線であるもの限定のプログラム技法なのですが、これの不便な点は、2つ検証済みの異なるプログラムA,Bが与えられたとき、頑張って両機能を提供する新たなプログラムCを実装しようとするときに、単純にプログラムA,Bを結合できない(単純に再利用することができない)というところにあります。

当たり前の話なので分かりにくいかもしれません。フローチャートを想像しながら読んでもらえれば幸いなのですが、あるフローチャートに翻訳できるプログラムAともう一つのフローチャートに翻訳できるプログラムBがあるとして、両機能を提供するプログラムCというのは、その2つのフローチャートを単純にくっつけたものでは「ない」のです。

変数名、関数名の衝突、if文の分岐の必要性などがあるので、整合性を保てるようにかならず手直しする必要があります。つまり、構造化プログラミングはあんまりモジュール性(カプセル化)がよろしくないので、純粋な手続とか関数以外は、分業した上で組み合わせるということがしづらいのです。

一方でクラスの場合は、一般にカプセル化と、呼び出しをしてもプロセスが消滅しないという性質のおかげで、クラスとしてプログラムA,とプログラムBを記述してしまえば、それぞれ独立にオブジェクトを宣言して疑似並列的に実行させることができます。この時、プログラムAとプログラムBで相互に衝突する変数名、関数名などの調整は不要です。

つまり、クラスとしてカプセル化した状態でプログラムを提供してしまえば、中の変数名の衝突とかを気にする必要なしに、そのプログラムを再利用することができるという利点があるのです。これはダイクストラの構造化プログラミングではありえません。

こういうプログラムの再利用性がよいという理由で、クラス機構を持ったプログラミング言語が必要とされているという面があると思います。

Quora Userさんのプロフィール写真

質問者ですがすでにご回答を多く頂いており、たいへんありがたく思います。

この質問の背景は、sumimさんの別の質問への回答

を読み、もっともだと思いつつ、でも世界のそうそうたるコンピュータサイエンスの泰斗らが、初期の段階で「OOPという概念総称が不適切であり不要で、別の概念をまぜこぜにしたものだ」とはなぜ思いつかなかったか、思いついても訂正を実行しえなかったことについて「やはりそれぞれのオブジェクト指向には、違いはあれど、やはり共通する本質的な特徴もある」ように思ったからです。

だからこそ、オブジェクト指向という名称は受け入れられ、広まったのではないかと。違いはあるにせよ、あるからこそ、それをしっかりと認識しつつ、共通部分を今一度確認したいというのがこの質問の動機です。


可能であり、その共通部分は以下のように説明できます。

オブジェクト指向と呼ばれる仕組みに共通するのは、呼び出し側で処理実体を特定せずに、呼び出しを実行でき、呼び出しに使われる(レシーバ、ターゲット、引数)が、処理実体を選ぶことである。

まあレイトバインディングのことですが、これを詳しく説明します。オブジェクト指向での機能実行は、クラス指向であろうがメッセージ指向であろうがプロトタイプ指向であろうが、常に以下のようになります。

呼び出し側:

  • (1)実行したい処理をあらわすためのシグネチャS(スコープから特定されるメソッド名+引数の

脚注

質問者ですがすでにご回答を多く頂いており、たいへんありがたく思います。

この質問の背景は、sumimさんの別の質問への回答

を読み、もっともだと思いつつ、でも世界のそうそうたるコンピュータサイエンスの泰斗らが、初期の段階で「OOPという概念総称が不適切であり不要で、別の概念をまぜこぜにしたものだ」とはなぜ思いつかなかったか、思いついても訂正を実行しえなかったことについて「やはりそれぞれのオブジェクト指向には、違いはあれど、やはり共通する本質的な特徴もある」ように思ったからです。

だからこそ、オブジェクト指向という名称は受け入れられ、広まったのではないかと。違いはあるにせよ、あるからこそ、それをしっかりと認識しつつ、共通部分を今一度確認したいというのがこの質問の動機です。


可能であり、その共通部分は以下のように説明できます。

オブジェクト指向と呼ばれる仕組みに共通するのは、呼び出し側で処理実体を特定せずに、呼び出しを実行でき、呼び出しに使われる(レシーバ、ターゲット、引数)が、処理実体を選ぶことである。

まあレイトバインディングのことですが、これを詳しく説明します。オブジェクト指向での機能実行は、クラス指向であろうがメッセージ指向であろうがプロトタイプ指向であろうが、常に以下のようになります。

呼び出し側:

  • (1)実行したい処理をあらわすためのシグネチャS(スコープから特定されるメソッド名+引数の型など)の特定
  • (2)引数Aの形成
  • (3)S+Aを「呼び出し(メッセージor Invocation)」と呼ぶ。ただし実体としてメッセージというオブジェクト等を存在させるかどうかは実装依存
  • (4)Aの中からレシーバOの選出(マルチメソッドの場合はOの候補は複数)★
  • (5)OがS+Aから処理本体を特定★

呼び出され側:

  • (6)処理本体の実行

ここでは、object.method(a1,a2)という呼び出しのとき、objectはAの一部、つまり引数と同列として一般に見做しています。(マルチメソッドを実現するCLOSやGroovyなどのオブジェクト指向言語をカバーするため)。

OはS+Aから処理本体を特定する機能をもっており「オブジェクト」と呼びます。

これに対して、非オブジェクト指向、もしくはオブジェクト指向以前は以下となります。

呼び出し側:

  • (1)実行したい処理をあらわすためのシグネチャS(スコープから特定されるメソッド名+引数の型など)の特定
  • (2)引数Aの形成
  • (3)SとAから処理本体を特定

呼び出され側:

  • (4)処理本体の実行

トップダウンの定義になるかどうかは別として、★をもつことが、ボトムアップの観察としては、オブジェクト指向なる概念についての例外のない、漏れのない共通の特徴であると言えます。

ちなみにHaskellなどの言語の、OOPにインスパイアされて発案されたという「型クラス」は以下のようになります。

呼び出し側:

  • (1)実行したい処理をあらわすためのシグネチャS(スコープから特定されるメソッド名+引数、返り値の型など)の特定
  • (2)引数Aの形成
  • (3)SとAからV(「S,A→処理実体」という対応表)を形成
  • (4)Vを引いて、SとAから処理本体を特定

呼び出され側:

  • (6)Vを(暗に)引数に追加して、処理本体の実行

このVがオブジェクトと呼ばれるデータに予め組み込まれているのがオブジェクト指向、Vが呼び出す側のスコープで非明示的に決定されていてデータに対して直接処理を呼び出すのが非オブジェクト指向、Vとデータが呼び出しのタイミングで結合するのが型クラスです。

ちなみに型クラスでは、手続きの集合Vを求めるためのメソッドシグネチャSに戻り値の型が含まれていてターゲット型推論に親和するのも特徴です。


質問では包摂と言ってるのに共通部分をいうのは反則かもとも思いましたが、回答を書く前はあまり突き詰めて考えていませんでした。

なお、本回答を通じて、これをOOPの定義としたい、そうであるべきだ、とか大それたことを言いたいわけではなく、昨今ある種の反動としてOOPは実体のないもの、定義のないもの、世界はそして騙された、と言うぐらいまでの向きがもしあるとしたら、それは言いすぎであって、そこまででもないんだよ、コア概念と言えるかはわかりませんが、共通のものはしっかりとあるんだよと言いたいということです。

その有用性や功罪は別の話ですが、それが具体的に、精密に、どんな機能についての議論かを認識しなければ世間話の域を出ないでしょう。


以下、コメントいただいた点からの、背景をもう少し説明するための追記です。

現在の、もしくは今後の問題として建設的であるために、私は、OOPという総称を具体的に意味ある言葉であるかのように使うのではなく、それを暗に陽に構成してきた、具体的な個別機能(メッセージ抽象、継承、操作の集合による代入互換性の静的・動的チェック、レイトバインディング、ほか)の方を、プログラマや技術者は意識すべきだ、という意見を持っております

。そうすることで、実体のない藁人形としてのOOP批判や擁護ではない、衒学的ではない、日々のプログラミングで実用上の意味やメリットのある議論や、他言語との建設的な比較 ができると思います。

この質問は、そのために、OOP構成要素、部品をはっきりさせたかった、という動機によります。

その上でレイトバインディングは名称から結合タイミングだけの特徴と認識されそうですが、そうではなく、データと操作実体の結合のありさまそのものなので「OOP」という単語が指す概念に意味がもしあるとしたら、その中核的本質に近い概念だと思っており、全てのOOP概念に一貫して存在し、OOPの形以外には他に存在しないユニークなものであるという立場で本回答を記述しました。

特に型クラスとの類似性、意味的な関係性を示すことは、関数型プログラミングスタイルとの不毛な疑似対立を避けるために重要だと思っています。

脚注

Quora Userさんのプロフィール写真

はい、不当ですね。

しかし、私もたまに原理主義者みたいなのに噛みつかれる事がありますが、マトモに相手するだけ無駄ですよ、話なんて通じませんし。

個人的に思うのは、達人に近づけば近づく程に、常に意識しなければならん事は、「プログラムなんて、動いてナンボ」という事だと思います。

(初心者だと危険な考えだけど、達人であれば逆にこの考えが必要)

何らかのポリシーに則ったコーディングはたしかに大事たけど、結局の所、最も大事なのはチャンスを逃さないタイミングでのアプリリリースなんですね。

原理主義的な能書きばかり垂れてて手が遅いヤツ(一昔前の私だったりするけど)を見るとイライラします。

アンタが組んでるのは一体何のためのプログラムだ?という話です。

新田 光宏さんのプロフィール写真

不当ではなかったですよ。

彼を擁護している人もいるようですが、彼が言っている内容と整合性が取れていません。