はじめに
コードのあちこちに同じようなswitch文処理が書かれている場合があります。
機能追加・修正するたびに変更が必要なswitch文処理を探してコード修正しなくてはなりません。
このようなswitch文処理を見つけたら、ポリモーフィズムが使うことで、リファクタリングできないか考えてみましょう。
また、他のクラスのインスタンスを取得してswitch文で条件分岐処理している場合は、リファクタリングの優先度が高いと考えています。
書籍
- 新装版 リファクタリング 既存のコードを安全に改善する
- Martin Fowler [著]
- P83
static Factory Method Patternを用いて、switch文を削除
switch文を削除する方法はポリモーフィズムを使用しますが、ポリモーフィズムの使用方法はいくつかあります。
そのときの状況で最適な方法が変化すると思います。
本記事では、(1)の方法を使用して、コード例のswitch文を削除します。
- static Factory Method Patternで削除
- Factory Method Patternで削除
- Stateパターンで削除
- Starategyパターンで削除
コード例
コード例は、従業員の職業ID値と職業名を表示する処理です。
Mainクラスで、Employeeクラスのインスタンスを生成します。
そして、MainクラスのshowId関数とshowTypeName関数で、Employeeクラスのインスタンスの情報を元に、switch文で条件分岐処理をします。
本コードを(1)の方法で、リファクタリングしていきます。
言語はJavaを使用してますが、オブジェクト指向プログラミングが実現できる言語であれば、どんな言語でも実現可能です。
修正前のコード
本コードで問題なのは、従業員の職業IDの種類が増えるたびに、MainクラスのshowId関数とshowTypeName関数の両方をコード修正しなくてはなりません。
Mainクラスが仮に巨大で複雑なクラスであった場合は、コード修正洩れが発生しやすいです・・・。
クラス図
[Main.java]
import java.util.*; public class Main { public static final int ENGINEER = 0; public static final int SLAESMAN = 1; public static final int MANAGER = 2; public static void main(String[] args) throws Exception { // (1) インスタンスを生成 // (2) 関数に作成したインスタンスを渡す Employee engineer = new Employee(ENGINEER); showTypeName(engineer); showId(engineer); Employee salesman = new Employee(SLAESMAN); showTypeName(salesman); showId(salesman); Employee manager = new Employee(MANAGER); showTypeName(manager); showId(manager); Employee nullEmployee = new Employee(-9999); // 不正値を入力する showTypeName(nullEmployee); showId(nullEmployee); } public static void showId(Employee employ) { switch(employ._typeId) { case ENGINEER: System.out.println(employ._typeId); break; case SLAESMAN: System.out.println(employ._typeId); break; case MANAGER: System.out.println(employ._typeId); break; default: System.out.println(employ._typeId); break; } } public static void showTypeName(Employee employ) { switch(employ._typeId) { case ENGINEER: System.out.println("ENGINEER"); break; case SLAESMAN: System.out.println("SLAESMAN"); break; case MANAGER: System.out.println("MANAGER"); break; default: System.out.println("Error!! Illegal Parameter."); break; } } }
[Employee.java]
public class Employee { public int _typeId; public Employee(int type) { _typeId = type; } }
ENGINEER 0 SLAESMAN 1 MANAGER 2 Error!! Illegal Parameter. -9999
修正後のコード
static Factory Methodパターンを用いて、MainクラスのshowId関数とshowTypeName関数のswitch文を削除します。
手順
おおまかな手順は以下のとおりです。
- サブクラス(本例では、Enginner, Salesman, Managerクラス)を作成します。
共通部分はスーパークラス(本例では、Empolyeeクラス)で定義します
Creatorクラス(本例では、CreatorEmployeeクラス)を作成します。
- Creatorクラスは、インスタンス化しません(インスタンス化を防ぎます)
- factoryMethodメソッドで、サブクラスのインスタンスを生成します
Mainクラスのswitch文を削除します
クラス図
[Main.java]
import java.util.*; public class Main { public static void main(String[] args) throws Exception { // (1) インスタンスを生成 // (2) 関数に作成したインスタンスを渡す Employee engineer = createEmployee(CreatorEmployee.ENGINEER); showTypeName(engineer); showId(engineer); Employee salesman = createEmployee(CreatorEmployee.SALESMAN); showTypeName(salesman); showId(salesman); Employee manager = createEmployee(CreatorEmployee.MANAGER); showTypeName(manager); showId(manager); Employee nullEmployee = createEmployee(-9999); // 不正値を入力する showTypeName(nullEmployee); showId(nullEmployee); } public static Employee createEmployee(int type) { return CreatorEmployee.factoryMethod(type); } public static void showId(Employee employ) { // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。 if (employ != null) { System.out.println(employ.getTypeName()); } } public static void showTypeName(Employee employ) { // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。 if (employ != null) { System.out.println(employ.getTypeId()); } } }
[CreatorEmployee.java]
// 本クラスは継承を禁止するため、final修飾子を付与します public final class CreatorEmployee { // 本例では、定数の定義はCreatorEmployeeクラスが持ちます public static final int ENGINEER = 0; public static final int SALESMAN = 1; public static final int MANAGER = 2; public static final int ILLEGAL_EMPLOYEE = -1; // コンストラクタをprivateにすることで、インスタンス化を禁止させましょう private CreatorEmployee(){ } // factoryMethodメソッドの役割は与えられたパラメータによって生成するインスタンスを切り替えること public static Employee factoryMethod(int type) { switch(type) { case ENGINEER: return new Engineer(ENGINEER); case SALESMAN: return new Salesman(SALESMAN); case MANAGER: return new Manager(MANAGER); default: System.out.println("Error!! Illegal Parameter."); return null; } } }
[Employee.java]
// 抽象クラス public abstract class Employee { protected int _typeId; protected String _typeName; public abstract int getTypeId(); public abstract String getTypeName(); }
[Engineer.java]
// 具象クラス public class Engineer extends Employee { public Engineer(int typeId) { _typeId = typeId; } @Override public int getTypeId() { return _typeId; } @Override public String getTypeName() { return "Engineer"; } }
[Salesman.java]
// 具象クラス public class Salesman extends Employee { public Salesman(int typeId) { _typeId = typeId; } @Override public int getTypeId() { return _typeId; } @Override public String getTypeName() { return "Salesman"; } }
[Manager.java]
public class Manager extends Employee { public Manager(int typeId) { _typeId = typeId; } @Override public int getTypeId() { return _typeId; } @Override public String getTypeName() { return "Manager"; } }
0 Engineer 1 Salesman 2 Manager Error!! Illegal Parameter.
まだ、switch文残っているじゃん・・・。
はい。まだswitch文残っています。CreatorEmployeeクラスのfactoryMethodメソッドは、引数に異常値を渡した場合、戻り値としてNullのインスタンスを返します。
そのため、factoryMethodメソッド関数の呼び出し元では、Nullチェックが必要なのです。(Nullチェックしないと、JavaではNull Pointer Exceptionのエラーが発生)
public static void showId(Employee employ) { // switch文を撲滅完了。ただし、NullPointerExceptionの例外を防ぐため、Nullチェックのif文は必要。 if (employ != null) { System.out.println(employ.getTypeName()); } }
でも、ご安心ください。そのための秘策として、NullObjectパターンがあります。
以下の記事に、NullObjectパターンを用いて、Nullチェックを削除する方法について記載しました
- Nullチェックを削除する方法 (NullObjectパターンとNull合体演算子)
リンク
Factory MethodパターンとAbstract Factory Methodパターンの違いについては、以下のサイトが詳しいです。