Skip to content.

Sections
Personal tools
You are here: Home » 技術文書 » リファクタリング » リファクタリングしてみよう2

Document Actions
リファクタリングしてみよう

(株)永和システムマネジメント    梅田 政利
作成日:初出:ソフトバンク『C MAGAZINE 2003/6』

それでは実際にリファクタリングを体験してみましょう。使用する言語はJavaです。

   「フィールドの引き上げ(320)」の適用

「フィールドの引き上げ(320)」の手順

  • フィールドの名前が違う場合、スーパークラスのフィールドとして使いたい名前に変更する
  • コンパイルしてテストする
  • スーパークラスに新しいフィールドを作成する
     =>フィールドがprivateならば、サブクラスから参照できるようにprotectedに変更する
  • サブクラスのフィールドを削除する
  • コンパイルしてテストする
  • そのフィールドに対して「自己カプセル化フィールド(171)」の適用を検討する

第一の問題点に適用するリファクタリングは、「フィールドの引き上げ(320)」です。 慣れないうちは、カタログに示された手順を守って、着実に実行します。 慣れたリファクタリングに対しては、多少ステップをまとめても良いですが、 要所要所でのテストはかかさないように気をつけましょう。 ここでは、ネストしていないステップ毎にテストを実行します。

1.フィールドを引き上げるため、CreaterインタフェースをCreaterクラスにする

1.1.CreaterインタフェースをCreater抽象クラスにする

1.2.Createrクラスの各メソッドをabstractにする

1.3.RectCreaterとLineCreaterを、"implements Creater"から"extends"に変更する
-----------------------------------------------------------------

[Creater]

public interface Creater {

    public void init(Point point);

    public void aid(Point point);

    public void create(Point point);

      ↓

public abstract class Creater {

    public abstract void init(Point point);

    public abstract void aid(Point point);

    public abstract void create(Point point);

-----------------------------------------------------------------

[RectCreater]

public class RectCreater implements Creater {

      ↓

public class RectCreater extends Creater {

-----------------------------------------------------------------

[LineCreater]

public class LineCreater implements Creater {

      ↓

public class LineCreater extends Creater {

-----------------------------------------------------------------



2.panelフィールドを引き上げる

2.1.Createrクラスに、"protected DrawPanel panel;"を作成する

  => サブクラスから参照できるようにprotectedにする

2.2.RectCreaterとLineCreaterからpanelフィールドを削除する

-----------------------------------------------------------------

[Creater]

public abstract class Creater {

    protected DrawPanel panel;

-----------------------------------------------------------------



3.フィールド store, startについて上記の手順を繰り返す

-----------------------------------------------------------------

[Creater]

public abstract class Creater {

    protected DrawEntityStore store;

    protected Point start;

-----------------------------------------------------------------



4.ラバーバンド用のフィールドを引き上げるため、型と名前を同じにする

  =>"Rectangle2D rect"と"Line2D line"はどちらもShapeクラスのサブクラス

4.1.RectCreaterクラスのrectフィールドの名前をrubberに変更する

  =>ラバーバンド用のフィールドであることを明示する

4.2.RectCreaterクラスのフィールドの型をShapeに変更する

  =>必要な箇所にはRectangle2Dへのキャストを追加する

4.3.LineCreaterクラスのlineフィールドの名前をrubberに変更する

4.4.LineCreaterクラスのフィールドの型をShapeに変更する

  =>必要な箇所にはLine2Dへのキャストを追加する

-----------------------------------------------------------------

[RectCreater]

public class RectCreater extends Creater {

    private Rectangle2D rect;

      ↓

    private Shape rubber;



    public RectCreater(DrawPanel panel, DrawEntityStore store) {

        ……

        rect = new Rectangle2D.Double();

      ↓

        rubber = new Rectangle2D.Double();

    }



    public void init(Point point) {

        ……

        rect.setRect(start.getX(), start.getY(), 0, 0);

        store.addRubberEntity(rect);

      ↓

        ((Rectangle2D)rubber).setRect(start.getX(), start.getY(), 0, 0);

        store.addRubberEntity(rubber);

    }



    public void aid(Point point) {

        resetRectangle(rect, point);

      ↓

        resetRectangle((Rectangle2D)rubber, point);

    }



    public void create(Point point) {

        ……

        store.removeRubberEntity(rect);

      ↓

        store.removeRubberEntity(rubber);

    }

-----------------------------------------------------------------

[LineCreater]

public class LineCreater extends Creater {

    private Line2D line;

      ↓

