第13章 継承とポリモーフィズム
この章の目的
Section titled “この章の目的”この章では、オブジェクト指向プログラミングの重要な考え方である 継承 と ポリモーフィズム を学習します。
第7章から第12章までで、クラス、オブジェクト、プロパティ、メソッド、コンストラクター、List、LINQを学習してきました。
これまで作成してきたクラスは、基本的に1つずつ独立していました。
class Employee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = "";}しかし、実際のプログラムでは、複数のクラスに共通する情報や処理が出てくることがあります。
たとえば、社員には次のような種類があるかもしれません。
正社員契約社員アルバイトそれぞれ違いはありますが、共通する情報もあります。
社員ID氏名部署名社員情報を表示する処理このような共通部分を親となるクラスにまとめ、そこから別のクラスを作る仕組みが 継承 です。
また、共通の型として扱いながら、実際のオブジェクトによって異なる動きをさせる考え方を ポリモーフィズム といいます。
この章では、まず次のことを目標にします。
継承の基本的な書き方を知る親クラスと子クラスの関係を理解するoverrideによってメソッドの動きを変えられることを知るListの中で共通の型として扱えることを理解する既存コードで継承が出てきても読めるようになる補足
継承は強力な機能ですが、何でも継承すればよいわけではありません。 この研修では、継承を多用した設計を目指すのではなく、まずは「読める」「基本的な動きが分かる」ことを重視します。
この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- 継承とは何かをおおまかに説明できる
- 親クラスと子クラスの関係を説明できる
:を使ってクラスを継承できる- 子クラスから親クラスのプロパティやメソッドを利用できる
baseを使って親クラスのコンストラクターを呼び出せるvirtualとoverrideの役割を説明できる- 子クラスで親クラスのメソッドを上書きできる
- ポリモーフィズムの基本的な考え方を説明できる
- 親クラス型の変数に子クラスのオブジェクトを代入できる
List<親クラス>で複数種類の子クラスをまとめて扱える- 抽象クラスの基本的な考え方を説明できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | コンソール アプリ |
| 対象フレームワーク | .NET 8 |
| プロジェクト名 | Chapter13_Inheritance |
作業前チェック
Section titled “作業前チェック”作業を始める前に、次の内容を確認してください。
- クラスを作成できる
- プロパティを定義できる
- メソッドを定義できる
- コンストラクターを作成できる
-
List<T>を使える -
foreach文を使える - 第12章の内容をGitに提出済みである
13-1 継承とは?
Section titled “13-1 継承とは?”共通部分をまとめたい
Section titled “共通部分をまとめたい”まず、正社員を表す RegularEmployee クラスを考えます。
class RegularEmployee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = ""; public int MonthlySalary { get; set; }
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}次に、契約社員を表す ContractEmployee クラスを考えます。
class ContractEmployee{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = ""; public int HourlyWage { get; set; }
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}この2つのクラスには、共通する部分があります。
EmployeeIdEmployeeNameDepartmentNamePrintProfile()一方で、違う部分もあります。
RegularEmployee → MonthlySalary
ContractEmployee → HourlyWage共通する部分を毎回書くと、コードが重複します。
そこで、共通部分を親クラスにまとめます。
親クラスを作る
Section titled “親クラスを作る”社員に共通する情報を EmployeeBase クラスにまとめます。
class EmployeeBase{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = "";
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}この EmployeeBase クラスは、社員に共通する基本情報を持つクラスです。
子クラスで継承する
Section titled “子クラスで継承する”EmployeeBase を継承して、正社員クラスを作成します。
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; set; }}: の後ろに親クラス名を書くことで、クラスを継承できます。
class 子クラス : 親クラス{}RegularEmployee は、EmployeeBase を継承しているため、EmployeeBase のプロパティやメソッドを利用できます。
継承を使ったコード
Section titled “継承を使ったコード”次のコードを入力して実行してください。
using System;
class Program{ static void Main() { RegularEmployee employee = new RegularEmployee();
employee.EmployeeId = 1001; employee.EmployeeName = "山田太郎"; employee.DepartmentName = "営業部"; employee.MonthlySalary = 300000;
employee.PrintProfile(); Console.WriteLine($"月給:{employee.MonthlySalary}円"); }}
class EmployeeBase{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = "";
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; set; }}実行結果:
社員ID:1001氏名:山田太郎部署:営業部月給:300000円RegularEmployee クラスには、EmployeeId、EmployeeName、DepartmentName、PrintProfile を直接書いていません。
しかし、親クラスである EmployeeBase から受け継いでいるため、利用できます。
継承では、次のような用語が使われます。
| 用語 | 意味 | 例 |
|---|---|---|
| 親クラス | 継承元のクラス | EmployeeBase |
| 子クラス | 継承先のクラス | RegularEmployee |
| 基底クラス | 親クラスとほぼ同じ意味 | EmployeeBase |
| 派生クラス | 子クラスとほぼ同じ意味 | RegularEmployee |
現場の資料やコードでは、親クラス・子クラスだけでなく、基底クラス・派生クラスという言葉も出てきます。
この章では、まず次のように考えておきましょう。
親クラス → 共通部分を持つクラス
子クラス → 親クラスを受け継ぎ、独自の情報や処理を追加するクラス13-2 親クラスと子クラス
Section titled “13-2 親クラスと子クラス”複数の子クラスを作る
Section titled “複数の子クラスを作る”EmployeeBase を継承して、正社員と契約社員を作成します。
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; set; }}
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; set; }}どちらも EmployeeBase を継承しているため、社員ID、氏名、部署名を持ちます。
複数の子クラスを使う
Section titled “複数の子クラスを使う”using System;
class Program{ static void Main() { RegularEmployee regularEmployee = new RegularEmployee { EmployeeId = 1001, EmployeeName = "山田太郎", DepartmentName = "営業部", MonthlySalary = 300000 };
ContractEmployee contractEmployee = new ContractEmployee { EmployeeId = 2001, EmployeeName = "佐藤花子", DepartmentName = "開発部", HourlyWage = 1800 };
regularEmployee.PrintProfile(); Console.WriteLine($"月給:{regularEmployee.MonthlySalary}円");
contractEmployee.PrintProfile(); Console.WriteLine($"時給:{contractEmployee.HourlyWage}円"); }}
class EmployeeBase{ public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = "";
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; set; }}
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; set; }}実行結果:
社員ID:1001氏名:山田太郎部署:営業部月給:300000円社員ID:2001氏名:佐藤花子部署:開発部時給:1800円is-a関係
Section titled “is-a関係”継承は、よく is-a関係 で説明されます。
RegularEmployee is an EmployeeBase正社員は社員の一種である
ContractEmployee is an EmployeeBase契約社員は社員の一種であるこのように、「AはBの一種である」と自然に言える場合、継承の候補になります。
一方、次のような関係は継承に向いていません。
社員は部署の一種である商品は注文の一種であるこのように言うと不自然です。
継承は便利ですが、関係が自然かどうかを考える必要があります。
継承を使いすぎない
Section titled “継承を使いすぎない”初学者が継承を学ぶと、何でも親子関係にしたくなることがあります。
しかし、実務では、継承を使いすぎるとコードが読みにくくなることもあります。
この研修では、次の程度の理解を目指します。
継承は共通部分をまとめる方法の1つ親子関係が自然な場合に使う現場の既存コードを読むために重要何でも継承すればよいわけではないDBから取得した employees 表の1行を表すような単純なDTOクラスでは、無理に継承を使わないことも多いです。
13-3 baseを使って親クラスのコンストラクターを呼び出す
Section titled “13-3 baseを使って親クラスのコンストラクターを呼び出す”親クラスにコンストラクターを用意する
Section titled “親クラスにコンストラクターを用意する”第10章では、コンストラクターを使ってオブジェクト作成時に初期値を設定しました。
継承でも、親クラスにコンストラクターを用意できます。
class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }}この場合、子クラスから親クラスのコンストラクターを呼び出す必要があります。
baseで親クラスのコンストラクターを呼び出す
Section titled “baseで親クラスのコンストラクターを呼び出す”子クラスから親クラスのコンストラクターを呼び出すには、base を使います。
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }}: base(...) の部分で、親クラスのコンストラクターを呼び出しています。
baseを使ったコード
Section titled “baseを使ったコード”using System;
class Program{ static void Main() { RegularEmployee employee = new RegularEmployee( 1001, "山田太郎", "営業部", 300000 );
employee.PrintProfile(); Console.WriteLine($"月給:{employee.MonthlySalary}円"); }}
class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }
public void PrintProfile() { Console.WriteLine($"社員ID:{EmployeeId}"); Console.WriteLine($"氏名:{EmployeeName}"); Console.WriteLine($"部署:{DepartmentName}"); }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }}実行結果:
社員ID:1001氏名:山田太郎部署:営業部月給:300000円baseの考え方
Section titled “baseの考え方”base は、親クラスを表します。
: base(employeeId, employeeName, departmentName)これは、次のような意味です。
親クラスであるEmployeeBaseのコンストラクターに、employeeId、employeeName、departmentNameを渡す子クラス独自の値は、子クラスのコンストラクター内で設定します。
MonthlySalary = monthlySalary;このように、共通部分は親クラス、個別部分は子クラスで扱います。
13-4 メソッドを上書きする
Section titled “13-4 メソッドを上書きする”子クラスごとに処理を変えたい
Section titled “子クラスごとに処理を変えたい”正社員と契約社員で、給与の計算方法が異なる場合を考えます。
正社員 → 月給をそのまま支給額とする
契約社員 → 時給 × 勤務時間を支給額とするどちらも「支給額を求める」という目的は同じです。
しかし、計算方法が違います。
このようなときに、親クラスのメソッドを子クラスで上書きできます。
virtualとoverride
Section titled “virtualとoverride”親クラス側では、上書きしてよいメソッドに virtual を付けます。
public virtual int GetPaymentAmount(){ return 0;}子クラス側では、上書きするメソッドに override を付けます。
public override int GetPaymentAmount(){ return MonthlySalary;}正社員の支給額を求める
Section titled “正社員の支給額を求める”using System;
class Program{ static void Main() { RegularEmployee employee = new RegularEmployee( 1001, "山田太郎", "営業部", 300000 );
int paymentAmount = employee.GetPaymentAmount();
Console.WriteLine($"支給額:{paymentAmount}円"); }}
class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }
public virtual int GetPaymentAmount() { return 0; }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }
public override int GetPaymentAmount() { return MonthlySalary; }}実行結果:
支給額:300000円契約社員の支給額を求める
Section titled “契約社員の支給額を求める”次に、契約社員クラスを追加します。
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; private set; } public int WorkHours { get; private set; }
public ContractEmployee(int employeeId, string employeeName, string departmentName, int hourlyWage, int workHours) : base(employeeId, employeeName, departmentName) { HourlyWage = hourlyWage; WorkHours = workHours; }
public override int GetPaymentAmount() { return HourlyWage * WorkHours; }}全体のコードは次の通りです。
using System;
class Program{ static void Main() { RegularEmployee regularEmployee = new RegularEmployee( 1001, "山田太郎", "営業部", 300000 );
ContractEmployee contractEmployee = new ContractEmployee( 2001, "佐藤花子", "開発部", 1800, 120 );
Console.WriteLine($"{regularEmployee.EmployeeName}:{regularEmployee.GetPaymentAmount()}円"); Console.WriteLine($"{contractEmployee.EmployeeName}:{contractEmployee.GetPaymentAmount()}円"); }}
class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }
public virtual int GetPaymentAmount() { return 0; }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }
public override int GetPaymentAmount() { return MonthlySalary; }}
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; private set; } public int WorkHours { get; private set; }
public ContractEmployee(int employeeId, string employeeName, string departmentName, int hourlyWage, int workHours) : base(employeeId, employeeName, departmentName) { HourlyWage = hourlyWage; WorkHours = workHours; }
public override int GetPaymentAmount() { return HourlyWage * WorkHours; }}実行結果:
山田太郎:300000円佐藤花子:216000円同じ GetPaymentAmount というメソッド名でも、正社員と契約社員で処理内容が異なります。
13-5 ポリモーフィズム
Section titled “13-5 ポリモーフィズム”ポリモーフィズムとは
Section titled “ポリモーフィズムとは”ポリモーフィズムは、日本語では 多態性 と訳されます。
少し難しい言葉ですが、この章では次のように考えてください。
同じ呼び出し方で、実際のオブジェクトに応じて異なる動きをすること先ほどの例では、どちらも同じように呼び出しています。
employee.GetPaymentAmount()しかし、実際のオブジェクトが正社員なら月給を返し、契約社員なら時給×勤務時間を返します。
親クラス型の変数に子クラスを代入する
Section titled “親クラス型の変数に子クラスを代入する”継承関係がある場合、親クラス型の変数に子クラスのオブジェクトを代入できます。
EmployeeBase employee1 = new RegularEmployee( 1001, "山田太郎", "営業部", 300000);
EmployeeBase employee2 = new ContractEmployee( 2001, "佐藤花子", "開発部", 1800, 120);RegularEmployee も ContractEmployee も、EmployeeBase の一種なので、EmployeeBase 型の変数に入れられます。
Listでまとめる
Section titled “Listでまとめる”親クラス型を使うと、異なる種類の子クラスを1つのリストにまとめられます。
List<EmployeeBase> employees = new List<EmployeeBase>{ new RegularEmployee(1001, "山田太郎", "営業部", 300000), new ContractEmployee(2001, "佐藤花子", "開発部", 1800, 120)};このリストには、正社員と契約社員の両方を入れています。
同じ呼び出し方で異なる動きをする
Section titled “同じ呼び出し方で異なる動きをする”using System;using System.Collections.Generic;
class Program{ static void Main() { List<EmployeeBase> employees = new List<EmployeeBase> { new RegularEmployee(1001, "山田太郎", "営業部", 300000), new ContractEmployee(2001, "佐藤花子", "開発部", 1800, 120) };
foreach (EmployeeBase employee in employees) { Console.WriteLine($"{employee.EmployeeName}:{employee.GetPaymentAmount()}円"); } }}
class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }
public virtual int GetPaymentAmount() { return 0; }}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }
public override int GetPaymentAmount() { return MonthlySalary; }}
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; private set; } public int WorkHours { get; private set; }
public ContractEmployee(int employeeId, string employeeName, string departmentName, int hourlyWage, int workHours) : base(employeeId, employeeName, departmentName) { HourlyWage = hourlyWage; WorkHours = workHours; }
public override int GetPaymentAmount() { return HourlyWage * WorkHours; }}実行結果:
山田太郎:300000円佐藤花子:216000円foreach の中では、すべて EmployeeBase 型として扱っています。
foreach (EmployeeBase employee in employees){ Console.WriteLine($"{employee.EmployeeName}:{employee.GetPaymentAmount()}円");}しかし、実際には次のように動きます。
RegularEmployeeの場合 → RegularEmployeeのGetPaymentAmountが呼ばれる
ContractEmployeeの場合 → ContractEmployeeのGetPaymentAmountが呼ばれるこれがポリモーフィズムの基本です。
ポリモーフィズムのメリット
Section titled “ポリモーフィズムのメリット”ポリモーフィズムを使うと、呼び出し側のコードをシンプルにできます。
foreach (EmployeeBase employee in employees){ Console.WriteLine($"{employee.EmployeeName}:{employee.GetPaymentAmount()}円");}呼び出し側は、正社員か契約社員かを細かく判定していません。
if (employee is RegularEmployee){ // 正社員用の処理}else if (employee is ContractEmployee){ // 契約社員用の処理}このような分岐を減らし、各クラスに自分の処理を任せることができます。
13-6 抽象クラス
Section titled “13-6 抽象クラス”EmployeeBaseのGetPaymentAmountは本当に必要か
Section titled “EmployeeBaseのGetPaymentAmountは本当に必要か”ここまでの例では、親クラス EmployeeBase に次のメソッドを書いていました。
public virtual int GetPaymentAmount(){ return 0;}しかし、EmployeeBase は社員の共通部分を表すクラスです。
正社員でも契約社員でもない、ただの EmployeeBase オブジェクトを作る必要はあまりありません。
また、支給額の計算方法も、子クラスによって決まるものです。
このような場合、親クラスを 抽象クラス にできます。
abstractを使う
Section titled “abstractを使う”抽象クラスは、abstract キーワードを付けて定義します。
abstract class EmployeeBase{}抽象クラスは、new で直接オブジェクトを作れません。
EmployeeBase employee = new EmployeeBase();このコードはエラーになります。
抽象メソッド
Section titled “抽象メソッド”抽象クラスには、子クラスで必ず実装してほしいメソッドを定義できます。
public abstract int GetPaymentAmount();このようなメソッドを 抽象メソッド といいます。
抽象メソッドには、処理の中身を書きません。
public abstract int GetPaymentAmount();子クラス側では、必ず override して処理を書きます。
抽象クラスを使ったコード
Section titled “抽象クラスを使ったコード”using System;using System.Collections.Generic;
class Program{ static void Main() { List<EmployeeBase> employees = new List<EmployeeBase> { new RegularEmployee(1001, "山田太郎", "営業部", 300000), new ContractEmployee(2001, "佐藤花子", "開発部", 1800, 120) };
foreach (EmployeeBase employee in employees) { Console.WriteLine($"{employee.EmployeeName}:{employee.GetPaymentAmount()}円"); } }}
abstract class EmployeeBase{ public int EmployeeId { get; private set; } public string EmployeeName { get; private set; } public string DepartmentName { get; private set; }
public EmployeeBase(int employeeId, string employeeName, string departmentName) { EmployeeId = employeeId; EmployeeName = employeeName; DepartmentName = departmentName; }
public abstract int GetPaymentAmount();}
class RegularEmployee : EmployeeBase{ public int MonthlySalary { get; private set; }
public RegularEmployee(int employeeId, string employeeName, string departmentName, int monthlySalary) : base(employeeId, employeeName, departmentName) { MonthlySalary = monthlySalary; }
public override int GetPaymentAmount() { return MonthlySalary; }}
class ContractEmployee : EmployeeBase{ public int HourlyWage { get; private set; } public int WorkHours { get; private set; }
public ContractEmployee(int employeeId, string employeeName, string departmentName, int hourlyWage, int workHours) : base(employeeId, employeeName, departmentName) { HourlyWage = hourlyWage; WorkHours = workHours; }
public override int GetPaymentAmount() { return HourlyWage * WorkHours; }}実行結果:
山田太郎:300000円佐藤花子:216000円抽象クラスの特徴
Section titled “抽象クラスの特徴”抽象クラスには、次の特徴があります。
| 特徴 | 内容 |
|---|---|
abstract を付ける | 抽象クラスになる |
直接 new できない | 親クラスそのもののオブジェクトは作れない |
| 共通のプロパティやメソッドを持てる | 通常のクラスと同じように書ける |
| 抽象メソッドを持てる | 子クラスで実装を強制できる |
この章では、次のように理解してください。
抽象クラス → 共通部分をまとめるが、それ自体は直接使わない親クラス
抽象メソッド → 子クラスで必ず実装してほしいメソッドインターフェイスとの関係
Section titled “インターフェイスとの関係”次章では、インターフェイス を学習します。
抽象クラスとインターフェイスは、どちらもポリモーフィズムに関係します。
この章では、抽象クラスを次のように理解しておけば十分です。
抽象クラス → 共通する情報や処理を持たせられる親クラス
インターフェイス → 必ず持つべき機能の約束を表すもの詳しくは次章で扱います。
13-7 継承を読むときのポイント
Section titled “13-7 継承を読むときのポイント”現場では「使う」より「読む」場面が多い
Section titled “現場では「使う」より「読む」場面が多い”新人研修後の現場では、自分で一から継承設計をするよりも、既存コードを読む場面の方が多いかもしれません。
継承を使ったコードを読むときは、次の順番で確認するとよいです。
1. このクラスは何を継承しているか2. 親クラスにはどのようなプロパティやメソッドがあるか3. 子クラスで追加されたプロパティやメソッドは何か4. overrideされているメソッドは何か5. Listや変数が親クラス型で扱われていないかクラス定義の1行目を見る
Section titled “クラス定義の1行目を見る”まず、クラス定義の1行目を確認します。
class RegularEmployee : EmployeeBaseこの場合、RegularEmployee は EmployeeBase を継承しています。
つまり、RegularEmployee には、EmployeeBase のプロパティやメソッドも含まれていると考えます。
overrideを探す
Section titled “overrideを探す”次に、override が付いたメソッドを探します。
public override int GetPaymentAmount()override がある場合、親クラスで定義されたメソッドの動きを、子クラス側で上書きしています。
そのため、処理内容を確認するときは、親クラスだけでなく子クラス側の実装も見る必要があります。
親クラス型の変数に注意する
Section titled “親クラス型の変数に注意する”次のようなコードにも注意します。
EmployeeBase employee = new RegularEmployee(...);変数の型は EmployeeBase ですが、実際のオブジェクトは RegularEmployee です。
この状態で employee.GetPaymentAmount() を呼び出すと、RegularEmployee 側の GetPaymentAmount が実行されます。
この動きに慣れると、ポリモーフィズムを使ったコードを読みやすくなります。
よくあるつまずき
Section titled “よくあるつまずき”この章でよくあるつまずきを確認します。
| つまずき | 原因 | 対応 |
|---|---|---|
| 継承の意味が分からない | 親子関係のイメージが曖昧 | 共通部分を親クラスにまとめると考える |
: の意味が分からない | 継承の書き方に慣れていない | class 子 : 親 は「親を継承する」 |
| 子クラスに書いていないプロパティが使える理由が分からない | 親クラスから受け継いでいることを見落としている | 親クラスの定義を確認する |
base が分からない | 親クラスを表すキーワードに慣れていない | base は親クラスを指す |
virtual と override が分からない | メソッドの上書きに慣れていない | 親が virtual、子が override |
| 親クラス型に子クラスを代入できる理由が分からない | is-a関係が曖昧 | 子クラスは親クラスの一種と考える |
List<EmployeeBase> に複数種類を入れられる理由が分からない | 共通の親クラスとして扱う考え方に慣れていない | 正社員も契約社員もEmployeeBaseの一種 |
| 抽象クラスをnewできない | 抽象クラスの役割が曖昧 | 抽象クラスは直接使う親クラスではない |
| 継承を使うべき場面が分からない | 何でも継承しようとしている | 「AはBの一種」と自然に言えるか確認する |
学んだことチェック
Section titled “学んだことチェック”次の項目について、自分で説明できるか確認してください。
- 継承とは何かを説明できる
- 親クラスと子クラスの関係を説明できる
-
class 子クラス : 親クラスの書き方を理解している - 子クラスから親クラスのプロパティやメソッドを使えることを説明できる
-
baseの役割を説明できる - 親クラスのコンストラクターを子クラスから呼び出せる
-
virtualの役割を説明できる -
overrideの役割を説明できる - ポリモーフィズムとは何かをおおまかに説明できる
- 親クラス型の変数に子クラスのオブジェクトを代入できることを説明できる
-
List<親クラス>で複数種類の子クラスを扱えることを説明できる - 抽象クラスとは何かをおおまかに説明できる
- 抽象メソッドは子クラスで実装が必要であることを説明できる
- 継承を使いすぎるべきではないことを理解している
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- 継承はどのようなときに使いますか。
- 親クラスと子クラスの違いは何ですか。
RegularEmployee : EmployeeBaseは何を意味していますか。base(...)は何をしていますか。virtualとoverrideは何のために使いますか。- ポリモーフィズムとは、ひとまずどのような考え方ですか。
List<EmployeeBase>にRegularEmployeeとContractEmployeeを入れられるのはなぜですか。- 抽象クラスを直接
newできないのはなぜですか。
説明するときは、完全な答えでなくても構いません。
自分の言葉で説明しようとすることが大切です。
この章の演習課題に取り組みます。
制限時間は 80分 です。
時間内にすべて完成しなくても構いません。
できたところまでを保存し、Gitに提出してください。
まずは、全員が必須課題に取り組んでください。
課題13-1 EmployeeBaseを継承する
Section titled “課題13-1 EmployeeBaseを継承する”社員の共通情報を持つ EmployeeBase クラスを作成し、それを継承する RegularEmployee クラスを作成してください。
EmployeeBase のプロパティ:
| プロパティ名 | 型 |
|---|---|
EmployeeId | int |
EmployeeName | string |
DepartmentName | string |
RegularEmployee のプロパティ:
| プロパティ名 | 型 |
|---|---|
MonthlySalary | int |
実行結果例:
社員ID:1001氏名:山田太郎部署:営業部月給:300000円条件:
EmployeeBaseクラスを作成するRegularEmployee : EmployeeBaseと書く- 親クラスのプロパティを子クラスから利用する
課題13-2 ContractEmployeeを追加する
Section titled “課題13-2 ContractEmployeeを追加する”EmployeeBase を継承する ContractEmployee クラスを作成してください。
ContractEmployee のプロパティ:
| プロパティ名 | 型 |
|---|---|
HourlyWage | int |
実行結果例:
社員ID:2001氏名:佐藤花子部署:開発部時給:1800円条件:
ContractEmployee : EmployeeBaseと書くEmployeeBaseの共通プロパティを使うHourlyWageを表示する
課題13-3 baseで親クラスのコンストラクターを呼び出す
Section titled “課題13-3 baseで親クラスのコンストラクターを呼び出す”EmployeeBase に引数付きコンストラクターを作成し、子クラスから base を使って呼び出してください。
実行結果例:
社員ID:1001氏名:山田太郎部署:営業部月給:300000円条件:
EmployeeBaseにコンストラクターを作成するRegularEmployeeのコンストラクターからbase(...)を呼び出す- プロパティは可能であれば
private setにする
課題13-4 virtualとoverrideを使う
Section titled “課題13-4 virtualとoverrideを使う”EmployeeBase に GetPaymentAmount メソッドを作成し、RegularEmployee で上書きしてください。
仕様:
| クラス | GetPaymentAmount の戻り値 |
|---|---|
EmployeeBase | 0 |
RegularEmployee | MonthlySalary |
実行結果例:
山田太郎:300000円条件:
- 親クラスのメソッドに
virtualを付ける - 子クラスのメソッドに
overrideを付ける
必須課題が終わった人は、発展課題に取り組んでください。
課題13-5 契約社員の支給額を計算する
Section titled “課題13-5 契約社員の支給額を計算する”ContractEmployee で GetPaymentAmount を上書きし、HourlyWage * WorkHours を返してください。
ContractEmployee のプロパティ:
| プロパティ名 | 型 |
|---|---|
HourlyWage | int |
WorkHours | int |
実行結果例:
佐藤花子:216000円条件:
ContractEmployeeでGetPaymentAmountをoverrideする- 時給 × 勤務時間で支給額を求める
課題13-6 Listでまとめて処理する
Section titled “課題13-6 Listでまとめて処理する”RegularEmployee と ContractEmployee を List<EmployeeBase> に入れ、foreach で支給額を表示してください。
実行結果例:
山田太郎:300000円佐藤花子:216000円条件:
List<EmployeeBase>を使うRegularEmployeeとContractEmployeeを同じリストに入れるforeachでGetPaymentAmountを呼び出す- オブジェクトごとに異なる結果になることを確認する
課題13-7 抽象クラスにする
Section titled “課題13-7 抽象クラスにする”EmployeeBase を抽象クラスに変更し、GetPaymentAmount を抽象メソッドにしてください。
条件:
abstract class EmployeeBaseとするpublic abstract int GetPaymentAmount();を定義する- 子クラスで必ず
overrideする new EmployeeBase(...)ができないことを確認する
課題13-8 ReportBaseを作成する
Section titled “課題13-8 ReportBaseを作成する”社員以外の題材で、継承とポリモーフィズムを確認します。
次のクラスを作成してください。
| クラス | 内容 |
|---|---|
ReportBase | 抽象クラス。Title プロパティと Print 抽象メソッドを持つ |
SalesReport | 売上レポートを表示する |
AttendanceReport | 勤怠レポートを表示する |
実行結果例:
売上レポートを出力します。勤怠レポートを出力します。条件:
ReportBaseを抽象クラスにするPrintを抽象メソッドにするSalesReportとAttendanceReportでPrintをoverrideするList<ReportBase>に入れてforeachでPrintを呼び出す
Gitへの提出
Section titled “Gitへの提出”課題が終わったら、できたところまでをGitに提出します。
まず、現在の状態を確認します。
git status変更されたファイルを追加します。
git add .コミットします。
git commit -m "Chapter13 継承とポリモーフィズム"ファイルサーバー上のリポジトリへpushします。
git pushGitの操作でエラーが出た場合は、自己判断で同じ操作を繰り返さず、講師に確認してください。
提出前チェックリスト
Section titled “提出前チェックリスト”提出前に、次の項目を確認してください。
- 親クラスを作成できている
- 子クラスで親クラスを継承できている
-
:を使って継承を書けている - 親クラスのプロパティを子クラスから使えている
-
baseで親クラスのコンストラクターを呼び出せている -
virtualを使えている -
overrideを使えている -
List<親クラス>に子クラスのオブジェクトを入れている - 同じメソッド呼び出しで異なる動きを確認できている
- 抽象クラスまたは抽象メソッドを使えている
- インデントが整っている
- Gitにcommitしている
- Gitにpushしている
この章のまとめ
Section titled “この章のまとめ”この章では、継承とポリモーフィズムについて学習しました。
この章で学んだ主な内容は次の通りです。
- 継承は、親クラスの機能を子クラスが受け継ぐ仕組みである
- 共通するプロパティやメソッドを親クラスにまとめられる
class 子クラス : 親クラスの形で継承を書く- 子クラスは、親クラスのプロパティやメソッドを利用できる
baseを使うと、親クラスのコンストラクターを呼び出せるvirtualを付けたメソッドは、子クラスで上書きできる- 子クラスで上書きするメソッドには
overrideを付ける - ポリモーフィズムとは、同じ呼び出し方で実際のオブジェクトに応じて異なる動きをすることである
- 親クラス型の変数に、子クラスのオブジェクトを代入できる
List<親クラス>を使うと、複数種類の子クラスをまとめて扱える- 抽象クラスは、直接
newせず、共通部分をまとめるために使う - 抽象メソッドは、子クラスで実装を強制できる
- 継承は便利だが、何でも継承すればよいわけではない
次章では、インターフェイス を学習します。
インターフェイスは、クラスに「この機能を持っていること」を約束させる仕組みです。
継承や抽象クラスと同じく、現場の既存コードを読むうえで非常に重要な考え方です。