そもそも結合度とは
- 結合度はモジュール間の依存性を表す指標。
- 結合度は低ければ低いほどよい。
- 依存性が少なくなり、修正・拡張がしやすくなるため。
- 各モジュールが独立して、再利用しやすくなるため。
結合度には7つの種類が存在する。
7種類の結合度の詳細
1 : 内部結合(Content coupling)
あるクラスが別のクラスの内部構造や状態に直接依存する結合。
内部結合しているコード
class User
{
public $is_admin = false;
}
class Admin
{
public function promote(User $user)
{
$user->is_admin = true;
}
}
コードの解説
- Userクラスのプロパティ$is_adminをAdminクラスのpromoteメソッドが変更している。
- Userクラスのカプセル化が崩壊している。
- Userクラスのプロパティ$is_adminが変更されると、Adminクラスに影響が生じる。
- また、Adminクラスのpromoteメソッドを変更するとUserクラスに影響が生じるため、双方向が依存し合う密な結合になっている。
結合度を低くするように改善したコード
class User
{
private $is_admin = false;
public function setIsAdmin($isAdmin)
{
$this->is_admin = $isAdmin;
}
}
class Admin
{
public function promote(User $user)
{
// Userクラスの公開メソッドを使用。
$user->setIsAdmin(true);
}
}
改善したコードの解説
- Adminクラスのpromoteメソッドは、UserクラスのsetIsAdminメソッドを通してUserクラスのプライベート変数を変更している。
- カプセル化が守られている。
- Admin→Userへの一方向の依存しか生じていない。
2 : 共通結合(Common coupling)
複数のクラスが共通のグローバル変数に依存する結合。
共通結合しているコード
# app.php
return [
'version' => '1.0.0',
];
# UserController.php
class UserController extends Controller
{
public function profile(Request $request)
{
$version = config('app.version');
return view('user.profile', compact('version'));
}
}
# ClientController.php
class ClientController extends Controller
{
public function profile(Request $request)
{
$version = config('app.version');
return view('client.profile', compact('version'));
}
}
コードの解説
- グローバル変数のapp.versionをUserControllerとClientControllerで共有している。
- app.versionを変更すると、UserControllerとClientControllerの両方のクラスに影響が出る。
- 開発の規模が大きくなると、どのモジュールがどのデータに依存しているか追跡するのが難しくなる。
結合度を低くするように改善したコード
# VersionProvider.php
class VersionController
{
puplic function getVersion()
{
return config('app.version');
}
}
# UserController.php
class UserController
{
public function profile(VersionProvider $version)
{
$version = $version->getVersion();
return view('user.profile', compact('version'));
}
}
# ClientController.php
class ClientController
{
public function profile(VersionProvider $version)
{
$version = $version->getVersion();
return view('client.profile', compact('version'));
}
}
改善したコードの解説
-
Version
クラスにゲッターを定義して、getVersion()
メソッドを定義した。 - それによって、将来バージョン情報の取得方法を変更する必要が生じた場合、
VersionProviderのgetVersion()
メソッドの中身だけを変更すればよくなった。
3 : 外部結合(External coupling)
外部のシステムやライブラリに依存する結合。
外部結合しているコード
class User
{
public function get(){
$users = DB::table('users')->get();
}
}
コードの解説
- Userクラスが、外部システムのDBと直接結合している。
結合度を低くするように改善したコード
class UserRepository {
public function get()
{
return DB::table('users')->get();
}
}
class User
{
__construct (private readonly UserRepository $user_repository)
public function get(){
$users = $user_repository->get();
}
}
改善したコードの解説
- リポジトリパターンを用いてDBへのアクセスを隠蔽した。それによって、DBへ直接アクセスする部分を切り離すことができた。
- 呼び出す側は内部のDB接続処理を意識する必要がなくなった。
- テストの際にもモック化やスタブ化が容易になった。
4 : 制御結合(Control coupling)
あるモジュールが別のモジュールに制御情報を渡して、挙動を決定する結合。
制御結合しているコード
class FirstClass
{
protected $secondClass;
public function __construct(SecondClass $secondClass)
{
$this->secondClass = $secondClass;
}
public function process($flag)
{
return $this->secondClass->process($flag);
}
}
class SecondClass
{
public function process($flag)
{
if ($flag) {
return 'first';
} else {
return 'second';
}
}
}
コードの解説
- FirstClassが制御情報をSecondClassのprosessメソッドに渡し、実行結果を受け取っている。
結合度を低くするように改善したコード1
class FirstClass
{
protected $secondClass;
public function __construct(SecondClass $secondClass)
{
$this->secondClass = $secondClass;
}
public function processFirst()
{
return $this->secondClass->processFirst();
}
public function processSecond()
{
return $this->secondClass->processSecond();
}
}
class SecondClass
{
public function processFirst()
{
return 'first';
}
public function processSecond()
{
return 'second';
}
}
改善したコードの解説
- Secondクラスに'first'を返すメソッド、'second'をそれぞれ定義することで、引数(制御情報)を渡さないようにした。
結合度を低くするように改善したコード2
interface ProcessInterface
{
public function process($flag);
}
class FirstClass
{
protected $process;
public function __construct(ProcessInterface $process)
{
$this->process = $process;
}
public function process($flag)
{
return $this->process->process($flag);
}
}
class SecondClass implements ProcessInterface
{
public function process($flag)
{
if ($flag) {
return 'first';
} else {
return 'second';
}
}
}
改善したコードの解説
- インターフェースを実装して、FirstClassがSecondClassに直接依存することを避け、結合度を下げた。
5 : スタンプ結合(Stamp coupling)
各クラスが配列やオブジェクトの一部を共有する結合。
スタンプ結合しているコード
class FirstClass
{
protected $data = ['first', 'second', 'third'];
public function getData()
{
return $this->data;
}
}
class SecondClass
{
protected $firstClass;
public function __construct(FirstClass $firstClass)
{
$this->firstClass = $firstClass;
}
public function process()
{
$data = $this->firstClass->getData();
return $data[1]; // second
}
}
コードの解説
- SecondClassがFisrtClassのプロパティの配列の一部を使用している。
- SecondClassが必要な情報を取得するにはFirstClassの配列のデータ構造を知っている必要がある。
結合度を低くするように改善したコード
class FirstClass
{
protected $data = ['first', 'second', 'third'];
public function getSecondItem()
{
return $this->data[1];
}
}
class SecondClass
{
protected $firstClass;
public function __construct(FirstClass $firstClass)
{
$this->firstClass = $firstClass;
}
public function process()
{
return $this->firstClass->getSecondItem();
}
}
コードの解説
- SecondClassはFirstClassのデータ構造に依存しなくなり、結合度が低くなった。
- SecondClassはFirstClassの配列のデータ構造を知る必要がなくなった。
6 : データ結合(Data coupling)
関数やクラスが引数を通じてのみデータを受け渡す結合。
データ結合しているコード
class UserController extends Controller
{
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$user->name = $request->input('name');
$user->email = $request->input('email');
$user->save();
return redirect('users/'.$id);
}
}
コードの解説
- updateメソッドは引数で受け取った$request, $idのデータのみを用いている。
- 結合度は低い状態になっている。
7 : メッセージ結合(Message coupling)
引数のないメソッドの呼び出し関係をで結合している状態がメッセージ結合。
メッセージ結合しているコード
class UserController extends Controller
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function index(Request $request)
{
$users = $this->userService->getAllUsers();
return view('users.index', ['users' => $users]);
}
}
コードの解説
- UserControllerは引数を渡すこと無く、Serviceのメソッドを呼び出している。
- UserControllerはServiceのメソッドの実装の詳細を知る必要がない。
- 結合度が最も低い状態になっている。