オブジェクト指向プログラミングを実践するために必要なトピックをカバーしていきます。「【C#】クラスを使ってカプセル化をする方法」ではクラスをカプセル化する方法について解説してきました。クラスをカプセル化するにはアクセス修飾子を活用して、クラス内部にとどめるもの・外部に公開するものを決める必要がありました。
ここではクラス内部の要素をオブジェクト指向に活用する方法から、クラス自体をオブジェクト指向プログラミングに活用する方法を解説していきます。なかでも「継承」を解説したいと思います。
「継承」はクラスを基底として別の「派生」クラスを作成することができるもので、複数のオブジェクト間に「共通する機能」などを閉じ込めることができる機能になります。継承はオブジェクト指向プログラミングにおいて、重要なキーワードの一つとして認識されているのでしっかりと覚えておく必要があるトピックになります。
継承とは
継承の基本的な部分は別の連載記事である「【C#】クラスを継承して子クラスを作成する方法」にて解説済みですので、こちらも最初に読んでおくことをオススメします。「継承」とは読んで字のごとく「受け継ぐこと」にあります。基底クラスに定義されている要素や処理、属性などを派生クラスでも使用可能にすることです。
継承元となるクラスを「基底クラス」や「スーパークラス」と呼び、派生して定義されるクラスを「派生クラス」や「サブクラス」などと呼んだりもします。簡単な例でいうと「動物」という基底クラスがあったとすると、派生クラスとしては「人間」や「犬」「猫」といったモノが該当します。
プログラム内で定義する様々なクラスに共通する部分を切り出して「基底クラス」としてまとめてしまったり、一つの核になるクラスを作成して、そこから派生クラスを作成していくなど記述の仕方は様々になると思います。C#では1つのクラスに対して1つのクラスしか継承元として設定することはできないので注意しましょう。
継承の使い方
実際に継承を使用した簡単なプログラムを記述していきます。コンソールアプリケーションから新規のアプリケーションを作成して、コンソールアプリの準備が整ったら以下のコードを記載してみてください。いくつかクラス別にファイルを作成しています。
基底クラスを定義する
まずは基底クラスを定義したいと思います。今回は製品クラスを作成していきます。
using System;
namespace App01
{
public class Product
{
public int Id { get; }
public int Price { get; }
public Product(int id, int price)
{
Id = id;
Price = price;
}
public int PriceIncludeTax()
=> Convert.ToInt32(Price * 1.1);
}
}
プロパティとコンストラクタ、メソッドを追加しておきます。メソッドは税込価格を取得するためのメソッドになります。ただし細かい計算処理などは気にしていません(小数点以下や端数などの部分)。サンプルコードでは小数点以下が出ないような処理にするので、ここでは単純に1.1倍してint型にしています。
Productクラスには製品を管理するためのIDプロパティと価格プロパティを持たせています。製品である以上、どのような製造物かを特定する識別子と商品の価格は持たせておいても良いと判断したからです。
派生クラスを定義する
それでは派生クラスです。今回は「本」クラスを作成してみました。本も一つの製品であるのでProductクラスを基底クラスとしても十分に機能します。Bookクラスには基底クラスにはないプロパティとして「タイトル」プロパティを持たせました。
namespace App01
{
public class Book : Product
{
public string Title { get; }
public Book(int id, int price, string title) : base(id, price)
{
this.Title = title;
}
}
}
基底・派生クラス同様に完全コンストラクタパターンでオブジェクトを定義しています。ですので、ンストラクタで値を渡すようにしました。また基底クラスのコンストラクタに対して派生クラスから引数を渡す場合は、上記のように「base」というキーワードを使用することで可能になります。
public Book(int id, int price, string title) : base(id, price)
{
this.Title = title;
}
このように記述することで、Bookの引数であるidとpriceの値を、基底クラスであるProductクラスのコンストラクタ引数として設定することが可能です。派生クラスを使って完全コンストラクタパターンを再現するには、このような記述方法を用いることになるので覚えておきましょう。
メイン部分を記載する
最後に処理のメインとなる部分を記載していきます。メイン部分は単純な処理を行っています。今回は「本棚」としてBookクラスのインスタンスを管理できるような仕組みにしており、それらを巡回しながらタイトルや価格、IDといった要素を画面上に表示させるだけの簡単なアプリケーションです。
using System;
using System.Collections.Generic;
namespace App4
{
class Program
{
static void Main(string[] args)
{
var book1 = new Book(1, 800, "素敵な本");
var book2 = new Book(2, 900, "素晴らしい本");
var book3 = new Book(3, 1000, "かっこいい本");
var bookShelf = new List();
bookShelf.Add(book1);
bookShelf.Add(book2);
bookShelf.Add(book3);
foreach (var book in bookShelf)
{
Console.WriteLine($"商品ID:{ book.Id }");
Console.WriteLine($"タイトル:{ book.Title}");
Console.WriteLine($"税抜価格:{ book.Price }");
Console.WriteLine($"税込価格:{ book.PriceIncludeTax() }");
Console.WriteLine();
}
Console.ReadLine();
}
}
}
Book型のオブジェクトを生成しておき、リストとして保管する様にします。その値を回しながら画面に表示させるようにしています。以下の部分は基底クラスと派生クラスのプロパティやメソッドにアクセスして、クラスに定義された設定値などを画面に表示させるようにしています。
商品ID:1
タイトル:素敵な本
税抜価格:800
税込価格:880
商品ID:2
タイトル:素晴らしい本
税抜価格:900
税込価格:990
商品ID:3
タイトル:かっこいい本
税抜価格:1000
税込価格:1100
実行すると上記のようになりますが、商品IDと税抜・税込価格は基底クラスであるProductクラスに記載され、タイトルはBookクラスに記載されています。こうすることでオブジェクトに共通するような箇所はProductクラスに閉じ込めることが可能になります。
「製品」として必要な情報が共通項目としてProductクラスにまとまっていれば、例えば、他の製品であるペン、ノート、車、自転車といったクラスを簡単に追加できるようになります。Productクラスを継承して独自の製品クラスを定義するだけでよくなるのです。
継承は「共通」をまとめる
オブジェクト指向における「継承」の使い方は「共通」をまとめて基底クラスとして定義して、派生クラスが必要になったときに「楽して作れること」が重要になります。ここではBookクラスとしてProductクラスを派生させて作成しましたが、必要になれば車を表すクラス、ペンを表すクラスなどが簡単に作成できます。
オブジェクト指向における継承は「共通」をまとめあげ、そのあとのオブジェクトを簡単に管理できるようにするためにあります。個別にクラスが独立しているよりも、「○○クラスは○○クラスの派生である」というクラスの特徴をしっかりとつかめている方が変更が楽にであるということです。
継承を上手く使ってオブジェクトを「分類」できるようになると、より記述するべきソースコードを省くことができ、そのクラスに「本当に必要な部分」のみを表現できるようになります。ただその代わり、継承を多重に行ってしまうと可読性がグッと下がってしまうため気を付ける必要があります。