オブジェクト指向に関する世界観です。
オブジェクト指向とは、「データ構造のふるまいとしてプログラムが存在する」という考え方です。
オブジェクト指向においては、プログラムの中にデータがあるのではなく、データの中の一部として、プログラムが包有されます。
なぜ、プログラムをデータの一部として見なすのでしょうか。
それは、プログラムの基本とは、「入力データに対して手続き処理を行い、それを出力データとして返す」ということだからです。
すべてのプログラムは、入力としてデータを取り、そのデータに基づいて決められた処理を行い、処理の結果を出力としてデータを返します。
そのように、「データを元にして手続き処理をして新しいデータを返す」ということが基本ならば、そもそも手続き処理自体をデータの一部にしてしまい、データの定義である「クラス」の中にあるデータメンバを「ふるまい」として操作するようにすれば、エレガントだと思いませんか。
そのように、データと手続き処理であるメソッドを一緒にして、「クラス」というものを作り、そのクラスのメソッドとして、クラスから生成された「オブジェクト」を操作できるようにするという発想が、オブジェクト指向です。
また、オブジェクト指向には、過去のスパゲティ・プログラム(大規模かつ複雑になりすぎてわけが分からなくなったプログラム)を解決するために、グローバル変数を使わず、「インスタンス」というオブジェクトの個別のデータを作り、それに対して処理を行います。そのため、名前空間を汚染することなく、複数人での開発がやりやすくなります。構造化プログラミングによってGOTOがなくなったように、オブジェクト指向によってグローバル変数がなくなるのです。
また、継承や委譲やインターフェースという考え方を用いることで、大規模で再利用可能なプログラムが作りやすくなります。オブジェクト指向においては、過去に誰かが作ったプログラムを改良しようと思った時に、実際にコードを書き換える必要なく、継承を使うことでプログラムを派生し、新しい機能を後で追加したり上書きしたりできます。また、インターフェースを実装することで、共通の同じインターフェースから別々のクラスのオブジェクトを操作できます。一部の言語では委譲を使うことで、クラスとメソッドを密接に結合せず、場合場合によってクラスの一部ではないメソッドからオブジェクトを操作を「お願い」できます。これらの機能により、複数人でチームでプログラムを作るのがやりやすくなります。
このようなオブジェクト指向プログラミングは、一部、関数型言語に重なるところがあります。オブジェクト指向と同様、関数型プログラミングにおいても、「引数に基づいて返り値を返す」という関数の考え方を採用しているからです。
関数型プログラミングにおいては、変数と関数は同じものとして見なされ、オブジェクト指向に見られるような破壊的操作を好まず、変数を書き換えるのではなく新しく作り出すということを基本としています。オブジェクト指向のようにデータメンバとクラスは密接に結合されておらず、変数と関数が明確に区別されないため、関数を別の関数に引数として与えることもできます。
関数型プログラミングは抽象度が高く、数式に近い表現力を持っていますが、初心者には難しく、また括弧だらけのLispコードのように一見して分かりづらいコードになってしまいます。よって、両者は一長一短があると言えます。ですが、Lispではプログラムコード自体がS式のリストとして扱われるため、ある意味でオブジェクト指向よりもデータとプログラムの結合度合いは高いと言えるでしょう。
2024.01.27
オブジェクト指向でプログラムを記述する場合に気をつけるべきことは、データを保持しながら、データに対するふるまいとしてプログラムを書くことです。
たとえば、線や円や四角形などの図を表示する場合は、それぞれの線情報(パス情報)のデータ(形、大きさ、位置、色、太さなどの情報)を保持しながら、それぞれのデータを表示したり操作したりするようなプログラムを書きます。
実際には、一般的な「パス」のクラスを作り、その派生クラスとして、「線」や「円」や「多角形」のクラスを継承して作ります。
単純なクラスであれば、それだけでプログラムは書けますが、実際には、単なるひとつのクラスでは実現できないプログラムもあります。
すなわち、複数のクラスとオブジェクトが密接に関わり合って、はじめて機能を実現できるようなプログラムもあります。
たとえば、お絵描きをする際に、パスのクラスだけで動くようなプログラムはありません。「キャンバス」とか「イベント操作」とか「環境設定」などのクラスも作り、それぞれのクラスが関わり合うようにしなければいけません。
このほか、Adobe系のデザインソフトで「ペンツール」として採用されているような、マウスでなぞった筆跡に基づいて編集中の一時的なパスの情報に変換する「パスの作成」のための処理を行うクラスや、レイヤーごとにパスの情報と存在を管理する「パスの管理」のためのクラスや、あるいは既に存在するパスを再び編集できるようにするような「パスの編集」を行うクラスも必要となるでしょう。
あるいは、ひとつの機能を作る際にも、複数のクラスが必要となる場合があります。たとえばWeb上で試験問題を解くようなプログラムで、「45分経ったら時間切れと表示する」ような機能は、簡単にひとつのクラスでは作れません。そのような際には、複数のクラスを考えて設計する必要があります。
そのような際には、複数のクラスの「クラス設計」を行い、それぞれのクラスとオブジェクトがどのように関わり合うか、ということを設計します。
このような際によく使われるのは、オブジェクト指向モデリングと呼ばれる手法と、UMLというダイアグラムです。ダイアグラムとは図のことで、UMLを使うことで、それぞれのクラスの関係性を、チームの誰でも分かる形で表現することができます。
また、多くの機能の実現において、「設計の際に頻繁によく使われるパターン」というのがあり、「デザインパターン」と呼びます。デザインパターンを学ぶことで、「このような機能を実現する際にはこのようにクラス設計をすればいい」ということが分かります。いわば、デザインパターンは「クラス設計のネタ帳」であると言えます。
注意点として、お絵描きソフトを作るのはそんなに簡単ではありません。たとえば、パスを編集するためには、ベジェ曲線の曲率を計算するための「ハンドル」のクラスが必要です。そして、そのハンドルクラスを作るために、並大抵ではない努力が必要となります。ハンドルを曲げる、ということだけでたくさんのプログラムが必要となるのです。面倒くさいので、そのようなお絵描きソフトは誰も作りたくありません。Adobe製品を購入するか、GIMPやInkscapeのようなオープンソースのソフトウェアを使うことのほうが、はるかに短時間かつコストパフォーマンスがよいです。Adobe製品はきちんと動きます。
ハンドルの意味が分からない人のために書いておくと、ハンドルはデザイン用語で、ベジェ曲線という曲線の曲率を設定します。ハンドルの付け方が分かると、綺麗で滑らかな曲線を表示することができます。デザイナーになるために、ハンドルの使い方は必ずマスターする必要があります。このほか、デザインソフトには、たとえばグラデーションや透明、あるいはパターンやブラシやフィルター効果といったさまざまな機能を付ける必要があります。そのような機能をすべて作らなければ、デザインソフトを買う人はいないでしょう。それを作りたいのであれば、ぜひ作ってください。ひとりのグラフィックデザイナーとして、その業績が成功することを応援しています。
オブジェクト指向モデリングやデザインパターンも参照のこと。
2024.01.28
クラスとインスタンスについて言えば、クラスは「計算内容の枠組み」であり、インスタンスは「具体的な値の格納」です。
クラスは、計算内容を定義した枠組みです。データとメソッドを定義することで、どのインスタンスにも共通で必要となる「データ型のふるまい」を記述します。
インスタンスは、具体的なデータの値の格納です。クラスひとつひとつの「実体」を作り出し、その実体に具体的なデータを格納します。
クラスをひとつ作ったら、インスタンスはそのクラスからいくつでも作れます。クラスはデータ型であり、インスタンスはデータの値です。あるいは、クラスは計算内容の枠組みであり、インスタンスはデータの格納です。クラスをひとつ作ったら、そのクラスを型とし、その型の実体であるインスタンスをいくらでも作ることができます。
たとえば、パス(線による図形の情報)のクラスをひとつ作ったら、そのインスタンスとして、それぞれの図形のオブジェクトをいくらでも作ることができます。
あるいは、設定項目を保持する際に、ひとつひとつの設定項目を、共通の設定項目クラスからインスタンスとして作ることができます。この際に、すべての特別な設定項目クラスの基底クラスとして、「基本設定項目クラス」を作ったり、あるいは、「基本設定項目インターフェース」を実装するようにすると、それぞれの設定項目でクラスが違っていても、同じインターフェースからクラスを操作できます。
クラス設計をする際には、どのような属性とふるまいがクラスに本当に必要なのか、ということをよく考えます。そして、最低限の機能を持つのか、それとも最大限の機能を持つのか、といったことも考えます。そのような設計の下に、クラスはインスタンスを実体化させることで動作します。関数になぞらえて言えば、クラスは関数定義に共通のデータがくっついたものであり、インスタンスは関数の呼び出しにデータの保持が伴うものであると言えます。
2024.02.04
オブジェクト指向についての余談として、「英文法のSVOに似ている」ということが言えます。
newされて作られたオブジェクトは、主語(Subject)であるオブジェクトのインスタンスに対して、動詞(Verb)であるメソッドを、目的語(Object)である引数を取って実行します。
COBOLやBASICのように、プログラミング言語は英語とよく似通った文法をある意味では目指しています。英語で書くのと同じようにプログラミングできるということが、プログラミングの「言語」の最終到達地点だと思います。
順番だけでなく、「newされたオブジェクトはdeleteで破棄するまで使える(C++の場合)」とか、「ひとつのクラス定義から複数のインスタンスを作ることができる」といった点も、英語における「Object」すなわち目的語とよく似ていると思います。
そして、僕の実感からいうと、むしろオブジェクト指向は日本語とよく似ていると思います。何かに対して何かをする、という「主語に対して述語が続く」というのは、日本語でも同じです。僕は日本語で動詞をものの後ろに付けるのと、オブジェクト指向はまるで同じだと思います。
2024.02.08
オブジェクト指向について言えるのは、「オブジェクト」というものがまずあって、それに対する「メッセージ」で処理を行う、ということです。
抽象的な「モノ」を表す概念として「オブジェクト」があり、ひとつの万能関数がすべてを行うのではなく、それぞれの処理はそれぞれのオブジェクトの中に「メソッド」という形で包有され、そのメソッドに対する「メッセージ」によって関連してシステムが動く、という発想です。
実際、低レベルレイヤーでは、ウィンドウシステムにおいても万能関数やデバイスコンテキストは使いますが、高レベルレイヤーではそれをひとつに詰め込むのではなく、それぞれのオブジェクトに独立・分散して処理を記述し、そのオブジェクトのメッセージングによって全体の処理を行う、というアイデアです。
オブジェクト指向を使うことで、特にGUIのシステムにおいて、処理がそれぞれのアプリケーションに分かれた上で、そのアプリケーションの対話によってシステムを書くことができる、と言えます。
2024.09.02
プログラミングの考え方として、「get」と「set」があります。
getとは、データを得ること。setとは、データを書き換えること。
プログラミングの基本とは、このgetとsetだと考えられます。特に、データを表示したり書き換えたり(破壊)するプログラムでは、これは重要な考え方です。
C#などでは、getキーワードとsetキーワードを使うことで、プロパティを作ることができる。これにより、Javaのようにstr.getValue()やstr.setValue("text")としなくても、「x = str.Value」や「str.Value = "text"」のように、取得と変更を=のような記号を用いて直感的に記述できる。
C#も参照のこと。
オブジェクト指向の面白い点は、「.」の連鎖で「メソッドを実行して返るオブジェクトのメソッドを実行する」ということができる、ということです。
この.の連鎖は面白く、また分かりやすいです。VBやDelphiなどのコードエディタでも、この.の連鎖を辿っていくことで、とても優雅にプログラミングを行えます。
大規模なソフトウェア開発プロジェクトでは、「インターフェース」の考え方が重要になります。
これは、オブジェクト指向言語だけではありません。たとえば、Linuxカーネルでは、デバイスドライバやカーネルモジュールなどのインターフェースが重要になりますし、仮想ファイルシステムという機構では、たくさんのファイルシステムをひとつのVFSというインターフェースで操作します。また、カーネルの内部だけではなく、外部のプログラムとのかかわり合いとして、システムコールなどのインターフェースも重要です。仮想CPUや仮想メモリ機構を使う上でも、プロセスやバイナリファイル、メモリアロケーションインターフェースなどの規則や規範が必要になります。
UNIXには、POSIXという標準規格があり、多くのUnix互換OSではPOSIXの仕様を満たしています。ここではUNIXの標準的な規格が定められており、ファイルシステムのopen(), close(), read(), write()などのシステムコールも例外ではありません。カーネルだけではなく、libcのAPIもインターフェースの一種であると考えられるでしょう。
オブジェクト指向を考えるためには、英語で「オブジェクト」(object)と考えることです。
良く分からない方は、英語でobjectと考えれば良いのです。オブジェクト指向はもの指向です。なんでもものだと考えることで、オブジェクト指向の意味が分かるでしょう。
オブジェクト指向では、データ構造をインターフェースとともにカプセル化された「オブジェクト」と考えます。この「オブジェクト」としてプログラムを作り出す、という発想がオブジェクト指向です。
データ構造をインターフェースとともに隠蔽するように設計し、その場その場で作成する、ということは、大規模なプロジェクトにおけるプログラミングの簡素化をもたらすと同時に、クラスライブラリを使う側にとっても単純かつ使いやすくなります。これはC言語(特にK&R)などでも推奨されるプログラミングの方法です。インターフェースを適切に用意し、データ構造を隠蔽することで、プログラマは安全にデータを取り扱うことができるのです。
オブジェクト指向を感覚でつかむためには、「クラスからオブジェクトを作り、メソッドでアクセスする」ということを掴むことです。
クラスとは、オブジェクトの設計図であると同時に、メソッドを使ってアクセスできるデータ構造です。データ構造の内部の仕様は隠蔽され、アクセスすることを許されたメソッドからしかアクセスできません。
また、コンストラクタとデストラクタによって、初期化と後始末のためのコードを書くことができ、単純な操作でもアクセサやプロパティを介することで、柔軟に必要になった段階で必要な部分に処理を書くことができます。
機能の拡張が必要になった段階で、既存のコードを変更せずにコードを継承・拡張することができ、インターフェースを実装することで共通の操作の記述から別の種類のクラスを操作できます。
クラスからオブジェクトを作り、メソッドを用いてアクセスする。この時、メソッドはレシーバに対するメッセージであり、同時にメソッドは独自に定義される演算子のようなものです。+や-の代わりに、obj.add(item)やobj.remove(item)とするのです。
オブジェクト指向とは、そうした「なんでもかんでもオブジェクト」にする、最近のプログラミングパラダイムです。
実際のところ、Perlのように何でもグローバル変数でやっていると、大規模なプログラム開発を行う時に、何千行と書いているととても無理がでてきます。
大規模なプログラム開発では、データ構造とアルゴリズムを、関連した単位でパッケージ化し、そのデータ構造をひとつひとつオブジェクトとして作りながら、そのアルゴリズムをメソッドとして実行するようにします。
最初は分かりづらいと思いますが、「グローバル変数vsオブジェクト」のように考えると良いでしょう。
また、オブジェクト指向のメリットとして、UMLで設計したダイアグラムをそのままプログラムの中で表現することができる。
オブジェクト指向モデリングも参照のこと。
要するに、オブジェクト指向とは、特定のデータ構造をメソッドから操作する、ということです。
これによって、自分でシステムにはない型や操作方法を作ったり、アクセスをそうした安全な方法でしかできないようにする、ということです。
加えて言えば、システムに用意されている型を改良することも、扱う時に共通の方法で同じように扱うこともできるようにする、ということです。
こうした「データを手で持って操作する」といった感じが、オブジェクトすなわち「もの」のように見える、というのが、「オブジェクト指向」という名前の由来なのです。
僕は、プログラミングのコツは「アクセス」と「再利用」だと思います。
既存のコードやデータをどのように再利用し、コードやデータにどのようにアクセスできるようにするか、これを考えることで、プログラミングは成り立ちます。
プログラミングを行う上で、アクセスと再利用を上手く設計・実装することができれば、プログラミング入門者から中級者になれていると僕は思います。
オブジェクト指向とは、要するに「クラスとインスタンスを使って、特定のプログラムの機能群を作り上げる」ということです。
たとえば、MVCのWebフレームワークであれば、ルーティング、コントローラ、ビュー、モデル、そしてアプリケーションのさまざまな基本機能(セッション管理やフロントコントローラ、データベースマネージャなど)をフレームワークとして実装し、これらのクラスを継承するなどしてアプリケーションを作ります。
こうした時に、クラスを上手く設計し、たとえば抽象メソッドを定義するなどして、「拡張可能な使いやすさ」を実現しながら、インスタンスをクラスの中に包括したり、継承や例外などを上手く使うことで、「再利用可能な巨大アプリケーション」を開発する、これがオブジェクト指向です。
オブジェクト指向は、「データの保護」から生まれた考え方であると言えます。
たとえば、オブジェクトのメンバ変数xがあったとして、このxには0よりも大きな数しか入らないようにしたいとします。
この時、xを外部に単に制限なく公開してしまうと、このデータを保護することができません。これを、メソッドやアクセサ関数のみからアクセスできるようにし、そこでさまざまな対応処理を行うこと(xが0以下であれば0を代入するなど)で、「インターフェースというラッパーを一枚かぶせる」ことで、このデータを保護できるのです。
C++では、STLの可変長リストのように、「どんな型でも使用できる汎用型」を提供している。これを、テンプレートと呼び、テンプレートを使ったプログラミングのことをジェネリック(ジェネリクス)と呼ぶ。
たとえば、文字列型をリストに使ったり、数値型をリストに使ったりする時に、その型名をクラス名と同時に宣言して使うことができる。
詳しくは、C++(2.ジェネリック)やC++(3.STL・ライブラリ)を参照のこと。
JavaScriptのプロトタイプチェーンでおなじみの考え方。「オブジェクトが自分自身で備えていない特性(機能)を、別のオブジェクトにお願い(委譲)する」ことでオブジェクト指向を実現する。
クラスからオブジェクトを量産するのではなく、あるメソッドを別のオブジェクトに関連づけて実行させることができる。
後日注記:プロトタイプチェーンでは、オブジェクトのプロトタイプをプラモデルを作るかのように作成し、そのプロトタイプからオブジェクトを量産する。ここで、あるメソッドをそれが含まれているクラスのオブジェクトだけではなく、別のクラスのオブジェクトにも適用できる。これを委譲と言う。
JavaScript(プロトタイプチェーン)を参照のこと。
シリアライズも参照のこと。
永続化とO/Rマッピングも参照のこと。
リフレクションとevalも参照のこと。
状態も参照のこと。
インターフェースも参照のこと。
型も参照のこと。
保守性も参照のこと。
データへのアクセス保護や、専用のインターフェイスだけを提供し、データにはメソッドを通じてアクセスする。
継承により、既存プログラムの再利用や上書き・改良を、既存コードを変更せず、追加で行える。
メンバ変数は、メソッドの中で共有される共有財産であると同時に、オブジェクトがその時その時保持する状態となる一時データである。
クラス階層はオブジェクト指向でのオブジェクトすなわちものとしてのデータのふるまいと階級を表す。
メンバ変数をメソッドのみから操作することでカプセル化する。
必ずしもクラスのメンバのメソッドにすることがカプセル化につながるわけではなく、メンバではないメソッドにした方が正しいこともある(メンバを無用意に増やすと逆にカプセル化を破壊する)ので注意しよう。
インスタンス作成、破棄の時に、オブジェクトの初期化と後始末を行う。
時に効率化の問題(単にオブジェクトを宣言したりコピーしたりした時にコンストラクタやデストラクタが何度も無駄に実行されること)があるので注意して設計しよう。
クラスにあとから機能を付け足す。
継承した時に、メソッドの内容を上書きして書き換える。
どのようなプログラムでも同じインターフェースから操作できるようにする。
たとえばwrite()とread()を実装するインターフェースを作れば、どの実装でも同じwrite()とread()というAPIから操作できる。
よく似たものに抽象メソッドがあるが、これはメソッド呼び出しを先に記述しておき、メソッドの内容だけを後から記述することができる。
テンプレートとは、要するに、クラスの中で使う型について、お望みの型が使えるようにする。
まるで「型を作る型」のように、たとえばコンテナの場合であれば、コンテナに格納するデータ型の型をその都度指定する。
汎用関数や汎用クラスでは、「どんな型が入れられても成り立つように、型を抽象的なTにして記述する」という記述をする。