仕事でプログラミング研修の講師をやることになりました。私はシステム開発を中心に行っていますが、講師も業務の一つとしています。過去には、HTML/CSS、JavaScript、Python、C#を担当しました。今度もC#です。
講師の実力をはかりたいと、依頼側から「クラスの継承」について動画で説明せよとの依頼がありました。動画はZoomで作るつもりですが、そのための原稿をここに記載します。
クラスは2つの背景から生まれました。1つはグローバル変数の反省から、もう1つはプログラムの再利用のしやすさの考慮からです。
グローバル変数とは、プログラムのどこからでもアクセスできる変数です。C#の前身の前身のC言語では、モジュールAにaというグローバル変数を作ると、モジュールB・C・Dからでもアクセスできます。
これは便利な反面、予期しない副作用を生むことにもなります。グローバル変数を使っている場所がわかりづらいので、システムの保守が難しくなります。グローバル変数aに新たな値を持たせる場合は、影響度調査をしなければなりません。これを反省とし、変数にアクセスできるメソッドを制限させる方法として、クラスが考え出されました。
システムの開発効率を高めるには、プログラムを資産として蓄える必要があります。毎回何もかもを最初から開発していては時間がかかりすぎるため、既にあるものはそのまま使おうという発想です。ある分野の処理を行うプログラムをクラスにしておくと、あとで再利用することができます。例えば、システム的なものとしてはファイル読み書きに関するクラス、画面表示に関するクラス、数値計算に関するクラスなど。業務的なものとしては受注クラス、出荷クラス、売上クラスなどです。
再利用できるクラスを作るには、それなりのプログラミングのスキルが必要になります。変数名・メソッド名の統一性も必要になります。C#には最初から最低限のクラスが用意されています。また、VisualStudioでC#の開発をする場合、マイクロソフトがクラスのまとまりであるクラスライブラリを提供してくれています。
既にあるクラスを再利用して新たな機能を付け加えたり違う動作をさせたい場合、次の2つの方法があります。
1. クラスを修正する
2. クラスを継承する
自分(自社、自部門)が持っているクラスであれば、クラスを修正することが可能です。しかし、開発効率向上のためにソフトウェアの再利用を推進している組織では、自分の想いだけでクラスを修正することはできません。ソフトウェアは資産ですので、既にテストが済んでいるソフトウェア資産を修正することは、バグを混入させる可能性があるため、上位レベル(上司、開発プロジェクト、品質管理部門など)の承認が要ります。そのため、重要な・役に立つ機能追加・修正でないと、承認されにくいです。
もちろん趣味のプログラミングや自分以外に関わる人がいないプログラムであれば、自由にクラスを作り変えてかまいません。
大元のクラスを修正せずに機能を追加したり修正したい場合は、大元のクラスの特徴を引き継いで新しい別のクラスを作ります。このことをクラスを継承するといいます。クラスを継承するなら、ソフトウェアの再利用を推進している組織でも上位レベルの承認が要りません。
大元のクラスを基底クラスといいます。参考書によっては基本クラス、親クラス、スーパークラスと呼んだりもしますが全部同じ意味です。継承して作る新しいクラスを派生クラスといいます。子クラス、サブクラスと呼ぶこともあります。
C#でクラスを作り、継承する例を以下に示します。まず、大元のクラスです。上の図の例のように、円クラスとします。
円クラスは、円の面積の計算、円周の計算ができます。円の半径を入力すると円の面積を計算するメソッド、円の半径を入力すると円周を計算するメソッドがあります。変数としては半径、直径、面積、円周、円周率があります。
using System;
namespace Enkanren
{
class En
{
public double hankei;
public double chokkei;
public double menseki;
public double enshu;
public const double pi = 3.14;
// 円の面積を計算する。
// 円の面積=半径×半径×円周率
public void calc_menseki()
{
Console.Write("\r\n円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
menseki = hankei * hankei * pi;
Console.Write("半径が{0}の円の面積は{1}です。\r\n", hankei, menseki);
}
// 円周を計算する。
// 円周=直径×円周率
public void calc_enshu()
{
Console.Write("\r\n円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
chokkei = hankei * 2;
enshu = chokkei * pi;
Console.Write("半径が{0}(直径が{1})の円周は{2}です。\r\n", hankei, chokkei, enshu);
}
}
class Enkanren
{
static void Main(string[] args)
{
En e = new En();
e.calc_enshu();
e.calc_menseki();
}
}
}
円クラスを使っているうちに、「円柱の体積も計算したいな」という要望が出たとします。上位レベルに円クラスへの機能追加を相談しましたが、「円柱の体積は円クラスに追加するのではなく、円クラスを継承して円柱クラスを作ってください。」と言われました。そのようにして円柱クラスを作ります。
class Enchu : En
{
public double taiseki;
public double takasa;
// 円柱の体積を計算する。
// 円柱の体積=円の半径×半径×円周率×高さ
public void calc_taiseki()
{
Console.Write("\r\n円柱の円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
Console.Write("円柱の高さを入力してください: ");
takasa = double.Parse(Console.ReadLine());
taiseki = hankei * hankei * pi * takasa;
Console.Write("半径が{0}、高さ{1}の円の体積は{2}です。\r\n", hankei, takasa, taiseki);
}
}
class Enchu : En
この部分が継承を指示しているところです。
円柱を計算するメソッド、変数としては体積、高さを追加しました。円柱の体積の計算方法は
円柱の体積=円の半径×半径×円周率×高さ
で、半径と円周率は基底クラスである円クラスに既にあるので円柱クラスには不要です。
最終的に、プログラム全体はこのようになります。
using System;
namespace Enkanren
{
// 基底クラス
class En
{
public double hankei;
public double chokkei;
public double menseki;
public double enshu;
public const double pi = 3.14;
// 円の面積を計算する。
// 円の面積=半径×半径×円周率
public void calc_menseki()
{
Console.Write("\r\n円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
menseki = hankei * hankei * pi;
Console.Write("半径が{0}の円の面積は{1}です。\r\n", hankei, menseki);
}
// 円周を計算する。
// 円周=直径×円周率
public void calc_enshu()
{
Console.Write("\r\n円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
chokkei = hankei * 2;
enshu = chokkei * pi;
Console.Write("半径が{0}(直径が{1})の円周は{2}です。\r\n", hankei, chokkei, enshu);
}
}
// 派生クラス
class Enchu : En
{
public double taiseki;
public double takasa;
// 円柱の体積を計算する。
// 円柱の体積=円の半径×半径×円周率×高さ
public void calc_taiseki()
{
Console.Write("\r\n円柱の円の半径を入力してください: ");
hankei = double.Parse(Console.ReadLine());
Console.Write("円柱の高さを入力してください: ");
takasa = double.Parse(Console.ReadLine());
taiseki = hankei * hankei * pi * takasa;
Console.Write("半径が{0}、高さ{1}の円の体積は{2}です。\r\n", hankei, takasa, taiseki);
}
}
class Enkanren
{
static void Main(string[] args)
{
// En e = new En(); // 継承する前なら、このようにインスタンスを作る。
Enchu c = new Enchu();
// e.calc_enshu(); // 継承する前なら、このようにメンバ関数を呼び出す。
// e.calc_menseki(); // 継承する前なら、このようにメンバ関数を呼び出す。
c.calc_enshu();
c.calc_menseki();
c.calc_taiseki();
}
}
}
円柱クラスは、円クラスの特徴を引き継いでいます。円柱クラスのインスタンスを作り、円クラスに定義しているメソッドを呼ぶこともできます。
実行結果を以下に示します。
これがクラスの継承の基本です。
以上