Skip to content

第13章 継承とポリモーフィズム

この章では、オブジェクト指向プログラミングの重要な考え方である 継承ポリモーフィズム を学習します。

第7章から第12章までで、クラス、オブジェクト、プロパティ、メソッド、コンストラクター、List、LINQを学習してきました。

これまで作成してきたクラスは、基本的に1つずつ独立していました。

class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; } = "";
}

しかし、実際のプログラムでは、複数のクラスに共通する情報や処理が出てくることがあります。

たとえば、社員には次のような種類があるかもしれません。

正社員
契約社員
アルバイト

それぞれ違いはありますが、共通する情報もあります。

社員ID
氏名
部署名
社員情報を表示する処理

このような共通部分を親となるクラスにまとめ、そこから別のクラスを作る仕組みが 継承 です。

また、共通の型として扱いながら、実際のオブジェクトによって異なる動きをさせる考え方を ポリモーフィズム といいます。

この章では、まず次のことを目標にします。

継承の基本的な書き方を知る
親クラスと子クラスの関係を理解する
overrideによってメソッドの動きを変えられることを知る
Listの中で共通の型として扱えることを理解する
既存コードで継承が出てきても読めるようになる

補足

継承は強力な機能ですが、何でも継承すればよいわけではありません。 この研修では、継承を多用した設計を目指すのではなく、まずは「読める」「基本的な動きが分かる」ことを重視します。


この章でできるようになること

Section titled “この章でできるようになること”

この章を終えると、次のことができるようになります。

  • 継承とは何かをおおまかに説明できる
  • 親クラスと子クラスの関係を説明できる
  • : を使ってクラスを継承できる
  • 子クラスから親クラスのプロパティやメソッドを利用できる
  • base を使って親クラスのコンストラクターを呼び出せる
  • virtualoverride の役割を説明できる
  • 子クラスで親クラスのメソッドを上書きできる
  • ポリモーフィズムの基本的な考え方を説明できる
  • 親クラス型の変数に子クラスのオブジェクトを代入できる
  • List<親クラス> で複数種類の子クラスをまとめて扱える
  • 抽象クラスの基本的な考え方を説明できる

項目内容
開発環境Visual Studio 2022
プロジェクト種類コンソール アプリ
対象フレームワーク.NET 8
プロジェクト名Chapter13_Inheritance

作業を始める前に、次の内容を確認してください。

  • クラスを作成できる
  • プロパティを定義できる
  • メソッドを定義できる
  • コンストラクターを作成できる
  • List<T> を使える
  • foreach 文を使える
  • 第12章の内容をGitに提出済みである

まず、正社員を表す 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つのクラスには、共通する部分があります。

EmployeeId
EmployeeName
DepartmentName
PrintProfile()

一方で、違う部分もあります。

RegularEmployee
→ MonthlySalary
ContractEmployee
→ HourlyWage

共通する部分を毎回書くと、コードが重複します。

そこで、共通部分を親クラスにまとめます。


社員に共通する情報を 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 クラスは、社員に共通する基本情報を持つクラスです。


EmployeeBase を継承して、正社員クラスを作成します。

class RegularEmployee : EmployeeBase
{
public int MonthlySalary { get; set; }
}

: の後ろに親クラス名を書くことで、クラスを継承できます。

class 子クラス : 親クラス
{
}

RegularEmployee は、EmployeeBase を継承しているため、EmployeeBase のプロパティやメソッドを利用できます。


次のコードを入力して実行してください。

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 クラスには、EmployeeIdEmployeeNameDepartmentNamePrintProfile を直接書いていません。

しかし、親クラスである EmployeeBase から受け継いでいるため、利用できます。


継承では、次のような用語が使われます。

用語意味
親クラス継承元のクラスEmployeeBase
子クラス継承先のクラスRegularEmployee
基底クラス親クラスとほぼ同じ意味EmployeeBase
派生クラス子クラスとほぼ同じ意味RegularEmployee

現場の資料やコードでは、親クラス・子クラスだけでなく、基底クラス・派生クラスという言葉も出てきます。

