はじめに
ソフトウェア設計におけるモジュール結合について学んでおり、
その中で表題のパターンについて知ったので、今回はこれを記事にしてみたいと思います。
メッセージ結合はスタンプ結合やデータ結合と言ったモジュール結合の程度が弱いものよりも結合強度が弱く、一般的には良いモジュール結合であるとされています。
スタンプ結合やデータ結合については、僭越ながら、以下の記事をご参照ください。
メッセージ結合とは
メッセージ結合とは、引数のない関数・メソッドなどの呼び出し時に見られるモジュール結合のことであり、
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);
}
}
usersをnotifyUserListUpdatedメソッドに渡し、このメソッドの中で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に変わったことからもわかる通り、
エラー表示の状態切り変えを元のメソッド(showErrorViewとhideErrorView)から切り出し、それを統合させ、それに伴ってsetErrorViewVisibilityメソッドに引数が渡るようになっています。
こうすることで結合度はデータ結合になりましたが、メソッドを役割ごとに分けていることでコードも読みやすくなっています。
先述のコードも実際はデータ結合になっていて、その上で複雑さが増すように実装されていたことを考えると、
こっちの方が同じ結合度ながらもコードの流れも追いやすくなっていて良さそうですね。
参考