    private Shape rubber;



    public LineCreater(DrawPanel panel, DrawEntityStore store) {

        ……

        line = new Line2D.Double();

      ↓

        rubber = new Line2D.Double();

    }



    public void init(Point point) {

        ……

        line.setLine(start, start);

        store.addRubberEntity(line);

      ↓

        ((Line2D)rubber).setLine(start, start);

        store.addRubberEntity(rubber);

    }



    public void aid(Point point) {

        line.setLine(start, point);

      ↓

        ((Line2D)rubber).setLine(start, point);

    }



    public void create(Point point) {

        ……

        store.removeRubberEntity(line);

      ↓

        store.removeRubberEntity(rubber);

    }

-----------------------------------------------------------------



5.rubberフィールドを引き上げる

5.1.Createrクラスに、"protected Shape rubber;"を作成する

5.2.RectCreaterとLineCreaterからrubberフィールドを削除する

-----------------------------------------------------------------

[Creater]

public abstract class Creater {

    protected Shape rubber;

-----------------------------------------------------------------


これらのステップを実行した結果、次のような構造になりました。 サブクラスで重複していたフィールドが、全てスーパークラスに移動され、少しすっきりしましたね。 第一の問題点はこれで解決です。



-----> Creater.java <--------------------

import java.awt.*;



public abstract class Creater {



    protected DrawPanel panel;

    protected DrawEntityStore store;

    protected Point start;

    protected Shape rubber;



    // 入力開始

    public abstract void init(Point point);



    // 入力補助(ラバーバンド)

    public abstract void aid(Point point);



    // 入力確定(モデル登録)

    public abstract void create(Point point);

}

-----> Creater.java <--------------------



-----> RectCreater.java <--------------------

import java.awt.*;

import java.awt.geom.Rectangle2D;



public class RectCreater extends Creater {

    

    public RectCreater(DrawPanel panel, DrawEntityStore store) {

        this.panel = panel;

        this.store = store;

        rubber = new Rectangle2D.Double();

    }



    // 入力を開始する

    public void init(Point point) {

        start = point;

        ((Rectangle2D)rubber).setRect(start.getX(), start.getY(), 0, 0);

        store.addRubberEntity(rubber);

        if (panel != null) panel.repaint();

    }



    // 入力補助(ラバーバンド)

    public void aid(Point point) {

        resetRectangle((Rectangle2D)rubber, point);

        if (panel != null) panel.repaint();

    }



    // 入力確定する(作成)

    public void create(Point point) {

        Rectangle2D newRect = new Rectangle2D.Double();

        resetRectangle(newRect, point);

        store.addEntity(newRect);

        store.removeRubberEntity(rubber);

        if (panel != null) panel.repaint();

    }



    // 四角を調整する

    protected void resetRectangle(Rectangle2D rectangle, Point point) {

        if (start.x < point.x && start.y < point.y) {

            rectangle.setFrame(start.x, start.y, point.x - start.x, point.y - start.y);

        } else if (start.x < point.x && start.y > point.y) {

            rectangle.setFrame(start.x, point.y, point.x - start.x, start.y - point.y);

        } else if (start.x > point.x && start.y < point.y) {

            rectangle.setFrame(point.x, start.y, start.x - point.x, point.y - start.y);

        } else {

            rectangle.setFrame(point.x, point.y, start.x - point.x, start.y - point.y);

        }

    }

}

-----> RectCreater.java <--------------------





-----> LineCreater.java <--------------------

import java.awt.Point;

import java.awt.geom.Line2D;



public class LineCreater extends Creater {

    

    public LineCreater(DrawPanel panel, DrawEntityStore store) {

        this.panel = panel;

        this.store = store;

        rubber = new Line2D.Double();

    }



    // 入力開始

    public void init(Point point) {

        start = point;

        ((Line2D)rubber).setLine(start, start);

        store.addRubberEntity(rubber);

        if (panel != null) panel.repaint();

    }



    // 入力補助(ラバーバンド)

    public void aid(Point point) {

        ((Line2D)rubber).setLine(start, point);

        if (panel != null) panel.repaint();

    }



    // 入力確定(モデル登録)

