Programming

オブジェクト指向とは??

概要

こちらはプログラミングにおいて重要なオブジェクト指向についてまとめています。 少しでもどなたかの理解の助けになれば幸いです。
改善案や指摘等がありましたらコメントにてお願いいたします。

オブジェクト指向とは

object orientedの訳であり(各単語の頭文字をとって、OOと省略されることもあります)、直訳すると「モノ指向」「モノ中心」となります。

オブジェクト指向はソフトウェアの保守や再利用をしやすくすることを重視する技術です。個々の部品の独立性を高め、それらを組み上げてシステム全体の機能を実現することを基本にします。部品の独立性を高めることで、修正が起きた場合の影響範囲を最小限にし、他のシステムで容易に再利用できるようにします。

オブジェクト指向の起源

1967年にノルウェーで考案されたSimula67(シミュラ67)というプログラミング言語が起源になります。
この言語はクラス、ポリモーフィズム、継承という、それまでなかった優れた仕組みを備えており、後に最初のオブジェクト指向プログラミング言語(OOP: Object Oriented Programming language)と呼ばれることになりました。

この仕組みは米ゼロックス社のチームが開発したSmalltalk(スモールトーク)に引き継がれて、「オブジェクト指向」というコンセプトとして確立しました。
その後、C++、Objective-C、Java、C#、Ruby、Pythonなど、同様の仕組みを備えたプログラミング言語が数多く考案されて現在に至ります。

OOPの利用

OOPの利用により、それまでは不可能だった大規模なソフトウエアの再利用部品群であるクラスライブラリやフレームワークの作成が可能になりました。また、このような再利用部品群を作る際に利用された設計のアイデアがデザインパターンとして抽出されました。

OOPの基本的な仕組み

クラス・インスタンス

オブジェクト指向の最も基本的な仕組みはクラスです(クラスを持たないオブジェクト指向もあります)。これと強く関係するインスタンスもこちらで取り扱います。

クラスは英語のclassで「分類」「種類」といった「同種のものの集まり」という意味を持ちます。
インスタンスは英語のinstanceで「具体的なモノ」「実例」を意味します。

分類: 具体的なモノ
犬: 隣の家のポチ、忠犬ハチ公、どこかのお店の看板犬「太郎」…
国: 日本、アメリカ、イギリス、カナダ、スイス…

ポリモーフィズム(polymorphism)

多態性・多相性と訳されることが多く、「色々な形に変わる」という意味を持っています。これは、類似クラスに対する操作を共通化する技術と言えます。

文章だけでは想像が難しい部分があるかもしれないので、ポリモーフィズムの使用方法と利点についてKotlinを用いてまとめました。

最終的なゴールは、ポリモーフィズムを使用して、チワワ・秋田犬・ポメラニアンに自己紹介をしてもらうことです。

まずはポリモーフィズムを使用せず、チワワにだけ自己紹介をしてもらいます。

// チワワ
class Chihuahua {
    fun introduce() {
        println("おはようございます、私の名前はチワワです。") 
    }
}

fun main() {
    val chihuahua = Chihuahua()
    chihuahua().introduce()
}

// output
// おはようございます、私の名前はチワワです。

次に秋田犬とポメラニアンにも自己紹介をしてもらいます。

// チワワ
class Chihuahua {
    fun introduce() {
        println("おはようございます、私の名前はチワワです。") 
    }
}

// 秋田犬
class Akita { 
    fun introduce() { 
        println("こんにちは、私の名前は秋田犬と言います。") 
    } 
}

// ポメラニアン
class Pomeranian {
    fun introduce() { 
        println("こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。") 
    } 
}

fun main() {
    val chihuahua = Chihuahua()
    chihuahua.introduce()
    
    val akita = Akita()
    akita.introduce()
    
    val pomeranian = Pomeranian()
    pomeranian.introduce()
}

// output
// おはようございます、私の名前はチワワです。
// こんにちは、私の名前は秋田犬と言います。
// こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。

チワワ・秋田犬・ポメラニアンの3つのクラスは、それぞれの関数で独自の自己紹介を定義していますが、自己紹介をするという共通の役割を持っています。
ポリモーフィズムを使用すると、これらを共通化することが可能になります。利点等は置いておいて、まずは実施してみます。

先ほどの自己紹介を行う共通の関数をインターフェースを用いて定義します。

interface Dog {
  fun introduce()
}