この章では、まず次のように考えておきましょう。

親クラス
→ 共通部分を持つクラス
子クラス
→ 親クラスを受け継ぎ、独自の情報や処理を追加するクラス

EmployeeBase を継承して、正社員と契約社員を作成します。

class RegularEmployee : EmployeeBase
{
public int MonthlySalary { get; set; }
}
class ContractEmployee : EmployeeBase
{
public int HourlyWage { get; set; }
}

どちらも EmployeeBase を継承しているため、社員ID、氏名、部署名を持ちます。


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関係 で説明されます。

RegularEmployee is an EmployeeBase
正社員は社員の一種である
ContractEmployee is an EmployeeBase
契約社員は社員の一種である

このように、「AはBの一種である」と自然に言える場合、継承の候補になります。

一方、次のような関係は継承に向いていません。

社員は部署の一種である
商品は注文の一種である

このように言うと不自然です。

継承は便利ですが、関係が自然かどうかを考える必要があります。


初学者が継承を学ぶと、何でも親子関係にしたくなることがあります。

しかし、実務では、継承を使いすぎるとコードが読みにくくなることもあります。

この研修では、次の程度の理解を目指します。

継承は共通部分をまとめる方法の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(...) の部分で、親クラスのコンストラクターを呼び出しています。


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 は、親クラスを表します。

: base(employeeId, employeeName, departmentName)

これは、次のような意味です。

親クラスであるEmployeeBaseのコンストラクターに、
employeeId、employeeName、departmentNameを渡す

子クラス独自の値は、子クラスのコンストラクター内で設定します。

MonthlySalary = monthlySalary;

このように、共通部分は親クラス、個別部分は子クラスで扱います。


子クラスごとに処理を変えたい

Section titled “子クラスごとに処理を変えたい”

正社員と契約社員で、給与の計算方法が異なる場合を考えます。

正社員
→ 月給をそのまま支給額とする
契約社員
→ 時給 × 勤務時間を支給額とする

どちらも「支給額を求める」という目的は同じです。

しかし、計算方法が違います。

このようなときに、親クラスのメソッドを子クラスで上書きできます。


親クラス側では、上書きしてよいメソッドに virtual を付けます。

public virtual int GetPaymentAmount()
{
return 0;
}

子クラス側では、上書きするメソッドに override を付けます。

public override int GetPaymentAmount()
{
return MonthlySalary;
}

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円

次に、契約社員クラスを追加します。

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 というメソッド名でも、正社員と契約社員で処理内容が異なります。


ポリモーフィズムは、日本語では 多態性 と訳されます。

少し難しい言葉ですが、この章では次のように考えてください。

同じ呼び出し方で、
実際のオブジェクトに応じて異なる動きをすること

先ほどの例では、どちらも同じように呼び出しています。

employee.GetPaymentAmount()

しかし、実際のオブジェクトが正社員なら月給を返し、契約社員なら時給×勤務時間を返します。


親クラス型の変数に子クラスを代入する

Section titled “親クラス型の変数に子クラスを代入する”

継承関係がある場合、親クラス型の変数に子クラスのオブジェクトを代入できます。

EmployeeBase employee1 = new RegularEmployee(
1001,
"山田太郎",
"営業部",
300000
);
EmployeeBase employee2 = new ContractEmployee(
2001,
"佐藤花子",
"開発部",
1800,
120
);

RegularEmployeeContractEmployee も、EmployeeBase の一種なので、EmployeeBase 型の変数に入れられます。


親クラス型を使うと、異なる種類の子クラスを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が呼ばれる

これがポリモーフィズムの基本です。


ポリモーフィズムを使うと、呼び出し側のコードをシンプルにできます。

foreach (EmployeeBase employee in employees)
{
Console.WriteLine($"{employee.EmployeeName}{employee.GetPaymentAmount()}");
}

呼び出し側は、正社員か契約社員かを細かく判定していません。

if (employee is RegularEmployee)
{
// 正社員用の処理
}
else if (employee is ContractEmployee)
{
// 契約社員用の処理
}

