プログラミングの歴史は、より早く効率的に拡張性や保守性を求めていろいろな手法が考えられ新しいプログラミング言語が開発されたり、既存のプログラミング言語が拡張されていきます。
古くは、サブルーチンを使った構造化プログラミングやc言語では、関数が使われて全体を構築する等の歴史がありました。
その中で大きな手法の変化してオブジェクト指向という手法が考案されリリースされました。
現在では、オブジェクト指向を持つ言語がほとんどでJavaを筆頭に、C++・C#・Pythonなどの新しい言語や、既で存在していたPHP・VBA・JavaScriptなどもオブジェクト指向の手法を取り込んでいきました。
この記事では、オブジェクト指向について詳しく解説していきます。
コンピュータの専門学校がプログラミング及び、コンピュータの基礎を学び、その後、日本電気の子会社で働きました。その後、いくつかの開発の仕事を経て,コンピュータの専門学校の講師兼担任を経験し、その後はフリーにてシステムエンジニアやプログラマーの開発の仕事を担当、そのかたわらプログラミングスクールや職業訓練所、企業の新人教育などを担当しました。 25年以上のシステムエンジニア、プログラマーの仕事の経験があります。
オブジェクト指向とは?
オブジェクト指向の本当のというか概念というか思想というのは現実の物をモデル化してオブジェクトして捉えるということです。
例えば八百屋・魚屋・果物屋などはお店というオブジェクトとして考えられます。
お店は、商品とお金という属性(データとも言えます)を持ち仕入れる・売るなどという操作(メソッド)を持ちます。
一方で人というオブジェクトがあります。(人の持つ属性はたくさんありますがここではちょっと置いておきます。)
オブジェクト同士は相手の中でなにが行われているか、知る必要もないのでブラックボックスです。
やり取りはメッセージで行われます。(実際は相手のメソッドを呼び出す)
メソッドを使わず、人が無理やり店の商品を取り出すことはできません。これをカプセル化といいます。
お店は実際に使われるときは、八百屋・魚屋・果物屋などに継承(属性やメソッドを引き継ぐ)され、拡張されて使われます
こういうことをすることにより、システムの構築が容易になり再利用や拡張性をあげるということを考えられました。
オブジェクト指向プログラミングの基本概念
オブジェクト指向をプログラムの世界で考えると、基本的な概念としてはまず最初にクラスというものがあります。
クラスは、属性と属性を操作するメソッド(振る舞い)から構成されます。クラスは設計図と呼ばれることが多いです。
オブジェクト指向では、対象となるものを抽象的に捉えて共通するものを抽出します。
例えば魚屋・八百屋・果物屋は、商品という属性(プロパティあるいはデータ)を持ち、売るという振る舞い(メソッド)を持ちます。
※別の振る舞いとして、仕入れがあるとも考えられます。
これより、属性と振る舞いをまとめて持つと商品というクラスが考えられます。
クラスもひとつのオブジェクトです。
お店というクラスを定義して、使いますが、使う時にはクラスからインスタンスと呼ばれるオブジェクトを生成します。
他にもシステムに必要なものがあれば、クラスを定義してそれぞれの関わりあいをシステム化します。
この時の設計にはUMLというモデリング言語を使います。
UMLは次の図から構成されます。
- クラス図
- シーケンス図
- ユースケース図
- アクティビティ図
- 状態遷移図
UMLの説明だけで1冊の書籍になりますので、この記事ではオブジェクト指向の設計に使われるということだけを覚えておいてください。
オブジェクト指向プログラミングの特徴
オブジェクト指向言語の特徴は、クラスという設計図にあります。
クラスの特徴は、カプセル化と継承ということになります。
カプセル化というのは、冒頭でも少し触れましたが属性(プロパティともデータとも呼ばれますが、ここではわかりやすくデータとします。)とそのデータを操作する処理が隠蔽され外からアクセスできないということです。
カプセル化による利点は、
- データが外部からアクセスできないので変更されることがなく安全性が高い
- 内部でどのような処理が行われているかわからなくても何を入力すると何を返してくるかだけわかっていればいいので内部の処理を拡張や変更しても外部に与える影響がない。
- 内部の処理を隠蔽してることにより、部品として扱え再利用性が高くなる。
ということが挙げられます。カプセル化はオブジェクト指向の特徴のひとつですが、もうひとつの特徴に継承ということがあります。
継承というのはクラスの持つ属性やメソッドをそのまま引き継ぐ形で別のクラス(サブクラス)を作ることができます
店というクラス(親クラス)から魚屋・八百屋・果物屋というサブクラスを作ることができます。そして親クラスの持っている属性やメソッドを引き継ぐことができます。
そして、それぞれのサブクラスに必要な属性やメソッドを付け加えることができます。
さらに親クラスから引き継いだメソッドを書き換えることもできます。(これをオーバーライドと呼びます。)
継承が出来ることの利点は
- 親クラスの属性やメソッドを子クラスが引き継ぐので、何度も同じコード(プログラム言語で書いたテキストのこと)を書く必要がなく、開発の効率が上がる。
- 基礎的な修正や変更が必要な場合は、親クラスを修正すれば子クラスに反映されるので保守性が容易になる。※できないというプログラム言語もあります。
- サブクラスに属性やメソッドを追加したりすることで拡張がやりやすい。
- サブクラスが同じインターフェース(メソッドの呼び出し)を持つのでポリモーフィズムが実現できて柔軟性が高い。※ポリモーフィズムについては後ほど詳しく解説します。
継承とポリモーフィズムもオブジェクト指向言語の特徴のひとつです。
オブジェクト指向プログラミングの手法
別の観点から見ると、オブジェクト指向プログラミングはその名が示すとおりすべてをオブジェクトとし扱うプログラムの手法です。※クラスもひとつのオブジェクトです。
オブジェクトとして扱うことにより、プログラムの柔軟性と再利用性を向上させます。
また、現実的な複雑なものをオブジェクトとして抽象化することによってシンプルなモデルを作り出しシステムを構築しやすくします。
さらに継承やポリモーフィズムという手法を使って、保守性や拡張性を向上させます。
クラスとオブジェクトの概念
繰り返しになるかもしれませんが、オブジェクト指向におけるクラスとオブジェクトは中核的な概念です。
クラスはオブジェクトの設計図であり、逆にオブジェクトは設計図から作られた実体です。
クラスは属性(プロパティ)とメソッドを定義したコードのブロックです。
クラスは実体を抽象化したものであり、オブジェクトが持つべき特性と振る舞いを実装します。
例えば、車ならエンジン・ハンドル・ブレーキなどの属性(プロパティであり、データとも言われる)を持ち、走る・曲がる・止まるなどの振る舞い(メソッドであり、関数とも言われる)を持っていると言えます。
クラスは、設計図でありそれ自体ではプログラムの実行には関係しません。※クラスは実行することはできない。
通常、クラスからオブジェクトが生成されるという表現をしてる解説もありますが、クラスから作られるのはインスタンスオブジェクトです。※インスタンスは「実例」「実態」などの意味を持ちます。
インスタンスオブジェクトは、それ自体でデータを持ち、メソッドによって操作されます。
実際は、車クラスから継承によってカローラクラスなどを作り、カローラクラスからインスタンスを作り出して走る・止まるなどのメソッドで操作するという感じになります。
クラスからインスタンスオブジェクトを作るという手法により、いくつものインスタンスを作ることがらできて、再利用性が向上します。
カプセル化とデータ隠蔽の重要性
カプセル化はオブジェクトの属性やメソッドを外部から隠蔽して、オブジェクトの実装を外部から隠して外部からのアクセスを制限することです。
実際は変数名やメソッド名の前にProtected.pubric,privateなどのアクセス修飾子をつけてアクセスのコントロールをします。
属性に値を代入・取得するためにはセッター・ゲッターというメソッドからおこないます。
大規模なプログラム開発では、チームで開発を行うので誤って属性に違う値を入れてしまって実行時にエラーが出ることをあります。
データを外部からアクセスできなくすることによって勝手にデータを変更されることがなくなります。
また、開発する時に他の人が自分のクラスを使う場合があります。
その時にデータに何をいれるか、メソッドは何か結果は何になるかだけ知っていると自分のクラスの開発が終わってなくても相手側は開発を続けることができます。
これは開発の効率を上げることになります。
また、カプセル化を行うことにより、他のプロジェクトでも使うことができるようになり再利用が上がります。
継承とポリモーフィズムの活用
継承とは何かを振り返ってみると、親クラスからサブクラスを作ることができ親クラスの属性やメソッドを引き継ぐことができます。
この際に、親クラスからサブクラスを複数つくることができます。
それぞれのクラスは親クラスと同じメソッド名を持ちます。
しかし、メソッドはオーバーライド(書き換える)ことができるためそれぞれのサブクラスのメソッドで振る舞いを違うものになります。
そして、サブクラスそれぞれをインスタンスオブジェクトに生成します。
この時、インスタンスオブジェクトは違いますがメソッド名は同じですが、しかし、違う振る舞いをさせることができます。
これをポリモーフィズムといいます。
文章ではよくわからないかもしれませんので、実際のプログラムで見てみます。
※この例は継承を使ってませんが、同じメソッド名でも違うインスタンスオブジェクトによって振る舞いを変えている例です。
※ある程度のJavaがわからないと意味がわからないかもしれません。
class Hero {
String name;
int hp;
}
class Wizard {
String name;
int hp;
void heal(Hero heroX) {
heroX.hp += 10;
System.out.println
(heroX.name +
"のHPを10回復した!");
System.out.println
(heroX.name +
"のHPは" +
heroX.hp);
}
}
public class Main {
public static void
(String[] args) {
Hero hero1 = new Hero();
hero1.name = "ミナト";
hero1.hp = 100;
Hero hero2 = new Hero();
hero2.name = "アサカ";
hero2.hp = 100;
Wizard wizard =
new Wizard();
wizard.name =
"スガワラ";
wizard.hp = 50;
wizard.heal(hero1);
wizard.heal(hero2);
}
}
このプログラムの実行結果は
ミナトのHPを10回復した!ミナトのHPは110
アサカのHPを10回復した!
アサカのHPは110
になります。
ここで、
としているのに、何故ヒーローの名前がミナトとアサカなのでしょうか?
これは下のメソッドを実行するコードで
となっており、wizardのhealメソッドでは、Hero 型のインスタンスオブジェクトを引数として受け取るようになってます。
※クラスはその名前のよるクラス型となり、継承されたサブクラスのインスタンスは、そのクラス型とみなされます。
そして、出力する時は引数で受け取ったインスタンスオブジェクトのNaneを出力してます。
なので、上記の結果となっていますがここで同じクラスからつくれたサブクラスのインスタンスのもつ属性名やメソッド名は同じものを使っています。
これがポリモーフィズムの例です。
別の例として、Javaではインターフェースという形だけのクラスを作ってポリモーフィズムを実現します。
インターフェースは簡単に言うと、メソッドだけが定義されたクラスの一種になります。
※クラスという設計図のさらに定義書みたいなものです。
※他のプログラム言語では、抽象クラス・トレイト・プロトコルという似たものがあります。
例を載せます。
// Animal インターフェースの定義
interface Animal {
void sound();
}
// Dog クラスは Animal
インターフェースを実装
class Dog
implements Animal {
public void sound() {
System.out.println("ワンワン");
}
}
// Cat クラスも Animal
インターフェースを実装
class Cat
implements Animal {
public void sound() {
System.out.println
("ニャーニャー");
}
}
public class
PolymorphismExample {
public static
void main(String[]args{
// Animal 型の配列を作成
Animal[] animals =
new Animal[2];
animals[0] = new Dog();
// Dog オブジェクト
animals[1] = new Cat();
// Cat オブジェクト
// ポリモーフィズムを使って、
異なる動物の音を出力
for (Animal animal
: animals) {
animal.sound();
}
}
}
animalインターフェースを作成し、インターフェースからdogとcatというクラスを実装します。
animalというインターフェースの形をもつ配列を作ります。
そして、その配列にそれぞれ、dogとcatクラスから生成されたインスタンスオブジェクトを入れます。
その配列を頭から終わりまで繰り返しながら、soundメソッドを実行します。
※ここで、注意すべきはsoundというdogとcatクラスで持っている同じメソッド名を使っているということです。
for
(Animal animal : animals) {
animal.sound();
}
このPolymorphismExampleの実行結果は
ニャーニャー
になります。
ポリモーフィズムなより、プログラムはより洗練され簡潔にすることができます。
可読性も上がると説明しているサイトもありますが、オブジェクト指向に慣れてないと可読するのは難しいです。
オブジェクト指向のメリット
冒頭にも説明しましたが、プログラミングの歴史は常にいかに効率をあげるか、保守性をあげるか、可読性を上げるかでした。
そのために考えられていくつかのプログラム言語が考えられ新しいプログラミング手法が取り入られました。
もちろん、その他にもコンピュータの技術があがりインターネットが発展し大型のコンピュータから小型化が計れるなどにもよってプログラミング言語と手法は変わりました。
オブジェクト指向のメリットは、まさに先程述べたとおりプログラミングの効率をあげるための再利用性を高める・保守性を高める・拡張性の向上ということにあります。
ここまでの話と被るところもありますが、再度大事なことなのでここでは、もう少し詳しくそれらについて解説していきます。
再利用性の向上
再利用性が向上する1つめの理由は、クラスを機能単位やデータ別に個別にモジュール化して部品のように扱うようにすると、別のプロジェクトでも使えるようになります。
実際にJavaなどにはライブラリというものがあります。
ライブラリはいくつかのクラスが用意されています。
標準のライブラリというものも用意されていますが、自分達の作った新しいクラスも新しいライブラリを作ることができます。
ライブラリを他のブロジェクトにもっていって使うことができるので、クラスを再利用することができます。
また、もうひとつはインターフェースと抽象クラス(メソッドのいくつかが名前と引数だけのもの)を使って、先程説明したポリモーフィズムを利用することにより再利用を高めます。
例えば、データベースにアクセスするインターフェースを抽象化(メソッドと引数だけ)して定義しておきます。
インターフェースから実際のデータベース(MySql やpostgreSQLなど)にアクセスする実装クラスを作ります。
このようにすると、データベースにアクセスする部分以外の部分は別に作っておけば、将来的にデータベースが変わっても新しくインターフェースから実装するクラスを作るだけでよく、再利用性が向上します。
さらにデザインパターンというものを使うことにより再利用が上がるのですが、デザインパターンについてはそれだけで1冊の本が書けるので、ここではあまり触れないことにします。
簡単に言うとすでにあるブロジェクト(システム)についてこのように構築すればよいという回答がデザインパターンとなります。
従って、違うプロジェクト(システム)でも、同じようなことを行う部分ではデザインパターンに沿って作られたものがそのまま再利用できるということになります。
※現実的には少し変更する点が発生することもあります。
メンテナビリティの向上
まずひとつめは、カプセル化によるメンテナビリティの向上です。
カプセル化は、属性やメソッドを外部から隠蔽することでした。
従って、そのクラスの中の処理の修正や変更が行われても外部には一切の影響がありません。
そのクラスの中でのみの修正になるので、メンテナビリティが向上します。
もうひとつは継承によるメンテナビリティに向上です。
継承は、親クラスからサブクラスを作り出すことでしたが、その時は親クラスの属性・メソッドもすべて引き継ぐことになります。
なので、根本的に問題が発生した時に親クラスを修正すればサブクラスに反映されます。
また、サブクラスの独自のメソッドやオーバーライドしたメソッドで問題が発生した場合はサブクラスだけを修正すればよいということになります。
従って、継承により再利用が向上します。
最後にはポリモーフィズムによるメンテナビリティの向上になります。
ログというのを吐き出すというか、作成するプログラムを考えます。
ログというのは、windowsをかなり触ったことのある人はおわかりになるとおもいますが、正常に動いてます・なにか処理を行いました・エラーが発生しましたなどの複数のメッセージを記録しておくものです。
※通常はなテキストファイルに書き込むますが、特別なログファイルに書き込むものもあります。
従って、いくつかのログを作成するプログラムが必要となりますが、まずは最初にインターフェースを定義します。
そのインターフェースから、いろいろな種類のログを作成する実装クラスをつくります。
これは拡張性とも言えますが、なにかの新しいログを作成するプログラムが必要となったとします。
その時に他の実装クラスは、一切触らずにインターフェースから新しい実装クラスを作ればよいということになります。
既存のものを触らずに作成できますので、ポリモーフィズムによる再利用性が向上することができると言えます。
拡張性の向上
今まで述べてきたように、継承によって親クラスからサブクラスを作り出すのはいくつもできます。
そこで何かの機能が必要となった場合は、サブクラスを作って実装させればよいということになります。
これは拡張性の向上ということになります。
もうひとつ大事な考え方に、依存性逆転原則ということがあります。
以下に例を示します。
// クラスBの定義
class B {
getHowToGreet() {
// 挨拶の仕方を取得する関数
return "こんにちは";
}
}
// クラスAの定義
class A {
greet() {
// コンソールに挨拶を出力する関数
const b = new B();
// BをAの内部でインスタンス化
const greetMessage = b.getHowToGreet();
// Bを用いて挨拶の仕方を取得
console.log(greetMessage);
// 取得した挨拶を出力
}
}
//使う時の例
const a = new A();
a.greet();
このプログラムの設計だと、AはBに依存していると考えられます。
Bがもし、メソッドを追加したりメソッド名を変えたりするとAも変更を余儀なくされます。
これは、AがBに依存してることになります。
ここで、依存性逆転原則を使います。
// BのインターフェースIB
interface IB {
getHowToGreet(): string;
}
// Aの定義
class A {
b: IB; // Bのインターフェースに依存する
// コンストラクタで外からBの実体を受け取る
constructor(b: IB) {
this.b = b;
}
greet() {
// 受け取ったBの実体を用いる
const greetMessage = this.b.getHowToGreet();
console.log(greetMessage);
}
}
// Bの実装
class B implements IB {
// IBを継承してBを実装
getHowToGreet() {
return "こんにちは";
}
}
// Bは外で生成して渡す
const b = new B();
const a = new A(b);
a.greet();
Bの振る舞いを定義する(メソッド名やメソッドの数)インターフェースIBを作成して、BはIBから実装します。
Bのコンストラクタ(クラスがインスタンス化されるときに一回だけ実行されるメソッド)で、Bを実装します。
Bはインターフェースで振る舞いが定義されているので
AはBの処理が変わってもメソッド名は変わらないので
AはBに関わりなく設計できます。
依存性逆転原則は、データベースへの接続や通信、なにかのコンピュータの機器との接続・フレームワークなどの部分のプログラムに使われことが多いです。
また、Bがまだ完成してなくてもなにかのダミーを使えばAはテストできるなどの利点があります。
依存性逆転原則は、拡張性の向上だけでなく保守性の向上にも貢献します。
オブジェクト指向のデメリット
いままで、オプジェクト指向の良い面ばかり見てきましたが、世の中の仕組みには必ずデメリットが存在します。
オプジェクト指向プログラミングにおいてもデメリットは存在します。
ある意味、Pythonなどのプログラミング言語では、オプジェクト指向を使わなくてもプログラムを作ることができます。
むしろ、小規模のプログラミングではオプジェクト指向が必要ない場面もあります。
また、最近ではオプジェクト指向のプログラミングが主流ですが本当にオプジェクト指向が必要なのかという意見もでてきています。
一時期、オプジェクト指向を使えばすべてが解決できるみたいな考えがあり、開発現場ではオプジェクト指向を使うことがあたり前のような風潮はありました。
現在では、関数型プログラミングというあらたな手法もでてきています。
ここでは、オプジェクト指向プログラミングのデメリットについて解説していきます。
学習コストの増加
オプジェクト指向のプログラミングは以下の理由で学習コストが増加します。
オプジェクト指向はかなり抽象的であり、新しい概念や用語を理解しなくてはならなくて初めて学ぶ人にとっては学習コストが上がります。
例えば、次のようなことです。
- オプジェクト指向は、クラス・継承・オブジェクト・ポリモーフィズムなどの概念がありますが、これらの概念を理解するのに時間がかかります。
- プログラミングは設計ということをしてから開発をしますが、オブジェクト指向の設計はモデル化をしてクラスを作るということをしますが実際何度か実務的な経験をしないと難しいということがあります。
- オブジェクト指向には実際のプログラムの他に覚えることが多く、例えばデザインパターンですがデザインパターンを覚えるのはまた時間がかかります。
総じて言えるのは、実際のプロジェクト開発の経験が必要ということです。習得するのに経験を積む必要があります。
オブジェクト指向を習得するには、時間と労力が必要でありその分学習コストが増加する傾向があります。
しかし、オブジェクト指向により得られるメリットも大きいので習得することは大事なことです。
パフォーマンスの低下
オブジェクト指向はその原理と構造によって逆にコンピュータのメモリという資産を多く使うことがありパフォーマンスが低下してしまうということがあります。
結局、クラスやらインスタンスを作り出すのは実際はメモリ上にそれらが展開されます。
通常のプログラミングだと必要がなかったインスタンスオブジェクトなどが多く使われるためメモリが足りなくなるなどで実行のスピードが落ちることがあります。
また、データをインスタンスから参照しにいきますので、そこで間接的な参照が発生するのでその分がスピードを落とすことになります。
仕組みの複雑さ上に通常のプログラムで作成されたものより実行実行時にパフォーマンスが低下しやすい面はあります。
大規模なシステムの構築においては、不必要にインスタンスを作りすぎない使わないインスタンスは意識的に削除するなどの工夫が必要となります。
適切な設計と実装の難しさ
実際のシステムをオブジェクト指向で構築しようとすると、なにかをクラス化すればよいかクラスとクラスのやり取りなどの設計が難しくなります。
また、ポリモーフィズムなどを工夫して取り入れるにしてもインターフェースの定義をどうすればよいかという問題もあります。
初期の頃のJavaでの開発はオブジェクト指向の設計が難しくて、クラスを関数のような使い方をするというような現場も多かったです。
うまくないような設計をしてしまうと、逆に保守性や拡張性を損ねる場合もあります。
ただ、データベースの接続の部分やフレームワークを使う時などにはオブジェクト指向の設計がやりやすいというかフレームワークでは必須になる場合もあります。
特にオブジェクトになにかをさせるのに、プロパティになにかを設定してメソッドを呼び出すというやり方はプログラミングしやすくVBやVBAにはそれが取り入れられています。
総じて、オブジェクト指向での設計や実装は経験や洞察力を必須とする点で通常のプログラミング開発より設計に時間がかかるということになることもあります。
オブジェクト指向のまとめ
オブジェクト指向とは、まず最初にクラスという設計図を定義して、クラスから実体化(インスタンスオブジェクト)を生成してプログラムの実行をします。
この時にクラスは属性(プロパティ)とメッソドを持っていますが、属性は外部からアクセスできないようにしてメッソドだけは外部から使えるようにして、実際の処理は隠蔽します、このことをカブセル化といいます。
カブセル化をすることで、データは安全性が向上しクラス自体は再利用性と拡張性が向上します。
さらにクラスは継承ということ通じて、サブクラスを作成できます。サブクラスは親のクラスの属性とメソッドを引き継ぎますが、あらたな属性とメソッドを追加することができ親クラスのメソッドの処理をオーバーライドすることができます。
継承によって、さらに再利用性・拡張性・保守性が向上します。
また、ポリモーフィズムの活用についても説明しました。インタフェースを使っての依存性逆転原理も説明しました。これによりさらなる再利用性・拡張性・保守性が向上します。
オブジェクト指向のメリットは、そのように再利用性・拡張性・保守性が向上することにあります。
しかし、反面オブジェクト指向の概念やいろいろな言葉を覚えないといけないという点や、仕組み上複雑になるのでパフォーマンスが低下する。設計や実装が難しい。などのデメリットも存在します。
Jiteraでは、要件定義を書くだけでAIが生成するツールで、アプリ・システム開発を行っています。制作している途中で要件が変更になっても柔軟に修正しながら開発できるので、アプリ開発・システム開発のご相談があればお気軽に相談ください。また。オブジョクト思考を使ってのシステム開発の経験も豊富です。ぜひ、お問い合わせください。
お問い合わせはこちら->jitera