Dog interfaceでは、関数として「introduce」の定義は行いますが、どんな処理をするかは記述しません。
このように詳細な処理の記述をしていない関数を抽象関数と言います。

次に先ほどのチワワクラスにDog interfaceを継承させます。

class Chihuahua : Dog { 
    override fun introduce() { 
        println("おはようございます、私の名前はチワワです。") 
    } 
}

interfaceクラスを継承しているクラスは、interfaceで定義している抽象関数を「override」キーワードを使用して実装する(抽象関数の中身を記述する)必要があります。
そのため、2行目の「fun」の前に「override」を書き、関数の中身で先ほどの自己紹介を記述しています。

同じように秋田犬とポメラニアンも実装してみます。

class Akita : Dog { 
    override fun introduce() { 
        println("こんにちは、私の名前は秋田犬と言います。") 
    } 
}

class Pomeranian : Dog { 
    override fun introduce() { 
        println("こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。") 
    } 
}

チワワクラスと同様に、Dog interfaceを継承しています。

ここで、それぞれに自己紹介をしてもらいます。

// インターフェース
interface Dog {
  fun introduce()
}

class Chihuahua : Dog { 
    override fun introduce() { 
        println("おはようございます、私の名前はチワワです。") 
    } 
}

class Akita : Dog { 
    override fun introduce() { 
        println("こんにちは、私の名前は秋田犬と言います。") 
    } 
}

class Pomeranian : Dog { 
    override fun introduce() { 
        println("こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。") 
    } 
}

fun main() {
    val chihuahua = Chihuahua()
    chihuahua.introduce()
    
    val akita = Akita()
    akita.introduce()
    
    val pomeranian = Pomeranian()
    pomeranian.introduce()
}

// output
// おはようございます、私の名前はチワワです。
// こんにちは、私の名前は秋田犬と言います。
// こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。

このままではポリモーフィズムを使用していない時の実装と見た目はあまり変わっていません。

ここで、ポリモーフィズムの利点を活かすため、下記の関数を定義します。

fun introduce(dog: Dog) { 
    dog.introduce()
}

この関数はパラメーターにDog型を指定しています。
チワワ・秋田犬・ポメラニアンはDog interfaceを継承しているため、この関数に引数として渡すことができます。 そのため、mainを下記のように書き換えることが可能です。

interface Dog {
    fun introduce()
}

class Chihuahua : Dog { 
    override fun introduce() { 
        println("おはようございます、私の名前はチワワです。") 
    } 
}

class Akita : Dog { 
    override fun introduce() { 
        println("こんにちは、私の名前は秋田犬と言います。") 
    } 
}

class Pomeranian : Dog { 
    override fun introduce() { 
        println("こんばんは、自己紹介をさせていただきます。私の名前はポメラニアンです。") 
    } 
}

fun main() { 
    // インスタンス化 
    val chihuahua = Chihuahua()
    val akita = Akita()
    val pomeranian = Pomeranian()
    
    introduce(chihuahua)
    introduce(akita)
    introduce(pomeranian)
}

// ポリモーフィズムによる呼び出し 
fun introduce(dog: Dog) { 
    dog.introduce()
}

introduce関数はパラメーターに設定しているDog型のクラスが、実際にはどのような実装をしているかを気にすることなく共通の関数を呼び出すことができます。
このように類似クラスに対する操作を共通化する技術をポリモーフィズムと呼びます。
ポリモーフィズムの利点等については別の機会で触れたいと思います。

継承

継承は「クラス間の共通点をまとめる仕組み」と言えます。数学の用語を使用すると、全体集合と部分集合といえます。
オブジェクト指向では全体集合をスーパークラス、部分集合をサブセットと呼びます。

まとめ

所々で現実世界の例を使用し、オブジェクト指向プログラミングの3大要素をまとめました。 オブジェクト指向は、しばしば現実世界と紐づけて説明されます。
しかし、当たり前ですが、現実世界の犬は誕生から成長を経て成犬になり、クラスから生成されるわけではありません。

このように、現実世界をそのままオブジェクト指向に当てはめて考えても、うまくいかないことや混乱を生むこともあります。
現実世界を用いた説明はあくまで例であると割り切り、「クラス・ポリモーフィズム・継承」はソフトウェアの保守性や再利用性を向上させるための仕組みと覚えることをお勧めします。

参考文献

・オブジェクト指向でなぜ作るのか 第3版

Playground

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です