このような分岐を減らし、各クラスに自分の処理を任せることができます。


EmployeeBaseのGetPaymentAmountは本当に必要か

Section titled “EmployeeBaseのGetPaymentAmountは本当に必要か”

ここまでの例では、親クラス EmployeeBase に次のメソッドを書いていました。

public virtual int GetPaymentAmount()
{
return 0;
}

しかし、EmployeeBase は社員の共通部分を表すクラスです。

正社員でも契約社員でもない、ただの EmployeeBase オブジェクトを作る必要はあまりありません。

また、支給額の計算方法も、子クラスによって決まるものです。

このような場合、親クラスを 抽象クラス にできます。


抽象クラスは、abstract キーワードを付けて定義します。

abstract class EmployeeBase
{
}

抽象クラスは、new で直接オブジェクトを作れません。

EmployeeBase employee = new EmployeeBase();

このコードはエラーになります。


抽象クラスには、子クラスで必ず実装してほしいメソッドを定義できます。

public abstract int GetPaymentAmount();

このようなメソッドを 抽象メソッド といいます。

抽象メソッドには、処理の中身を書きません。

public abstract int GetPaymentAmount();

子クラス側では、必ず override して処理を書きます。


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円

抽象クラスには、次の特徴があります。

特徴内容
abstract を付ける抽象クラスになる
直接 new できない親クラスそのもののオブジェクトは作れない
共通のプロパティやメソッドを持てる通常のクラスと同じように書ける
抽象メソッドを持てる子クラスで実装を強制できる

この章では、次のように理解してください。

抽象クラス
→ 共通部分をまとめるが、それ自体は直接使わない親クラス
抽象メソッド
→ 子クラスで必ず実装してほしいメソッド

次章では、インターフェイス を学習します。

抽象クラスとインターフェイスは、どちらもポリモーフィズムに関係します。

この章では、抽象クラスを次のように理解しておけば十分です。

抽象クラス
→ 共通する情報や処理を持たせられる親クラス
インターフェイス
→ 必ず持つべき機能の約束を表すもの

詳しくは次章で扱います。


13-7 継承を読むときのポイント

Section titled “13-7 継承を読むときのポイント”

現場では「使う」より「読む」場面が多い

Section titled “現場では「使う」より「読む」場面が多い”

新人研修後の現場では、自分で一から継承設計をするよりも、既存コードを読む場面の方が多いかもしれません。

継承を使ったコードを読むときは、次の順番で確認するとよいです。

1. このクラスは何を継承しているか
2. 親クラスにはどのようなプロパティやメソッドがあるか
3. 子クラスで追加されたプロパティやメソッドは何か
4. overrideされているメソッドは何か
5. Listや変数が親クラス型で扱われていないか

まず、クラス定義の1行目を確認します。

class RegularEmployee : EmployeeBase

この場合、RegularEmployeeEmployeeBase を継承しています。

つまり、RegularEmployee には、EmployeeBase のプロパティやメソッドも含まれていると考えます。


次に、override が付いたメソッドを探します。

public override int GetPaymentAmount()

override がある場合、親クラスで定義されたメソッドの動きを、子クラス側で上書きしています。

そのため、処理内容を確認するときは、親クラスだけでなく子クラス側の実装も見る必要があります。


次のようなコードにも注意します。

EmployeeBase employee = new RegularEmployee(...);

変数の型は EmployeeBase ですが、実際のオブジェクトは RegularEmployee です。

この状態で employee.GetPaymentAmount() を呼び出すと、RegularEmployee 側の GetPaymentAmount が実行されます。

この動きに慣れると、ポリモーフィズムを使ったコードを読みやすくなります。


この章でよくあるつまずきを確認します。

