0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

『メッセージ結合』とは何か👀

0
Posted at

はじめに

ソフトウェア設計におけるモジュール結合について学んでおり、

その中で表題のパターンについて知ったので、今回はこれを記事にしてみたいと思います。

メッセージ結合はスタンプ結合やデータ結合と言ったモジュール結合の程度が弱いものよりも結合強度が弱く、一般的には良いモジュール結合であるとされています。

スタンプ結合やデータ結合については、僭越ながら、以下の記事をご参照ください。

メッセージ結合とは

メッセージ結合とは、引数のない関数・メソッドなどの呼び出し時に見られるモジュール結合のことであり、

class Caller {
  final Closable closable;
  Caller(this.closable);

  void doSomething() {
    closable.close();
  }
}

上記のコードではcloseメソッドの呼び出しに現れています。

不要な状態を渡さずに済む、と言う点では良いのですが、このメッセージ結合が必ずしも良いものとはいえないケースもあります。

良くないメッセージ結合の例:大極的には内容結合になっている

例えば以下のコードを見てみましょう。

class Caller {
  final UserListPresenter _userListPresenter;
  Caller({UserListPresenter userListPresenter}) : _userListPresenter = userListPresenter;

  void updateUserList() {
    final users = ...
    _userListPresenter.users = users;
    ...
    _userListPresenter.notifyUserListUpdated();
  }
}

上記コードのnotifyUserListUpdatedメソッドはメッセージ結合になっており、これ単体では結合度は低くなっています。

ところが少し視点を変えると、このnotifyUserListUpdatedメソッドはusersが更新された後に、

notifyUserListUpdatedメソッドを利用するように期待されています。

局所的にはメッセージ結合になっていても、大極的にみると「処理の呼び出し順とその確実な実行」と言う内部実装に依存した内容結合になっているのです。

内容結合については、僭越ながら、以下の記事をご参照ください。

リファクタリング後

このようにメッセージ結合になっているから良いのではなく、視点を変えてみたときにどう映るのかも意識せねばなりません。

例えば上記のコードなら、以下のようにリファクタリングをすることで、

データ結合になりつつも内容結合を解消することができています。

class Caller {
  final UserListPresenter _userListPresenter;
  Caller({UserListPresenter userListPresenter}) : _userListPresenter = userListPresenter;

  void updateUserList() {
    final users = ...
    _userListPresenter.notifyUserListUpdated(users);
  }
}

usersnotifyUserListUpdatedメソッドに渡し、このメソッドの中でusersの更新を行うようにすれば、

全体的な結合度を下げることに繋がりそうですね。

良くないメッセージ結合の例:表面的なメッセージ結合になっている

次に以下のコードを見てみましょう。

class Caller {
  final ErrorViewPresenter _errorViewPresenter;
  Caller({ErrorViewPresenter errorViewPresenter}) : _errorViewPresenter = errorViewPresenter;

  void updateViews() {
    var result = ...;
    ...
    var isErrorViewVisible = result.isError;
    if (isErrorViewVisible) {
      _errorViewPresenter.showErrorView();
    } else {
      _errorViewPresenter.hideErrorView();
    }
  }
}

class ErrorViewPresenter {
  ...
  void showErrorView() {
    view.isVisible = true;
  }

  void hideErrorView() {
    view.isVisible = false;
  }
}

Callerクラスは_errorViewPresenterオブジェクトに依存しており、条件分岐によりshowErrorViewメソッドかhideErrorViewメソッドを呼んでいます。

この2つのメソッドはメッセージ結合ですが、これらの使い分けはisErrorViewVisibleという真偽値に依存しており、

この値を上記2つのメソッドの引数に渡すこととあまり変わりがありません。

加えて、updateViewsメソッドが何をしているのかという、振る舞いを理解するには、それぞれの分岐先の内部実装まで読まねばならず、コードの斜め読みにも向いていません。

これ以上に実装が膨らむこともあるでしょうし、そうなれば複雑さはより増し、コードの流れも追いづらくなり、斜め読みもしにくくなっていきます。

リファクタリング後

そこで以下のコードのようにしてみましょう。

class Caller {
  ...
  void updateViews() {
    var result = ...;
    ...
    var isErrorViewVisible = result.isError;
    _errorViewPresenter.setErrorViewVisibility(isErrorViewVisible);
  }
}

class ErrorViewPresenter {
  ...
  void setErrorViewVisibility(bool isVisible) {
    view.isVisible = isVisible;
  }
}

メソッド名がsetErrorViewVisibilityに変わったことからもわかる通り、

エラー表示の状態切り変えを元のメソッド(showErrorViewhideErrorView)から切り出し、それを統合させ、それに伴ってsetErrorViewVisibilityメソッドに引数が渡るようになっています。

こうすることで結合度はデータ結合になりましたが、メソッドを役割ごとに分けていることでコードも読みやすくなっています。

先述のコードも実際はデータ結合になっていて、その上で複雑さが増すように実装されていたことを考えると、

こっちの方が同じ結合度ながらもコードの流れも追いやすくなっていて良さそうですね。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?