740 likes | 964 Views
DI と AOP を理解しよう. 【 発表者のプロフィール 】 ・ Toki ・ 個人事業でプログラムの講師をしています ・ 現場指向 genba-oriented.jp. どうして?. ● Spring は、 DI 、 AOP の概念を適用する際に有用な機能を 提供してくれる → DI 、 AOP を理解すれば、自ずと Spring が使いたくなる ( はず ) ● DI 、 AOP を理解せずに Spring を使うのは危険 → 多機能さに惑わされ、適切ではない使い方をしてしまう. まずは、「 DI 、 AOP 」を理解しましょう!.
E N D
DIとAOPを理解しよう 【発表者のプロフィール】 ・ Toki ・ 個人事業でプログラムの講師をしています ・ 現場指向 genba-oriented.jp
どうして? ●Springは、DI、AOPの概念を適用する際に有用な機能を 提供してくれる → DI、AOPを理解すれば、自ずとSpringが使いたくなる(はず) ●DI、AOPを理解せずにSpringを使うのは危険 → 多機能さに惑わされ、適切ではない使い方をしてしまう まずは、「DI、AOP」を理解しましょう!
DI Dependency Injection 依存性の注入
何者なの? DIは、設計思想※です ※ 設計思想:プログラムを設計する際の基本方針
誤:DIは、OOP※に取って代わるもの 正:OOPをベースにした設計思想です ※Object Oriented Progamming:オブジェクト指向プログラミング
誤:DI=Spring 正:Springは、DIを適用してプログラム する際の有用なフレームワークです
誤:DI=DIコンテナ 正:DIコンテナは、 DIを適用したプログラムを動かすときに 一般的に使われる仕組みです
誤:DI =コードからnew をなくすこと 正:DIコンテナを利用すると、 内部でnewされるので、 結果的に少なくなるだけです。 DIコンテナを使ったとしても、 コード中でnewした方がよいクラスも存在します (DTO※、Value Object※など) ※:ロジックを持たず、データだけを保持するオブジェクト DTOはData Transfer Objectの略語
誤:DI = 外部ファイルを使う 正:DIを適用したプログラムの コンフィグレーションの手段の1つとして、 外部ファイルが用いられます
じゃあ・・・ 「DIの設計思想」ってどういうものなの?
説明のための前提知識 プログラムには、起動後、 アプリケーションとして「機能する」フェーズと、 その前の「準備する」フェーズが存在します。
起動後のオブジェクトのイメージ 「準備する」 フェーズ 「機能する」 フェーズ xxxxxxxx xxxxxxxx [] [] [] 起動 xxxxxxxx xxxxxxxx [] [] [] [] xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx newして、設定値を入れて、 オブジェクトを参照させて・・・
Webアプリケーションでのイメージ 「準備する」 「機能する」 ・ サーブレットがリクエストを 受け付ける ・ ビジネスオブジェクトが データを検索する ・ DAOがDBから データを取得する ・ サーブレットがHTMLを返す ・ Tomcatが サーブレットをロードする ・ ビジネスオブジェクト※1、 DAO※2が作成される ・ サーブレット → ビジネスオブジェクト → DAOの参照が形成される 起動 ※1:業務的な処理を行うオブジェクト ※2:データをDBから取得、更新するオブジェクト
具体例で見ていきましょう 「データ変換プログラム」 変換 B社 ファイル A社 ファイル
実際のファイル形式 ファイル形式 文字コード:EUC 改行コード:LF CSV(カンマ区切り)形式 ファイル形式 文字コード:Shift JIS 改行コード:CR+LF TSV(タブ区切り)形式 変換 B社用 A社用 ・「会社名」+「_」+「部署名」 ←→ 「お客様名」 ・「値段」 円 ←→ ドル
ファイルのイメージ A社 1000,株式会社東京産業,東京支部,p000111,5000 1001,株式会社東京産業,横浜支部,p000112,3000 ・・・ ※1ドル=130円で換算 B社 1000 株式会社東京産業_東京支部 p000111 38 1001 株式会社東京産業_横浜支部 p000112 23 ・・・ 一行が一注文データ
処理の流れ(A社からB社への変換) ・ A社のファイルから一行を読み込む ・ A社の注文データを、B社の注文データに変換する ・ B社のファイルに一行を書き出す ・ 上記をファイルの行数分繰り返す
上司:「A社ファイルから読み込んだ一行から、上司:「A社ファイルから読み込んだ一行から、 A社注文クラスのオブジェクトを 作成するクラスを担当してください。」 Xさん:「わかりました。」
"1000,株式会社東京産業,東京支部,p000111,5000" から、 ※ ゲッター、セッターメソッド クラスのオブジェクトを作成
A社注文クラスのオブジェクトを返す ファイルから一行の文字列を読み込んで返す Xさんの担当 《create》 Yさんの担当
注文読込()メソッドを実装 public class A社注文リーダー { String 区切文字; ファイルリーダー ファイルリーダー; public A社注文 注文読込() { String 一行 = ファイルリーダー.一行読込(); String[] 文字配列 = 一行.split(区切り文字); A社注文 A社注文 = new A社注文(); A社注文.set注文ID(文字配列[0]); A社注文.set会社名(文字配列[1]); A社注文.set部署名(文字配列[2]); A社注文.set製品ID(文字配列[3]); A社注文.set値段(文字配列[4]); return A社注文; } }
動作確認 public class 動作確認 { public static void main(String[] args) { A社注文リーダー A社注文リーダー = new A社注文リーダー(); A社注文 A社注文 = A社注文リーダー.注文読込(); System.out.println(A社注文.get注文ID()); System.out.println(A社注文.get会社名()); System.out.println(A社注文.get部署名()); System.out.println(A社注文.get製品ID()); System.out.println(A社注文.get値段()); } } NullPointerException
これらのフィールドをどうやって設定しようか?これらのフィールドをどうやって設定しようか? public class A社注文リーダー { String 区切文字; ファイルリーダー ファイルリーダー; public A社注文 注文読込() { String 一行 = ファイルリーダー.一行読込(); ・・・
起動後のオブジェクトのイメージ 「準備する」 フェーズ 「機能する」 フェーズ xxxxxxxx xxxxxxxx [] [] [] 起動 xxxxxxxx xxxxxxxx [] [] [] [] xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx newして、設定値を入れて、 オブジェクトを参照させて・・・
「準備する」 部分 public class A社注文リーダー { String 区切文字; ファイルリーダー ファイルリーダー; public A社注文 注文読込() { String 一行 = ファイルリーダー.一行読込(); String[] 文字配列 = 一行.split(区切り文字); A社注文 A社注文 = new A社注文(); A社注文.set注文ID(文字配列[0]); A社注文.set会社名(文字配列[1]); A社注文.set部署名(文字配列[2]); A社注文.set製品ID(文字配列[3]); A社注文.set値段(文字配列[4]); return A社注文; } } ? 「機能する」 部分
DIは、「準備する」部分を効率よく行うための設計思想DIは、「準備する」部分を効率よく行うための設計思想 目的は 「準備する」部分と「機能する」部分の分離
分離することで ・ 開発効率があがる ・ 再利用性があがる ・ スパゲティーコード化が抑止される
「準備する」部分:その1ハードコーディングする「準備する」部分:その1ハードコーディングする public class A社注文リーダー { String 区切文字= ","; ファイルリーダー ファイルリーダー= new LF改行ファイルリーダー(); public A社注文 注文読込() { String 一行 = ファイルリーダー.一行読込(); ・・・
上司:「A社のファイルは、たまにタブ区切だったり、上司:「A社のファイルは、たまにタブ区切だったり、 CRLF改行のときもあるし・・・ 汎用的に作れないかな?」
「準備する」部分:その2定数クラスを使う public class 定数クラス { public static final String A社_区切文字 = ","; public static final String A社_改行コード = "LF"; ・・・
public class A社注文リーダー { String 区切文字= 定数クラス.A社_区切文字; ファイルリーダー ファイルリーダー; 定数じゃないフィールドは どうすればいいのだろう?
コンストラクタでnew public class A社注文リーダー { String 区切文字= 定数クラス.A社_区切文字; ファイルリーダー ファイルリーダー; public A社注文リーダー() { if (定数クラス.A社_改行コード.equals("LF")) { ファイルリーダー = new LF改行ファイルリーダー(); } else if (定数クラス.A社_改行コード.equals("CRLF")) { ファイルリーダー = new CRLF改行ファイルリーダー(); } } public A社注文 注文読込() { String 一行 = ファイルリーダー.一行読込(); ・・・ もしくは・・・
「機能する」部分のメソッドの中でnew public class A社注文リーダー { String 区切文字= 定数クラス.A社_区切文字; ファイルリーダー ファイルリーダー; public 注文読込() { if (定数クラス.A社_改行コード.equals("LF")) { ファイルリーダー = new LF改行ファイルリーダー(); } else if (定数クラス.A社_改行コード.equals("CRLF")) { ファイルリーダー = new CRLF改行ファイルリーダー(); } String 一行 = ファイルリーダー.一行読込(); ・・・ ・メソッドが呼ばれる度(=ファイルの行数分)同じnewが呼ばれて無駄 ・「機能する」部分と「準備する」部分が混在すると、 メソッドの責務が曖昧になり、スパゲティコード化につながる
上司:「新しい改行コード用のクラスを追加するとき、上司:「新しい改行コード用のクラスを追加するとき、 このクラスも修正しないといけないのはよくないなあ。 そもそも、 定数クラスで改行コードを"LF"に設定する時点で、 LF改行ファイルリーダークラスを使うことが 分かってるはずだから、 わざわざこのクラスでnewすることは ないんじゃないの?」
「準備する」部分:その3ファクトリ※パターンを使う「準備する」部分:その3ファクトリ※パターンを使う ※ファクトリ:オブジェクトの工場という意味合い public class 定数兼ファクトリクラス { public static String A社_区切文字 = ","; static ファイルリーダー ファイルリーダー = new LF改行ファイルリーダー(); public static ファイルリーダー getファイルリーダー() { return ファイルリーダー; } ・・・ 改行コードを設定するかわりに、 具象クラスを設定するイメージ
public class A社注文リーダー { String 区切文字= 定数クラス.A社_区切文字; ファイルリーダー ファイルリーダー = 定数兼ファクトリクラス.getファイルリーダー(); public 注文読込() { String 一行 = ファイルリーダー.一行読込(); ・・・ 新しい改行コードのクラスが追加されても、 このクラスを修正する必要はない
動作確認 public class 動作確認 { public static void main(String[] args) { A社注文リーダー A社注文リーダー = new A社注文リーダー(); A社注文 A社注文 = A社注文リーダー.注文読込(); System.out.println(A社注文.get注文ID()); System.out.println(A社注文.get会社名()); System.out.println(A社注文.get部署名()); System.out.println(A社注文.get製品ID()); System.out.println(A社注文.get値段()); } } すると、 FileNotFoundException (ファイルが見つかりません)
Xさん:「そうか、LF改行ファイルリーダークラスがXさん:「そうか、LF改行ファイルリーダークラスが ファイルを読み込んでるのか。 しかし、どのファイルを読み込んでるのだろう? ファイルを用意しないといけないの? 僕のクラスとは関係ないのに・・・」 テストするのが億劫になる
上司:「他のプロジェクトで、A社注文リーダークラスを上司:「他のプロジェクトで、A社注文リーダークラスを 使いたいらいしいんだけど・・・」 他のプロジェクト 「集計プログラム」 Xさんのプロジェクト 「データ変換プログラム」 設定用のクラスを別にすることができないので、 使い回しが困難 再利用性が悪い
上司:「定数兼ファクトリクラスで、他のクラスの設定値も上司:「定数兼ファクトリクラスで、他のクラスの設定値も 一元して管理したいね。 Xさん、メンテナンスをお願いね。」 Xさん:「(そんなつもりで作ったんじゃないのに・・・)」
「準備する」部分:その4DIを適用する public class A社注文リーダー { String 区切文字; public void set区切文字(String 区切文字) { this.区切文字 = 区切文字; } ファイルリーダー ファイルリーダー; public void setファイルリーダー(ファイルリーダー ファイルリーダー) { this.ファイルリーダー =ファイルリーダー; } public 注文読込() { String 一行 = ファイルリーダー.一行読込(); ・・・ 「準備してもらう」ための仕組みを用意した
Yさん:「ごめん、LF改行ファイルリーダークラスがYさん:「ごめん、LF改行ファイルリーダークラスが まだできてないんだよ」
モッククラス※で動作確認 class モックファイルリーダー implements ファイルリーダー { public String 一行読込() { return "test001,東京企画,営業部,p001,3000"; } } public class 動作確認 { public static void main(String[] args) { モックファイルリーダー ファイルリーダー = new モックファイルリーダー(); A社注文リーダー A社注文リーダー = new A社注文リーダー(); A社注文リーダー.set区切文字(”、”); A社注文リーダー.setファイルリーダー(ファイルリーダー); A社注文 A社注文 = A社注文リーダー.注文読込(); System.out.println(A社注文.get注文ID()); System.out.println(A社注文.get会社名()); System.out.println(A社注文.get部署名()); System.out.println(A社注文.get製品ID()); System.out.println(A社注文.get値段()); } } ※ 見かけ上正しく動くクラス
"DIする"という表現 ・・・ A社注文リーダー A社注文リーダー = new A社注文リーダー(); A社注文リーダー.set区切文字(”、”); A社注文リーダー.setファイルリーダー(ファイルリーダー); ・・・
Bさん:「LF改行ファイルリーダークラスできたから、Bさん:「LF改行ファイルリーダークラスできたから、 使ってみてよ。 ファイルパスと文字コードは、DIできるよ。」
public class 動作確認 { public static void main(String[] args) { LF改行ファイルリーダー ファイルリーダー = new LF改行ファイルリーダー(); ファイルリーダー.setファイルパス("test.csv"); ファイルリーダー.set文字コード("EUC_JP"); A社注文リーダー A社注文リーダー = new A社注文リーダー(); A社注文リーダー.set区切文字(”、”); A社注文リーダー.setファイルリーダー(ファイルリーダー ); A社注文 A社注文 = A社注文リーダー.注文読込(); System.out.println(A社注文.get注文ID()); System.out.println(A社注文.get会社名()); System.out.println(A社注文.get部署名()); System.out.println(A社注文.get製品ID()); System.out.println(A社注文.get値段()); } }
上司:「そろそろ結合テストだから、 ベテランのCさん、「準備する」部分の実装をお願い」 適した時期に、適した人が「準備する」部分 を作り込むことができる
プログラム全体のクラス図 《create》 Xさんの担当したクラス A社注文 B社注文 《インターフェース》 注文リーダー 《インターフェース》 コンバーター 《インターフェース》 注文ライター A社注文リーダー B社注文リーダー A社注文ライター B社注文ライター A_Bコンバーター B_Aコンバーター 《インターフェース》 ファイルリーダー ファイルライター LF改行 ファイルリーダー CRLF改行 ファイルリーダー
プログラム全体の「準備する」部分を集約 public class コンフフィギュレーション { public static エグゼキューター コンフィギュア() { LF改行ファイルリーダー ファイルリーダー = new LF改行ファイルリーダー(); ファイルリーダー.setファイルパス("data/A社注文.csv"); ファイルリーダー.set文字コード("EUC_JP"); A社注文リーダー 注文リーダー = new A社注文リーダー(); 注文リーダー.set区切文字(","); 注文リーダー.setファイルリーダー(ファイルリーダー); A_Bコンバーター コンバーター = new A_Bコンバーター(); ファイルライター ファイルライター = new ファイルライター(); ファイルライター.set改行文字("\r\n"); ファイルライター.set文字コード("Shift_JIS"); ファイルライター.setファイルパス("data/B社注文.txt"); B社注文ライター 注文ライター = new B社注文ライター(); 注文ライター.set区切文字("\t"); 注文ライター.setラインライター(ラインライター); エグゼキューター エグゼキューター = new エグゼキューター(); エグゼキューター.set注文リーダー(注文リーダー); エグゼキューター.setコンバーター(コンバーター); エグゼキューター.set注文ライター(注文ライター); return エグゼキューター; } }
プログラムの実行 public static void main(String[] args) { エグゼキューター エグゼキューター = コンフィギュレーション.コンフィギュア(); エグゼキューター.実行(); }