複合リファクタリング
『Commandによる条件つきディスパッチャの置き換え』
第7章の実演ムービー全11本です。複合リファクタリング
複合リファクタリングとは,
第7章の実演ムービー全11本-簡易目次
- 手順1:条件分岐の中に
『メソッドの抽出』 を適用する - その1:条件分岐の中にロジックを集める
- その2:
「実行メソッド」 を抽出する - 手順2:各実行メソッドに
『クラスの抽出』 を適用する - その1:メソッドの移動
(リストアクセス編) - その1:メソッドの移動
(IDアクセス編) - その2:メソッドからコンストラクタへの引数の移行
(リストアクセス編) - その2:メソッドからコンストラクタへの引数の移行
(IDアクセス編) - その3:メソッドwriteXmlTo(response, xml)の具象コマンドクラスへの移動
- 手順3:具象コマンドに
『スーパークラスの抽出』 と 『インタフェースの抽出』 を適用することで 「コマンド」 を作成する 「コマンド」 の作成 [前編] 「コマンド」 の作成 [後編] - 手順4:クライアントコードをコマンドを使うように修正する
- クライアントコードの修正
- 双方向関連の解消
- 双方向関連の解消
→
手順1:条件分岐の中に『メソッドの抽出』を適用する
- その1:条件分岐の中にロジックを集める
[映像時間:3:56] - 『Commandによる条件つきディスパッチャの置き換え』
を行うために, まず別クラスに抽出したいコードをメソッドに抽出します。その前に, メソッド抽出の前準備として, 抽出したいロジックを条件分岐の中に集めます。 『一時変数のインライン化』 やコピー&ペーストを使用してif文の中に移動させています。 -
リスト1 RestOperationDelegateImpl.
java (6章リスト3を変更) public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess(request. getPathInfo())) { String daoPrefix = pathInfoTokenizer. getDaoPrefix(request. } else { String daoPrefix = pathInfoTokenizer.getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = daoOperation. getAll(daoPrefix); String xml = entityListMarshaller. marshal( resourceList, daoPrefix, listUri); writeXmlTo(response, xml); getDaoPrefix(request. } }getPathInfo()); long entityId = pathInfoTokenizer. getEntityId(request. getPathInfo()); Object result = daoOperation. getById(daoPrefix, entityId); String xml = entityMarshaller. marshal(result); writeXmlTo(response, xml); - その2:
「実行メソッド」 を抽出する [映像時間:1:52] - 手順1その1で条件分岐の中に集めたコードを,
「メソッドの抽出」 ([Alt]+[Shift]+[M]) でメソッドに抽出します。 『Commandによる条件つきディスパッチャの置き換え』 では, このような形で抽出したメソッドを 「実行メソッド」 と呼んでいます。 -
リスト2 RestOperationDelegateImpl.
java (リスト1を変更) public class RestOperationDelegateImpl implements RestOperationDelegate { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess(request. getPathInfo())) { handleListAccess(request, response); } else { handleIdAccess(request, response); } } private void handleListAccess(HttpServletRequest request, HttpServletResponse response) throws IOException { String daoPrefix = pathInfoTokenizer. getDaoPrefix(request. … }getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = daoOperation. getAll(daoPrefix); String xml = entityListMarshaller. marshal( resourceList, daoPrefix, listUri); writeXmlTo(response, xml); } private void handleIdAccess(HttpServletRequest request, HttpServletResponse response) throws IOException { String daoPrefix = pathInfoTokenizer. getDaoPrefix(request. getPathInfo()); long entityId = pathInfoTokenizer. getEntityId(request. getPathInfo()); Object result = daoOperation. getById(daoPrefix, entityId); String xml = entityMarshaller. marshal(result); writeXmlTo(response, xml); }
手順2:各実行メソッドに『クラスの抽出』を適用する
- その1:メソッドの移動
(リストアクセス編) [映像時間:5:01] - 手順1その2で抽出したメソッドを,
Eclipseのリファクタリング機能 「メソッドの移動」 ([Alt]+[Shift]+[V]) を使って新規作成したクラスに移動します。 「メソッドの移動」 を効率的に行うために, Eclipseの機能に合わせたトリッキーなコーディングを行っています。 -
リスト3 RestOperationDelegateImpl.
java (リスト2を変更) public class RestOperationDelegateImpl implements RestOperationDelegate {
ListAccessHandler handler = new ListAccessHandler();public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.isListAccess(request. getPathInfo())) { ListAccessHandler handler = new ListAccessHandler(); handler.handleListAccess(this, request, response); } else { handleIdAccess(request, response); } } private void handleListAccess( HttpServletRequest request, HttpServletResponse response) throws IOException { String daoPrefix = pathInfoTokenizer.private void handleIdAccess(HttpServletRequest request, HttpServletResponse response) throws IOException { String daoPrefix = pathInfoTokenizer.getDaoPrefix(request. getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = daoOperation. getAll(daoPrefix); String xml = entityListMarshaller. marshal( resourceList, daoPrefix, listUri); writeXmlTo(response, xml); } getDaoPrefix(request. getPathInfo()); long entityId = pathInfoTokenizer. getEntityId(request. getPathInfo()); Object result = daoOperation. getById(daoPrefix, entityId); String xml = entityMarshaller. marshal(result); writeXmlTo(response, xml); } privatevoid writeXmlTo( HttpServletResponse response, String xml) throws IOException { … }privateEntityListMarshaller entityListMarshaller; …privateEntityMarshaller entityMarshaller; …privatePathInfoTokenizer pathInfoTokenizer; …privateDaoOperation daoOperation; … }リスト4 ListAccessHandler.
java public class ListAccessHandler { void handleListAccess(RestOperationDelegateImpl facade, HttpServletRequest request, HttpServletResponse response) throws IOException { String daoPrefix = facade.
pathInfoTokenizer. getDaoPrefix(request. getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = facade. daoOperation. getAll(daoPrefix); String xml = facade. entityListMarshaller. marshal( resourceList, daoPrefix, listUri); facade. writeXmlTo(response, xml); } } - その1:メソッドの移動
(IDアクセス編) [映像時間: (5:14)] - 同様の作業をもう1つのメソッド
(IDに対するアクセスを扱うメソッド) に対して行います。紙幅の関係で誌面には登場しません。 - その2:メソッドからコンストラクタへの引数の移行
(リストアクセス編) [映像時間: (5:14)] - Eclipseのクイックフィクス機能
([Ctrl]+[1]) を使用して新しいコンストラクタを効率的に作成し, さらにEclipseのリファクタリング機能 「メソッドシグニチャの変更」 ([Alt]+[Shift]+[C]) を使用して既存のメソッドのシグニチャを変更しています。 リスト5 RestOperationDelegateImpl.
java (リスト3を変更) public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess(request. getPathInfo())) { ListAccessHandler handler = new ListAccessHandler(this, request, response); handler.handle(); } else { IdAccessHandler handler = new IdAccessHandler(); handler. handleIdAccess(this, request, response); } } リスト6 ListAccessHandler.
java (リスト4を変更) public class ListAccessHandler { private final RestOperationDelegateImpl facade; private final HttpServletRequest request; private final HttpServletResponse response; public ListAccessHandler(RestOperationDelegateImpl facade, HttpServletRequest request, HttpServletResponse response) { this.
facade = facade; this. void handle() throws IOException { … } }request = request; this. response = response; } - その2:メソッドからコンストラクタへの引数の移行
(IDアクセス編) [映像時間:2:15] - 同様の作業をもう1つのクラス
(IdAccessHandler) に対して行います。紙幅の関係で本誌には登場しません。 - その3:メソッドwriteXmlTo(response, xml)の具象コマンドクラスへの移動
[映像時間:3:53] - Eclipseのリファクタリング機能
「インライン化」 ([Alt]+[Shift]+[I]) を活用して, メソッドの移動相当の作業を行うテクニックを紹介します。 リスト7 RestOperationDelegateImpl.
java (リスト5を変更) public class RestOperationDelegateImpl implements RestOperationDelegate { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { … }
void writeXmlTo( HttpServletResponse response, String xml) throws IOException { response.… }setContentType("text/ xml"); response. setCharacterEncoding("UTF-8"); PrintWriter writer = response. getWriter(); IOUtils. copy(new StringReader(xml), writer); writer. flush(); } リスト8 ListAccessHandler.
java (リスト6を変更) public class ListAccessHandler { … void handle() throws IOException { String daoPrefix = facade.
pathInfoTokenizer. getDaoPrefix(request. getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = facade. daoOperation. getAll(daoPrefix); String xml = facade. entityListMarshaller. marshal( resourceList, daoPrefix, listUri); writeXml(xml); } void writeXml(String xml) throws IOException { response. setContentType("text/ }xml"); response. setCharacterEncoding("UTF-8"); PrintWriter writer = response. getWriter(); IOUtils. copy(new StringReader(xml), writer); writer. flush(); } 手順3:具象コマンドに
『スーパークラスの抽出』 と 『インタフェースの抽出』 を適用することで 「コマンド」 を作成する 「コマンド」 の作成 [前編] [映像時間:7:43] 「コマンド」 の作成 [後編] [映像時間:7:30] - 前編では,
2つのコマンドクラスの間に生じた 『重複したコード』 を, 『スーパークラスの抽出』 と 『メソッドの引き上げ』 を行うことで解消します。
まずEclipseのリファクタリング機能「スーパークラスの抽出」 ([Alt]+ [Shift] + [T] → [T])を使用して 『スーパークラスの抽出』 を行います。次にEclipseのリファクタリング機能 「プルアップ」 ([Alt] + [Shift] + [T] → [U])を使用して 『フィールドの引き上げ』 を行い, 最後にもう一度 「プルアップ」 を使用して今度は 『メソッドの引き上げ』 を行います。
後編では,Eclipseのリファクタリング機能を使って抽象メソッドやインタフェースなどの継承構造を扱う方法を解説します。
まず前編で抽出したスーパークラスに対して「プルアップ」 を使用して抽象メソッドを引き上げます。次にそのスーパークラスに対して 『インタフェースの抽出』 ([Alt] + [Shift] + [T] → [E])を行います。最後に抽出したインタフェースにメソッド定義を 「プルアップ」 することで, メソッド宣言をインタフェースまで移動します。 -
リスト9 AbstractGetMethodAccessHandler.
java public abstract class AbstractGetMethodAccessHandler implements GetMethodAccessHandler { protected RestOperationDelegateImpl facade; protected HttpServletRequest request; protected HttpServletResponse response; public AbstractGetMethodAccessHandler( RestOperationDelegateImpl facade, HttpServletRequest request, HttpServletResponse response) { this.
facade = facade; this. request = request; this. response = response; } protected void writeXml(String xml) throws IOException { response. setContentType("text/ xml"); response. setCharacterEncoding("UTF-8"); PrintWriter writer = response. getWriter(); IOUtils. copy(new StringReader(xml), writer); writer. flush(); } public abstract void handle() throws IOException; } リスト10 ListAccessHandler.
java (リスト8を変更) public class ListAccessHandler extends AbstractGetMethodAccessHandler { public ListAccessHandler(RestOperationDelegateImpl facade, HttpServletRequest request, HttpServletResponse response) { super(facade, request, response); } public void handle() throws IOException { … } }
リスト11 GetMethodAccessHandler.
java public interface GetMethodAccessHandler { void handle() throws IOException; }
手順4:クライアントコードをコマンドを使うように修正する
- クライアントコードの修正
[映像時間:1:14] - 手順3で作成したインタフェースGetMethodAccessHandlerを使うように,
クライアントコード (ここではRestOperationDelegateImpl) を修正しています。 -
リスト12 RestOperationDelegateImpl.
java (リスト7を変更) public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess(request. getPathInfo())) { GetMethodAccessHandler handler = new ListAccessHandler(this, request, response); handler. handle(); } else { GetMethodAccessHandler handler = new IdAccessHandler(this, request, response); handler. handle(); } }
双方向関連の解消
- 双方向関連の解消
[映像時間:10:06] - RestOperationDelegateImplと2つのコマンドクラスとの間の双方向関連を解消し,
DIコンテナに対応するための布石を打ちます。 -
リスト13 RestOperationDelegateImpl.
java (リスト12を変更) public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess(request. getPathInfo())) { GetMethodAccessHandler handler = new ListAccessHandler( this, request, response, pathInfoTokenizer, daoOperation, entityListMarshaller); handler. handle(); } else { GetMethodAccessHandler handler = new IdAccessHandler( this, request, response, pathInfoTokenizer, daoOperation, entityMarshaller); handler. handle(); } } リスト14 ListAccessHandler.
java (リスト10を変更) public class ListAccessHandler extends AbstractGetMethodAccessHandler { private final EntityListMarshaller entityListMarshaller; private final DaoOperation daoOperation; private final PathInfoTokenizer pathInfoTokenizer; public ListAccessHandler( RestOperationDelegateImpl facade, HttpServletRequest request, HttpServletResponse response, PathInfoTokenizer pathInfoTokenizer, DaoOperation daoOperation, EntityListMarshaller entityListMarshaller) { super(facade, request, response); this.
pathInfoTokenizer = pathInfoTokenizer; this. } public void handle() throws IOException { String daoPrefix =daoOperation = daoOperation; this. entityListMarshaller = entityListMarshaller; facade.pathInfoTokenizer.getDaoPrefix( request. getPathInfo()); String listUri = request. getRequestURL().toString(); List resourceList = facade.daoOperation.getAll(daoPrefix); String xml = facade.entityListMarshaller.marshal( resourceList, daoPrefix, listUri); writeXml(xml); } } リスト15 RestOperationDelegateImpl.
java (リスト13を変更) public class RestOperationDelegateImpl implements RestOperationDelegate { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { if(pathInfoTokenizer.
isListAccess( request. getPathInfo())) { GetMethodAccessHandler handler = new ListAccessHandler( this,request, response, pathInfoTokenizer, daoOperation, entityListMarshaller); handler.handle(); } else { GetMethodAccessHandler handler = new IdAccessHandler( this,request, response, pathInfoTokenizer, daoOperation, entityMarshaller); handler.handle(); } } privateEntityListMarshaller entityListMarshaller; …privateEntityMarshaller entityMarshaller; …privatePathInfoTokenizer pathInfoTokenizer; …privateDaoOperation daoOperation; … }リスト16 AbstractGetMethodAccessHandler.
java (リスト9を変更) public abstract class AbstractGetMethodAccessHandler implements GetMethodAccessHandler {
protected RestOperationDelegateImpl facade;protected HttpServletRequest request; protected HttpServletResponse response; public AbstractGetMethodAccessHandler(RestOperationDelegateImpl facade,HttpServletRequest request, HttpServletResponse response) {this.this.facade = facade; request = request; this. response = response; } … } リスト17 ListAccessHandler.
java (リスト14を変更) public ListAccessHandler(
RestOperationDelegateImpl facade,HttpServletRequest request, HttpServletResponse response, PathInfoTokenizer pathInfoTokenizer, DaoOperation daoOperation, EntityListMarshaller entityListMarshaller) { super(facade,request, response); this.pathInfoTokenizer = pathInfoTokenizer; this. daoOperation = daoOperation; this. entityListMarshaller = entityListMarshaller; }
- その1:メソッドの移動