目次
前提
- Spring BootのPJの管理をするにあたって改めてOOP、DI、AOPなどを整理した
- DB設計や関数型プログラミングなどのパラダイムにおける設計は省く
- また、DDDでの設計やGoFなどのデザインパターン、UMLでの要件定義などもここでは省く
モジュール論
モジュールの定義
「複合/構造化設計」では以下のようにモジュールが定義されている。
- 閉じたサブルーチンであること
- プログラム内の他のどんなモジュールからも呼び出すことができること
- 独立してコンパイルできる可能性をもっていること
モジュールの独立性を高める方法
モジュールの独立性を高めるには次の2つの観点が重要になる。
- 各モジュール内の関連性を最大にすること => 凝集度を上げる
- モジュール間の関連性を最小にすること => 結合度を下げる
凝集度(Cohesion)
凝集度とは
- 凝集度とは、単一モジュール内の要素間の関連性についての一つの尺度
- プログラムの構造の"良さ"あるいは"悪さ"を評価する尺度
凝集形態
凝集度は、望ましいものから順に、以下のように分類される。
- 機能的凝集度(Functional Cohesion)
- 1つの固有の機能を実行するモジュール
- 情報的凝集度(Informational Cohesion)
- 概念、データ構造、資源などを1つのモジュール内に隔離したモジュール
- 連絡的凝集度(Communicational Cohesion)
- データに関連のある複数の機能を逐次的に実行するモジュール
- 手順的凝集度(Procedural Cohesion)
- 仕様によって定められた、関連の少ない複数の機能を逐次的に実行するモジュール
- 時間的凝集度(Temporal Cohesion)
- 関連の少ない幾つかの機能を逐次的に実行するモジュール
- 論理的凝集度(Logical Cohesion)
- 関連したいくつかの機能を持っていて、呼び出しモジュールによって選択され、実行するモジュール
- 暗合的凝集度(Coincidental Cohesion)
- 何を根拠にモジュールになっているのか説明できないような、論外の状態
機能的凝集度と情報的凝集度は同列になる。
結合度(Coupling)
結合度とは
- 結合度は、主に、モジュール間の関連性についての尺度
- 結合度を小さく保つために次の2つが大切
- モジュール間の不要な関連性をなくすこと
- 必要とされる関連性の強さをできるだけ小さくする
結合形態
結合度は、望ましいものから順に、以下のように分類される。
- 非直接結合(Indirect Coupling)
- モジュールが直接的に互いに依存しないが、他のモジュールを介して間接的に依存しているもの
- 例
- モジュールAとモジュールBがイベントバスを介して通信する
- データ結合(Data Coupling)
- モジュール間のインタフェースデータが単一のデータであるもの
- 例
- モジュールAがモジュールBに単一の数値を渡す
- スタンプ結合(Stamp Coupling)
- 2つのモジュール間でやり取りするデータがグローバルではないもの
- 例
- モジュールAがモジュールBに構造体全体を渡し、その一部だけを使用する
- 制御結合(Control Coupling)
- 1つのモジュールが他のモジュールの論理をはっきりと制御する関連、制御されるモジュール
- 例
- モジュールAがフラグをモジュールBに渡し、そのフラグでモジュールBの処理を制御する
- 外部結合(External Coupling)
- 内容結合でも共通結合でもなく、単一のグローバルデータを参照しているも
- 例
- モジュールAとモジュールBが同じファイルシステムの設定ファイルを参照する
- 共通結合(Common Coupling)
- グローバルな、データ構造を参照するモジュールのグループの間で生じるもの
- 例
- モジュールAとモジュールBがグローバル変数を共有している
- 内容結合(Content Coupling)
- 2つのモジュールで、1つが他の内部を「直接」参照するケース
- 例
- モジュールAがモジュールBの内部データに直接アクセスする
凝集度と結合度の図
図のように、凝集度(Cohesion)が高く、結合度(Coupling)が低いと良い。
OOP, DI, AOPと結合度および凝集度の関係
それぞれの関係は次のようになる。
パラダイム | 結合度 | 凝集度 |
---|---|---|
OOP | - インターフェースやポリモーフィズム | - SOLID原則 |
DI | - 依存関係の外部注入 | - 依存関係の管理を明示的に |
AOP | - クロスカッティング関心事を分離 | - モジュールが特定の責務に集中 |
GoF | インターフェースと抽象クラスを利用 | 責務の分離 |
OOP
OOPの三大要素
いわゆるOOPの三大要素。
ポリフォーリズム
- ポリモーフィズムとは、多態性とも呼ばれ、同じインターフェースを使用して異なる実装を利用できる能力
- interfaceやprotocolをimplementsせずに、ダックタイピングすることもある
|
|
カプセル化
- カプセル化とは、データ(属性)とそれに関連するメソッド(操作)を1つのオブジェクトとしてまとめること
- アクセッサー(ゲッター・セッター)やインデクサーを使い、隠蔽する
|
|
継承
- 継承とは、既存のクラス(親クラスまたはスーパークラス)の属性とメソッドを、新しいクラス(子クラスまたはサブクラス)に引き継ぐこと
- いわゆるis-a関係を表現するために利用する
|
|
継承とコンポジションと委譲
- 明確な「is-a」関係がある場合は継承をする
- それ以外の「has-a」関係がある時はコンポジションをする
- 一般的に、継承より委譲を優先する
コンポジション(has-a関係)の例
|
|
委譲の例
|
|
NTOE:
- コンポジションと委譲の違いは、意味論的なモノにある
- 車はエンジンを持っているが、OfficeはPrinterを持っていない
- 違いは次
- コンポジット: Car has an Engine.
- 委譲: Office uses a Printer.
システム設計の原則
SOLID原則
クラス設計は最低限次の原則に従うべき。
単一責任の原則(Single Responsibility Principle, SRP)
- クラスは、単一の責任を持つべき
- その責任に対する変更理由は一つだけであるべき
オープン/クローズド原則(Open/Closed Principle, OCP)
- ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張に対して開かれているべき
- 変更に対して閉じられているべき
リスコフ置換原則(Liskov Substitution Principle, LSP)
- 基底クラスのインスタンスは、派生クラスのインスタンスに置き換え可能でなければならない
インターフェース分離の原則(Interface Segregation Principle, ISP)
- クライアントは、それが使用しないインターフェースに依存してはならない
- つまり、大きなインターフェースを小さなインターフェースに分割するべき
依存関係逆転の原則(Dependency Inversion Principle, DIP)
- 高レベルのモジュールは低レベルのモジュールに依存してはならず、両者は抽象に依存すべき
- 具体的な実装ではなく、抽象に依存する設計を行う
- importing cycleなどが悪いパターン
分離
関心の分離と責任の分離
クラスを分ける時の分離の基準は以下になる。
- 関心の分離(Separation of Concerns)
- ソフトウェアの各部分が異なる機能やロジックに集中することを指す
- 例
- MVC、レイヤードアーキテクチャ
- 責任の分離(Separation of Responsibilities)
- システムの各部分が特定の機能やロジックに対して責任を持つようにする設計
- 例
- 単一責任の原則(Single Responsibility Principle, SRP)
DIとAOPが必要な理由
クラスに対して次を担保するため。
- クリーンにする事
- 単一責任(SRP: Single Responsibility Principle)にする事
- 再利用性を高める事
- 疎結合にする事
共通の機能はAOPで注入し、依存するインスタンスはDIで注入する。
GRASP
GRASPとは
- Applying UML and Patterns(実践UMLパターン)で紹介された9つのOOPの設計パターン
- GRASPの意味は、General Responsibility Assignment Software Patterns(汎用責任割り当てソフトウェアパターン)
GRASPの9つのパターン
オブジェクト指向設計において適切な責任の割り当てを行い、保守性、再利用性、拡張性の高いシステムを構築するための9つの指針。
- Information Expert(情報エキスパート)
- 情報を持っているオブジェクトに、その情報に基づく責任を割り当てるという原則
- Creator(生成者)
- あるオブジェクトが他のオブジェクトの生成責任を持つべき場合についてのガイドライン
- Controller(コントローラー)
- システム操作を調整し、利用者からの入力を処理するオブジェクトに責任を割り当てるという原則
- Low Coupling(低結合)
- オブジェクト同士の依存関係を最小限に抑えることで、システムの柔軟性と保守性を向上させるという原則
- High Cohesion(高凝集)
- クラスやモジュールが一貫した責任を持ち、それを中心に機能がまとまっていることを目指す原則
- Polymorphism(ポリモーフィズム)
- 異なるオブジェクトが同じインターフェースを実装することで、多態性を実現し、柔軟性を向上させる原則
- Pure Fabrication(純粋な構築)
- ドメインモデルに直接関連しないが、システムの設計をシンプルにするために導入される人工的なクラスやオブジェクト
- Indirection(間接化):
- 関係性や依存性を間接的に管理することで、柔軟性や再利用性を向上させる原則
- Protected Variations(保護された変動)
- 変動する可能性のある部分を保護し、変動の影響を最小限に抑えるための設計手法
DI
DIの基礎
なぜDIが必要か?
- 下の例の通り、
X1 < X2 < X3
の順で疎結合になっている - プログラムの再利用性や変更可能性を上げるために、疎結合に作るのが基本
|
|
DIの利点
- 疎結合:
- オブジェクト間の依存関係を外部から注入することで、クラス同士の結合度が低くなり、変更に強くなる
- テストの容易さ:
- 依存関係を外部から注入するため、モックオブジェクトを用いたユニットテストが容易になる
- 再利用性の向上:
- 同じオブジェクトが異なる文脈で再利用しやすくなる
DISの種類
次の3つの方法がある。
アノテーションによるDI
- 次のアノテーションをクラスに付与することで、Springのコンテナがそれらのクラスを管理する
- @Component、@Service、@Repository、@Controllerなど
- 依存関係を注入するためには、@Autowiredアノテーションをフィールドやコンストラクタに付与する
- 自動でコンポーネントスキャンされる
- コンポーネントスキャンとは、Bean定義用のアノテーションが付与されたクラスをDIコンテナに登録すること
- Bean定義用のアノテーションとは、@Component, @Controller, @Serviceといったアノテーションのこと
|
|
Java ConfigによるDI
- @Configurationアノテーションを付与したクラス(Java Config)で、@Beanアノテーションを使ってBeanを定義する
- これにより、明示的にBeanを定義して管理することができる
- DIコンテナに登録したコンポーネントの事をBean、Configurationの事をBean定義と言う
|
|
XMLによるDI
- 以前はXMLファイルを使用してDIの設定を行う方法が主流だった
- Spring Bootでは主にアノテーションベースの設定が推奨されている
Beanのスコープ
Beanのスコープは、Beanのライフサイクルと可視性を定義する事。
以下のようなスコープがある。
- Singleton(シングルトン)
- グローバルに共有されるオブジェクトに適している
- Prototype(プロトタイプ)
- 各Beanのリクエストごとに新しいインスタンスが生成される
- Request(リクエスト)
- Session(セッション)
- Application(アプリケーション)
- WebSocket(ウェブソケット)
|
|
NOTE:
- シングルトンの中のプロトタイプの注意
- なお、シングルトンBeanにプロトタイプBeanを注入すると、プロトタイプBeanは1回だけインスタンス化される
- そして、それ以降は同じインスタンスが使用されてしまう
- そのため、@Lookupメソッドインジェクションを利用する事で、毎回新しいインスタンスを取得でるようになる
- 当然singletonはスレッドアンセーフなので注意
インジェクション
Spring BootのDIには3つのインジェクションがある。
コンストラクタインジェクション
|
|
セッターインジェクション
|
|
フィールドインジェクション
|
|
オートワイヤリング
@Autowiredアノテーションは型ベースと名前ベースの2種類で推測する。
例えば、同じ型名が複数あったときに分からなくなる。
その場合は、それぞれ次の方法で解決する。
- 型ベース
- @Qualiferアノテーション
- @Primaryアノテーション
- 名前ベース
- @Resourceアノテーション
- NOTE: Resourceはコンストラクタインジェクションでは利用できない
- @Resourceアノテーション
|
|
コンポーネントスキャン
- Spring Bootは、特定のパッケージやそのサブパッケージ内のBeanを自動的に検出して登録する
- コンポーネントスキャン(@ComponentScan)を使用する
|
|
- @Component
- 汎用的なSpring Beanとして定義されるクラスに使用
- 例: 汎用的なサービスやヘルパークラス
- @Service
- サービスレイヤーのBeanを示す
- 例: ビジネスロジックを実装するクラス
- @Repository
- データアクセスオブジェクト(DAO)クラスに使用
- 例: データベース操作を行うクラス
- @Controller
- プレゼンテーションレイヤー、特にWeb MVCのコントローラとして使用
- 例: HTTPリクエストを処理し、レスポンスを返すクラス
- @RestController
- 特にRESTful Webサービスのエンドポイントを定義するコントローラに使用
- 例: REST APIのエンドポイントを提供するクラス
- @Configuration
- JavaベースのSpring設定クラスに使用
- 例: Bean定義や設定を行うクラス
Beanのライフサイクル
Spring BootのBeanのライフサイクルはJava EE環境でのBeanのライフサイクル(JSR 250)と同じ。
|
|
プロファイル管理
@Profileアノテーションの利用してBeanを定義する。
|
|
Bean Validation
- バリデーション(入力チェック)用のフレームワーク
- 以下がBean Validationの例
|
|
|
|
JSR 330との比較
- JSR 330アノテーションは、Javaの依存性注入に関する標準仕様で、Springとの対応は次になる
- Spring BootでもJSR 330の一部のアノテーションは使用可能である
機能 | Spring アノテーション | JSR 330 アノテーション |
---|---|---|
コンポーネントの定義 | @Component | @Named |
依存関係の注入(フィールド/メソッド/コンストラクタ) | @Autowired | @Inject |
依存関係のクオリファイ | @Qualifier | @Named |
ライフサイクルコールバック(初期化) | @PostConstruct (JSR 250) | @PostConstruct (JSR 250) |
ライフサイクルコールバック(破棄) | @PreDestroy (JSR 250) | @PreDestroy (JSR 250) |
スコープの設定 | @Scope | @Singleton |
プロバイダの使用 | @Autowired (もしくは ObjectProvider ) | Provider |
AOP
AOPとは
アスペクト指向プログラミング(AOP: Aspect-Oriented Programming)は横断的関心事を効果的に管理する為のパラダイムの事
横断的関心事
横断的関心事とは
横断的関心事(Cross-cutting concerns)ソフトウェア開発において、アプリケーション全体にわたって共通して発生する機能や要件など。
- ロギング
- トランザクション管理
- エラーハンドリング
- メトリクスとモニタリング
横断的関心事の分離
- 関心の分離があるように、横断的関心事の分離もある
- アプリケーション全体にわたって共通して必要となる機能やロジックを、ビジネスロジックから独立させて管理すること
SpringとAOP
- SpringはAOPの機能がある
- 内部ではAspectJのライブラリを利用している
AOPの主要な用語
AOPの概念図
簡潔に表現すると、次のようになる。
- プログラム実行時、Adviceという処理はさむ
- はさむ箇所(メソッド)はPointcut式定義する
- そのメソッドの一つ一つの事をjoint Pointという
アスペクト(Aspect)
- 横断的関心事の事
- 例えば、ログを管理するなど
- 横断的関心事を定義するモジュール
$$ Aspect = Advice + Pointcut $$
ジョインポイント(Join Point)
- アプリケーション実行中の特定のポイント
- 通常はメソッドの呼び出しや実行がジョインポイントになる
- ポイントカットはjoin pointsの集合の事
アドバイス(Advice)
- 実際に横断的な機能を実行するコード
- アドバイスは、特定のジョインポイント(Join Point)に適用される
アドバイスの種類
Srpingには以下の種類がある。
- Before Advice:
- 対象メソッドが実行される前に実行される
- After Advice:
- 対象メソッドが実行された後に実行される
- Around Advice:
- 対象メソッドの前後で実行され、メソッドの実行を制御できる
- After Returning Advice:
- 対象メソッドが正常に終了した後に実行される
- After Throwing Advice:
- 対象メソッドが例外をスローした後に実行される
ポイントカット(Pointcut)
ポイントカットはJoint Pointsの集合の事。
$$ Pointcuts : Joint Points = 1 : N $$
ポイントカット式
下のような形式に従う。
ポイントカット式の例
Method Signature Patterns
|
|
Type Signature Patterns
|
|
Bean Name Patterns
|
|
Combining Pointcut Expressions
|
|
ウィービング(Weaving)
- アスペクトがアプリケーションコードに統合するプロセスの事
- コンパイル、クラスロード、実行時などのタイミングがある
ターゲット(Target)
- アスペクトが適用されるオブジェクトまたはメソッドを指す
まとめ
- モジュールを分割してdivide and conquerする
- クラスの設計にはSOLIDの原則に従い設計する
- クラスを綺麗に保つにはDIやAOPが効果的
参考文献
- 【Spring Boot + MyBatis】はじめてREST APIを作成してみる #初心者 - Qiita
- MyBatisとは?使い方やSpringとの連携方法をサンプル付きで解説! | プログラミングを学ぶならトレノキャンプ(TRAINOCAMP)
- 設計におけるオブジェクトの責務分配に有効なものさし -凝集度と結合度- | オブジェクトの広場
- ソフトウェアの複合/構造化設計
- GRASPパターン
- GoFデザインパターン
- P of EAA
- Applying UML and Patterns(実践UMLパターン)
- 【オブジェクト指向設計】GRASPの9つのパターンを分かりやすく解説 | ほわらぼIT大学
- Declaring Pointcut Expressions with Examples - Dinesh on Java
- Object Oriented Analysis and Design | TRUNGTQ.COM