Skip to content.

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

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

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

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

   はじめに

四角と直線を描くドローイングツールが題材です。ボタンは左から、「なし」「四角」「線」 となっています。図を描く方法は以下の通りです。

  1. 描きたい図形のボタンを選択する
  2. 始点を選択する
  3. 入力補助としてラバーバンド(確定するとどんな図形になるかを表現する)を表示する
  4. 図を確定する


これから、新しい描画アイテムとして楕円を追加するとします。機能を拡張するときは、リファクタリングのタイミングとして適しているので、楕円追加に先立ってリファクタリングしてみましょう。



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

import java.awt.Point;



public interface Creater {



    // 入力開始

    public void init(Point point);



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

    public void aid(Point point);



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

    public void create(Point point);

}

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





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

import java.awt.*;

import java.awt.geom.Rectangle2D;



public class RectCreater implements Creater {

    

    private DrawPanel panel;

    private DrawEntityStore store;

    private Point start;

    private Rectangle2D rect;



    public RectCreater(DrawPanel panel, DrawEntityStore store) {

        this.panel = panel;

        this.store = store;

        rect = new Rectangle2D.Double();

    }



    // 入力を開始する

    public void init(Point point) {

        start = point;

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

        store.addRubberEntity(rect);

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

    }



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

    public void aid(Point point) {

        resetRectangle(rect, point);

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

    }



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

    public void create(Point point) {

        Rectangle2D newRect = new Rectangle2D.Double();

        resetRectangle(newRect, point);

        store.addEntity(newRect);

        store.removeRubberEntity(rect);

        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 implements Creater {

    

    private DrawPanel panel;

    private DrawEntityStore store;

    private Point start;

    private Line2D line;



    public LineCreater(DrawPanel panel, DrawEntityStore store) {

        this.panel = panel;

        this.store = store;

        line = new Line2D.Double();

    }



    // 入力開始

    public void init(Point point) {

        start = point;

        line.setLine(start, start);

        store.addRubberEntity(line);

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

    }



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

    public void aid(Point point) {

        line.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(line);

        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 <--------------------



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

良かった 普通 イマイチ