|
|
(株)永和システムマネジメント
梅田 政利
作成日:初出:ソフトバンク『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 <--------------------
|
この記事への評価にご協力をお願いします。
|
|