はじめに
コードのあちこちに同じようなswitch文処理が書かれている場合があります。
機能追加・修正するたびに変更が必要なswitch文処理を探してコード修正しなくてはなりません。
このようなswitch文処理を見つけたら、ポリモーフィズムが使うことで、リファクタリングできないか考えてみましょう。
また、他のクラスのインスタンスを取得してswitch文で条件分岐処理している場合は、リファクタリングの優先度が高いと考えています。
書籍
- 新装版 リファクタリング 既存のコードを安全に改善する
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
{
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
{
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)
{
if (employ != null)
{
System.out.println(employ.getTypeName());
}
}
public static void showTypeName(Employee employ)
{
if (employ != null)
{
System.out.println(employ.getTypeId());
}
}
}
[CreatorEmployee.java]
public final class 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 CreatorEmployee(){ }
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)
{
if (employ != null)
{
System.out.println(employ.getTypeName());
}
}
でも、ご安心ください。そのための秘策として、NullObjectパターンがあります。
以下の記事に、NullObjectパターンを用いて、Nullチェックを削除する方法について記載しました
- Nullチェックを削除する方法 (NullObjectパターンとNull合体演算子)
リンク
Factory MethodパターンとAbstract Factory Methodパターンの違いについては、以下のサイトが詳しいです。