オブジェクト指向 プログラミングを楽にする3つの機能をわかりやすく説明
前回までで、なぜオブジェクト指向が生まれたのかを見てきた。
オブジェクト指向は、従来の言語が持つ以下の問題点を解決する必要から生み出された。
・グローバル変数問題
・低い再利用性
では、オブジェクト指向がこれらの問題をどのように解決するのかを見ていく。
オブジェクト指向の3つの機能
オブジェクト指向は、上記の2つの問題を解決するために、次の3つの機能を持つ。
・クラス
・継承
・ポリモーフィズム
それでは、これらの機能について詳しく見てみよう。
クラス
クラスの役割は大きく三つある。
・サブルーチンと変数をまとめる
まとめることで扱いやすくなる。
たとえば、関数が色々な場所に散らばっている場合と、同じ機能に関するものが一か所にまとまっている場合を考えればわかりやすい。
一か所にまとまっていれば、サブルーチンが探しやすくなるので、再利用性も高まる。
また、コンテクストが決まるので、名前がシンプルになる。
グローバルなOpenFile()よりも、FileクラスのOpen()となっていた方が名前もシンプルになる。
・クラス内部だけで使う変数やサブルーチンを隠す
変数へのアクセスを制限することで、値を追うことが簡単になる。(変更ポイントを特定できる)
生存期間を特定しやすいので、変更が入るポイントを特定しやすくなる。
これによって、グローバル変数がはらむ、「どこで変更されるのかがわからない」という問題を解消できる。
インスタンス変数ならインスタンス生成~破棄までの間。
ローカル変数なら、そのメソッド内のみ。
・1つのクラスからインスタンスをたくさん作る
同じ処理が複数走る時に、その制御のロジックを考えなくてよくなる。
それにより、ロジックがシンプルになる。
例えば、ファイル操作を複数行う場合、それぞれのファイルのパスを配列などで持たないといけない。
OOPであれば、その操作を行う分だけインスタンスを生成すればよい。
そうすることで、クラス内に制御の処理が不要となり、ロジックがシンプルになる。
ロジックがシンプルになれば、そのサブルーチンの再利用性も高まる。
継承
継承とは、複数間のクラスで共通する変数やサブルーチンを持つ場合、それをまとめてしまう機能である。
たとえば、顧客と優良顧客クラスが存在するとする。
継承の仕組みを使わないでそれらを定義すると、以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//顧客クラス public class Customer { public string Id { get; set; } public string Name { get; set; } } //優良顧客クラス public class RoyalCustomer { public string Id { get; set; } public string Name { get; set; } public string CustomerRank { get; set; } //顧客ランク } |
この場合、IdとNameフィールドが重複することになる。
コードの量が多くなるし、保守性も下がる。
例えば、NameのsetプロパティにIdから氏名を解決して代入するような処理を入れる場合、2か所に修正が入る。
また、顧客クラスが増えていけば、その分修正量は増えていく。
継承の仕組みを使えば、共通部分を纏めることができる。
重複するNameとIdのフィールドを共通化することができる。
これにより、コード量、保守性の低下の問題を解決することができる。
以下の例では、NameとIdフィールドを顧客基底クラス(BaseCustomer)に切り出している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//顧客基底クラス public abstract class BaseCustomer { public string Id { get; set; } public string Name { get; set; } } //顧客クラス public class Customer : BaseCustomer { } //優良顧客クラス public class RoyalCustomer : BaseCustomer { public string CustomerRank { get; set; } } |
ポリモーフィズム
ポリモーフィズムとは、オーバーライド、抽象クラス、インターフェースなどを使って、呼び出す側が統一されたインターフェースによって処理を呼び出せるという機能である。
これにより、関数を共通化することができる。
以下の例のように、MethodはBaseCustomerクラスを引数としており、このように記述した場合、BaseCustomer派生クラス(Customer、RoyalCustomer)を引数とすれば、同じ処理を呼び出すことができる。(この例は抽象クラスを使用したポリモーフィズムだが、オーバーライド、インターフェースを使用しても同じことができる。参考:ポリモーフィズムをもっと理解する)
1 2 3 4 5 6 7 8 9 10 11 12 |
static void Main(string[] args) { //顧客・優良顧客どちらでも使用可能 Method(new Customer{ Id="001", Name="山田太郎"}); Method(new RoyalCustomer { Id = "002", Name = "田中一郎" }); } //顧客・優良顧客共通処理 public static void Method(BaseCustomer customer) { //何らかの処理 } |
例えば、この機能が無ければ、Customer型、RoyalCustomer型を引数とする2種類の関数を作らないといけなくなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
static void Main(string[] args) { //顧客用の関数 MethodForCustomer(new Customer{ Id="001", Name="山田太郎"}); //優良顧客用の関数 MethodForRoyalCustomer(new RoyalCustomer { Id = "002", Name = "田中一郎" }); } //顧客用の関数 public static void MethodForCustomer(Customer customer) { //何らかの処理 } //優良顧客用の関数 public static void MethodForRoyalCustomer(RoyalCustomer customer) { //何らかの処理 } |
オブジェクト指向は仕組みを提供するに過ぎない
以上のように、オブジェクト指向はプログラミングを楽にするためにいろいろな仕組みを提供している。
しかし、あくまで提供しているに過ぎない。
大事なのは、これらの機能をどんな時に、どんな用途で使えばいいのかを理解することである。
要は、使い手の問題になってくる。
そのためにも、オブジェクト指向を有効に使えるような設計を行っていかないといけない。