nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

switch文を爆散

はじめに

コードのあちこちに同じようなswitch文処理が書かれている場合があります。

機能追加・修正するたびに変更が必要なswitch文処理を探してコード修正しなくてはなりません。

このようなswitch文処理を見つけたら、ポリモーフィズムが使うことで、リファクタリングできないか考えてみましょう。

また、他のクラスのインスタンスを取得してswitch文で条件分岐処理している場合は、リファクタリングの優先度が高いと考えています。

書籍

  • 新装版 リファクタリング 既存のコードを安全に改善する
    • Martin Fowler [著]
    • P83

static Factory Method Patternを用いて、switch文を削除

switch文を削除する方法はポリモーフィズムを使用しますが、ポリモーフィズムの使用方法はいくつかあります。

そのときの状況で最適な方法が変化すると思います。

本記事では、(1)の方法を使用して、コード例のswitch文を削除します。

  1. static Factory Method Patternで削除
  2. Factory Method Patternで削除
  3. Stateパターンで削除
  4. Starategyパターンで削除

コード例

コード例は、従業員の職業ID値と職業名を表示する処理です。

Mainクラスで、Employeeクラスのインスタンスを生成します。

そして、MainクラスのshowId関数とshowTypeName関数で、Employeeクラスのインスタンスの情報を元に、switch文で条件分岐処理をします。

本コードを(1)の方法で、リファクタリングしていきます。

言語はJavaを使用してますが、オブジェクト指向プログラミングが実現できる言語であれば、どんな言語でも実現可能です。

修正前のコード

本コードで問題なのは、従業員の職業IDの種類が増えるたびに、MainクラスのshowId関数とshowTypeName関数の両方をコード修正しなくてはなりません。

Mainクラスが仮に巨大で複雑なクラスであった場合は、コード修正洩れが発生しやすいです・・・。

クラス図

f:id:nprogram:20190618071624p:plain

[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文を削除します。

手順

おおまかな手順は以下のとおりです。

  1. サブクラス(本例では、Enginner, Salesman, Managerクラス)を作成します。
  2. 共通部分はスーパークラス(本例では、Empolyeeクラス)で定義します

  3. Creatorクラス(本例では、CreatorEmployeeクラス)を作成します。

    • Creatorクラスは、インスタンス化しません(インスタンス化を防ぎます)
    • factoryMethodメソッドで、サブクラスのインスタンスを生成します
  4. Mainクラスのswitch文を削除します

クラス図

f:id:nprogram:20190619230651p:plain

[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チェックを削除する方法について記載しました

リンク

Factory MethodパターンとAbstract Factory Methodパターンの違いについては、以下のサイトが詳しいです。