つまずき原因対応
継承の意味が分からない親子関係のイメージが曖昧共通部分を親クラスにまとめると考える
: の意味が分からない継承の書き方に慣れていないclass 子 : 親 は「親を継承する」
子クラスに書いていないプロパティが使える理由が分からない親クラスから受け継いでいることを見落としている親クラスの定義を確認する
base が分からない親クラスを表すキーワードに慣れていないbase は親クラスを指す
virtualoverride が分からないメソッドの上書きに慣れていない親が virtual、子が override
親クラス型に子クラスを代入できる理由が分からないis-a関係が曖昧子クラスは親クラスの一種と考える
List<EmployeeBase> に複数種類を入れられる理由が分からない共通の親クラスとして扱う考え方に慣れていない正社員も契約社員もEmployeeBaseの一種
抽象クラスをnewできない抽象クラスの役割が曖昧抽象クラスは直接使う親クラスではない
継承を使うべき場面が分からない何でも継承しようとしている「AはBの一種」と自然に言えるか確認する

次の項目について、自分で説明できるか確認してください。

  • 継承とは何かを説明できる
  • 親クラスと子クラスの関係を説明できる
  • class 子クラス : 親クラス の書き方を理解している
  • 子クラスから親クラスのプロパティやメソッドを使えることを説明できる
  • base の役割を説明できる
  • 親クラスのコンストラクターを子クラスから呼び出せる
  • virtual の役割を説明できる
  • override の役割を説明できる
  • ポリモーフィズムとは何かをおおまかに説明できる
  • 親クラス型の変数に子クラスのオブジェクトを代入できることを説明できる
  • List<親クラス> で複数種類の子クラスを扱えることを説明できる
  • 抽象クラスとは何かをおおまかに説明できる
  • 抽象メソッドは子クラスで実装が必要であることを説明できる
  • 継承を使いすぎるべきではないことを理解している

研修の進め方によっては、隣の人またはチーム内で説明確認を行います。

次の内容を、自分の言葉で説明してください。

  1. 継承はどのようなときに使いますか。
  2. 親クラスと子クラスの違いは何ですか。
  3. RegularEmployee : EmployeeBase は何を意味していますか。
  4. base(...) は何をしていますか。
  5. virtualoverride は何のために使いますか。
  6. ポリモーフィズムとは、ひとまずどのような考え方ですか。
  7. List<EmployeeBase>RegularEmployeeContractEmployee を入れられるのはなぜですか。
  8. 抽象クラスを直接 new できないのはなぜですか。

説明するときは、完全な答えでなくても構いません。
自分の言葉で説明しようとすることが大切です。


この章の演習課題に取り組みます。

制限時間は 80分 です。

時間内にすべて完成しなくても構いません。
できたところまでを保存し、Gitに提出してください。


まずは、全員が必須課題に取り組んでください。


社員の共通情報を持つ EmployeeBase クラスを作成し、それを継承する RegularEmployee クラスを作成してください。

EmployeeBase のプロパティ:

プロパティ名
EmployeeIdint
EmployeeNamestring
DepartmentNamestring

RegularEmployee のプロパティ:

プロパティ名
MonthlySalaryint

実行結果例:

社員ID:1001
氏名:山田太郎
部署:営業部
月給:300000円

条件:

  • EmployeeBase クラスを作成する
  • RegularEmployee : EmployeeBase と書く
  • 親クラスのプロパティを子クラスから利用する

課題13-2 ContractEmployeeを追加する

Section titled “課題13-2 ContractEmployeeを追加する”

EmployeeBase を継承する ContractEmployee クラスを作成してください。

ContractEmployee のプロパティ:

プロパティ名
HourlyWageint

実行結果例:

社員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 にする

EmployeeBaseGetPaymentAmount メソッドを作成し、RegularEmployee で上書きしてください。

仕様:

クラスGetPaymentAmount の戻り値
EmployeeBase0
RegularEmployeeMonthlySalary

実行結果例:

山田太郎:300000円

条件:

  • 親クラスのメソッドに virtual を付ける
  • 子クラスのメソッドに override を付ける

必須課題が終わった人は、発展課題に取り組んでください。


課題13-5 契約社員の支給額を計算する

Section titled “課題13-5 契約社員の支給額を計算する”

