# 密結合と疎結合を分かりやすく解説

> 「密結合」と「疎結合」の概念を解説し、それぞれのメリット・デメリット、具体例を挙げて、理想的なコード設計におけるバランスの重要性について解説しています。

- 公開日: 2024-11-27
- 更新日: 2024-12-05
- 著者: Matsu
- タグ: エンジニア概念マップ
- URL: https://tech.anycloud.co.jp/articles/loose-coupling-tightly-coupled

---

## 密結合とは？

密結合とは、クラスやウィジェット、コンポーネントが互いに強く依存している状態のことです。

## 密結合なコードの具体例・改善例

以下のコードは`UserProfileScreen`が`UserSettingsScreen`に直接依存しているため、密結合しています。

`UserProfileScreen`を別の場所で使おうとすると、他の関連部分も一緒に修正する必要が出てきます。

```dart
class UserProfileScreen extends StatelessWidget {
  final User user;
  UserProfileScreen(this.user);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Name: ${user.name}'),
        Text('Email: ${user.email}'),
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(MaterialPageRoute(
              builder: (context) => UserSettingsScreen(user),
            ));
          },
          child: Text('Settings'),
        ),
      ],
    );
  }
}
```

改善例として、以下のコードのように、コールバック関数を渡すことで、画面遷移の処理を`UserProfileScreen`から分離します。

このようにすると、遷移先や動作を後から変更しやすくなり、再利用性も向上します。

```dart
class UserProfileScreen extends StatelessWidget {
  final User user;
  final VoidCallback onPressed;
  final String buttonText;

  UserProfileScreen(this.user, this.onPressed, this buttonText);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Name: ${user.name}'),
        Text('Email: ${user.email}'),
        ElevatedButton(
          onPressed: onPressed,
          child: Text(buttonText),
        ),
      ],
    );
  }
}
```

## 密結合なコードのデメリット3つ

密結合なコードのデメリット3つを解説します。

### **1.変更が難しい**

ある部分を変更すると、依存している他の部分も一緒に修正する必要があり、時間と工数がかかります。

### **2.再利用性が低い**

他のプロジェクトや他の部分で再利用したくても、密結合しているため簡単に取り出して使うことができません。

### **3.テストが難しい**

一部の機能だけをテストしたい場合でも、密結合だと他の関連部分も含めてテストしないといけません。

## 疎結合とは？

疎結合とは、各コンポーネントやクラスができるだけ独立しており、互いに依存関係が少ない状態のことです。

## 疎結合なコードのメリット3つ

疎結合なコードのメリット3つを解説します。

### **1.変更や拡張がしやすい**

あるコンポーネントを変更しても他への影響が少ないため、保守が容易です。

### **2.テストが容易**

個々の部分を独立してテストできるため、単体テストがしやすく、バグの検出も簡単です。

### **3.再利用性が高い**

独立性が高いため、別の場所や別のプロジェクトでそのまま再利用しやすいです。

## すべてを疎結合にすることのデメリット3つ

疎結合は理想的に見えますが、すべてを疎結合にすることで生まれるデメリットもあります。

### 1.**複雑化**

すべてを疎結合にしようとすると、コンポーネント同士の連携に工夫が必要で、コード全体が複雑になることがあります。

### **2.設定の難しさ**

各部分が独立しているため、状態管理や依存関係の設定が増え、コードが読みにくくなる可能性があります。

### **3\. パフォーマンスの低下**

各部分が独立しすぎると、やり取りが増えてパフォーマンスが低下する場合もあります。

### 疎結合にしすぎた具体例

以下のように疎結合にしすぎるがゆえに、コールバックやデータの受け渡しが増えすぎてコードが複雑化しています。

また、複数のスクリーンで同じデータを管理するため、更新の際にどこを変更すべきかを把握するのが難しくなります。

今回の例においては、完全な疎結合を避けて、共通のデータを`Provider`や`Riverpod`などの状態管理パッケージを使って、一元化することで疎結合を保ちながらもデータの管理が簡単になります。

```dart
// ユーザー情報を表示し、設定ボタンが押されたときにコールバックを実行
class UserProfileScreen extends StatelessWidget {
  final User user;
  final VoidCallback onSettingsPressed;

  UserProfileScreen(this.user, this.onSettingsPressed);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Name: ${user.name}'),
        Text('Email: ${user.email}'),
        ElevatedButton(
          onPressed: onSettingsPressed,
          child: Text('Settings'),
        ),
      ],
    );
  }
}

// ユーザー設定を行いますが、UserProfileScreen からデータを引き継ぐ方法が複雑になっています
class UserSettingsScreen extends StatelessWidget {
  final Function(User) onSaveSettings;

  UserSettingsScreen(this.onSaveSettings);

  @override
  Widget build(BuildContext context) {
    // 設定を保存するためのフォームや入力フィールド
    return Column(
      children: [
        TextField(
          decoration: InputDecoration(labelText: 'Username'),
          onSubmitted: (newName) {
            // 保存ボタンでデータをコールバック経由で保存
            onSaveSettings(User(newName, 'newEmail@example.com'));
          },
        ),
        // 他の設定フィールド...
      ],
    );
  }
}

// つなぎ合わせるメインスクリーン
class MainScreen extends StatefulWidget {
  @override
  _MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  User user = User('Alice', 'alice@example.com');

  void _goToSettings() {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => UserSettingsScreen((updatedUser) {
        setState(() {
          user = updatedUser;
        });
      }),
    ));
  }

  @override
  Widget build(BuildContext context) {
    return UserProfileScreen(user, _goToSettings);
  }
}

class User {
  String name;
  String email;

  User(this.name, this.email);
}
```

## まとめ

疎結合を意識することは大切ですが、必要以上に分離しすぎず、適度に密結合も取り入れるバランスが重要です。