    public void create(Point point) {

        Line2D newLine = new Line2D.Double(start, point);

        store.addEntity(newLine);

        store.removeRubberEntity(rubber);

        if (panel != null) panel.repaint();

    }

}

-----> LineCreater.java <--------------------

まずは、クラス図でツールの構造を、図形作成を行なう部分に注目して見てみましょう。 DrawAdapterクラスがマウス入力イベントを受け付け、それを変換してRectCreaterや LineCreaterといったCreaterインタフェースを実現したクラスに伝達するようになっています。 これと言って難しいところはありません。 楕円もこれらのクラスと同じく、Createrインタフェースを実現して作成されることになると思われます。
しかし、コードを見ると少し気になるところがあります。何か匂いませんか? RectCreaterクラスもLineCreaterクラスもなんだか似ていますね。 第一に、サブクラス同士に同じフィールドが見られます。 第二にコンストラクタ, init(), aid(), create()といった全ての振る舞いが、 似たような処理を同じ手順で実行しているのに気付くでしょう。 第一については、リファクタリングカタログから 「フィールドの引き上げ(320)」を適用しましょう。 第二はまぎれもなく不吉な匂い「重複したコード」です。 「Template Methodの形成(345)」を適用しましょう。

問題点 不吉な匂い 予定するリファクタリング
1 フィールドの引き上げ(320)
2 重複したコード Template Methodの形成(345)

リファクタリングにとりかかる前に必要なことは、テストを作成することです。 RectCreaterTest.javaでは、形状の作成方法が異なる4つの領域(図参照)の、ラバーバンド、 登録する図形の作成をテストします。今回はLineCreaterクラスのテストは省略します。 そして、全てのテストにパスすることを確認しましょう。 それではリファクタリング開始です。



-----> RectCreaterTest.java <--------------------

import java.awt.Point;

import java.awt.geom.Rectangle2D;



import junit.framework.TestCase;



public class RectCreaterTest extends TestCase {



    private DrawEntityStore store;

    private RectCreater creater;

    

    protected void setUp() throws Exception {

        super.setUp();

        store = new DrawEntityStore();       

        creater = new RectCreater(null, store);

    }



    // case1 : start.x < end.x && start.y < end.y

    public void testRubberCase1() {

        creater.init(new Point(20, 40));

        creater.aid(new Point(30, 60));

        Rectangle2D rect = (Rectangle2D)store.getRubberEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(20, 40, 10, 20);

        assertEquals(correctRect, rect);

    }        



    // case2 : start.x < end.x && start.y > end.y

    public void testRubberCase2() {

        creater.init(new Point(20, 40));

        creater.aid(new Point(40, 20));

        Rectangle2D rect = (Rectangle2D)store.getRubberEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(20, 20, 20, 20);

        assertEquals(correctRect, rect);

    }



    // case3 : start.x > end.x && start.y < end.y

    public void testRubberCase3() {

        creater.init(new Point(20, 40));

        creater.aid(new Point(10, 60));

        Rectangle2D rect = (Rectangle2D)store.getRubberEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(10, 40, 10, 20);

        assertEquals(correctRect, rect);

    }



    // case4 : start.x > end.x && start.y > end.y

    public void testRubberCase4() {

        creater.init(new Point(20, 40));

        creater.aid(new Point(10, 10));

        Rectangle2D rect = (Rectangle2D)store.getRubberEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(10, 10, 10, 30);

        assertEquals(correctRect, rect);

    }



    // case1 : start.x < end.x && start.y < end.y

    public void testCreateCase1() {

        creater.init(new Point(20, 40));

        creater.create(new Point(30, 60));

        Rectangle2D rect = (Rectangle2D)store.getEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(20, 40, 10, 20);

        assertEquals(correctRect, rect);

    }        



    // case2 : start.x < end.x && start.y > end.y

    public void testCreateCase2() {

        creater.init(new Point(20, 40));

        creater.create(new Point(40, 20));

        Rectangle2D rect = (Rectangle2D)store.getEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(20, 20, 20, 20);

        assertEquals(correctRect, rect);

    }



    // case3 : start.x > end.x && start.y < end.y

    public void testCreateCase3() {

        creater.init(new Point(20, 40));

        creater.create(new Point(10, 60));

        Rectangle2D rect = (Rectangle2D)store.getEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(10, 40, 10, 20);

        assertEquals(correctRect, rect);

    }



    // case4 : start.x > end.x && start.y > end.y

    public void testCreateCase4() {

        creater.init(new Point(20, 40));

        creater.create(new Point(10, 10));

        Rectangle2D rect = (Rectangle2D)store.getEntities().get(0);

        Rectangle2D correctRect = new Rectangle2D.Double(10, 10, 10, 30);

        assertEquals(correctRect, rect);

    }

}

-----> RectCreaterTest.java <--------------------



この記事への評価にご協力をお願いします。

良かった 普通 イマイチ