ContractEmployeeGetPaymentAmount を上書きし、HourlyWage * WorkHours を返してください。

ContractEmployee のプロパティ:

プロパティ名
HourlyWageint
WorkHoursint

実行結果例:

佐藤花子:216000円

条件:

  • ContractEmployeeGetPaymentAmountoverride する
  • 時給 × 勤務時間で支給額を求める

課題13-6 Listでまとめて処理する

Section titled “課題13-6 Listでまとめて処理する”

RegularEmployeeContractEmployeeList<EmployeeBase> に入れ、foreach で支給額を表示してください。

実行結果例:

山田太郎:300000円
佐藤花子:216000円

条件:

  • List<EmployeeBase> を使う
  • RegularEmployeeContractEmployee を同じリストに入れる
  • foreachGetPaymentAmount を呼び出す
  • オブジェクトごとに異なる結果になることを確認する

EmployeeBase を抽象クラスに変更し、GetPaymentAmount を抽象メソッドにしてください。

条件:

  • abstract class EmployeeBase とする
  • public abstract int GetPaymentAmount(); を定義する
  • 子クラスで必ず override する
  • new EmployeeBase(...) ができないことを確認する

社員以外の題材で、継承とポリモーフィズムを確認します。

次のクラスを作成してください。

クラス内容
ReportBase抽象クラス。Title プロパティと Print 抽象メソッドを持つ
SalesReport売上レポートを表示する
AttendanceReport勤怠レポートを表示する

実行結果例:

売上レポートを出力します。
勤怠レポートを出力します。

条件:

  • ReportBase を抽象クラスにする
  • Print を抽象メソッドにする
  • SalesReportAttendanceReportPrintoverride する
  • List<ReportBase> に入れて foreachPrint を呼び出す

課題が終わったら、できたところまでをGitに提出します。

まず、現在の状態を確認します。

Terminal window
git status

変更されたファイルを追加します。

Terminal window
git add .

コミットします。

Terminal window
git commit -m "Chapter13 継承とポリモーフィズム"

ファイルサーバー上のリポジトリへpushします。

Terminal window
git push

Gitの操作でエラーが出た場合は、自己判断で同じ操作を繰り返さず、講師に確認してください。


提出前に、次の項目を確認してください。

  • 親クラスを作成できている
  • 子クラスで親クラスを継承できている
  • : を使って継承を書けている
  • 親クラスのプロパティを子クラスから使えている
  • base で親クラスのコンストラクターを呼び出せている
  • virtual を使えている
  • override を使えている
  • List<親クラス> に子クラスのオブジェクトを入れている
  • 同じメソッド呼び出しで異なる動きを確認できている
  • 抽象クラスまたは抽象メソッドを使えている
  • インデントが整っている
  • Gitにcommitしている
  • Gitにpushしている

この章では、継承とポリモーフィズムについて学習しました。

この章で学んだ主な内容は次の通りです。

  • 継承は、親クラスの機能を子クラスが受け継ぐ仕組みである
  • 共通するプロパティやメソッドを親クラスにまとめられる
  • class 子クラス : 親クラス の形で継承を書く
  • 子クラスは、親クラスのプロパティやメソッドを利用できる
  • base を使うと、親クラスのコンストラクターを呼び出せる
  • virtual を付けたメソッドは、子クラスで上書きできる
  • 子クラスで上書きするメソッドには override を付ける
  • ポリモーフィズムとは、同じ呼び出し方で実際のオブジェクトに応じて異なる動きをすることである
  • 親クラス型の変数に、子クラスのオブジェクトを代入できる
  • List<親クラス> を使うと、複数種類の子クラスをまとめて扱える
  • 抽象クラスは、直接 new せず、共通部分をまとめるために使う
  • 抽象メソッドは、子クラスで実装を強制できる
  • 継承は便利だが、何でも継承すればよいわけではない

次章では、インターフェイス を学習します。

インターフェイスは、クラスに「この機能を持っていること」を約束させる仕組みです。
継承や抽象クラスと同じく、現場の既存コードを読むうえで非常に重要な考え方です。