@nqounetです。
「Mooで覚えるオブジェクト指向プログラミング」シリーズの第7回です。
前回の振り返り
前回は、内部実装を外から触らせない「カプセル化」を学びました。
アンダースコアで始まる_like_countを内部属性として定義し、add_likeメソッド経由でのみ安全に操作できるようにしましたね。
今回は、 関連するデータを別のクラスに分離する方法 を学びます。
クラスが巨大化する問題
掲示板アプリを発展させていくと、Messageクラスにどんどん属性が増えていきます。投稿者の名前、メールアドレス、アバター画像のURL、自己紹介文……。
1
2
3
4
5
6
7
8
9
10
| package Message {
use Moo;
has content => (is => 'ro', required => 1);
has _like_count => (is => 'ro', default => 0);
has author_name => (is => 'ro', required => 1); # 投稿者名
has author_email => (is => 'ro'); # メールアドレス
has author_avatar => (is => 'ro'); # アバターURL
has author_bio => (is => 'ro'); # 自己紹介
# ... さらに増えていく
};
|
よく見ると、author_で始まる属性がたくさんあります。これらは「投稿者」に関するデータであり、「メッセージ」のデータとは性質が異なります。
このまま属性を追加し続けると、Messageクラスは「なんでも屋」になってしまいます。コードの見通しが悪くなり、変更も難しくなっていきます。
解決策:Userクラスを分離する
「投稿者」のデータは、別のクラスに分離しましょう。これが 複数クラスの連携 という考え方です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package User {
use Moo;
has name => (is => 'ro', required => 1);
has email => (is => 'ro');
has avatar => (is => 'ro');
has bio => (is => 'ro', default => '');
sub display_name {
my $self = shift;
return $self->name;
}
};
my $user = User->new(
name => 'nqounet',
email => 'nqounet@example.com',
avatar => '/images/nqounet.png',
bio => 'Perlが好きです',
);
print $user->display_name . "\n"; # nqounet
|
Userクラスには、ユーザーに関する属性だけを定義します。名前、メールアドレス、アバター、自己紹介文。すべて「ユーザー」という概念に関連するデータです。
これで、Messageクラスからauthor_で始まる属性を取り除けます。
解決策:MessageがUserを保持する
Userクラスを分離したら、MessageクラスにはUserオブジェクトを「持たせる」ようにします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| package User {
use Moo;
has name => (is => 'ro', required => 1);
sub display_name {
my $self = shift;
return $self->name;
}
};
package Message {
use Moo;
has content => (is => 'ro', required => 1);
has _like_count => (is => 'ro', default => 0);
has user => (is => 'ro', required => 1); # Userオブジェクトを保持
sub show {
my $self = shift;
my $author = $self->user->display_name;
print "[$author] " . $self->content . " (いいね: " . $self->_like_count . ")\n";
}
sub add_like {
my $self = shift;
$self->{_like_count}++;
}
sub like_count {
my $self = shift;
return $self->_like_count;
}
};
# Userオブジェクトを作成
my $user = User->new(name => 'nqounet');
# Messageオブジェクトを作成(Userオブジェクトを渡す)
my $msg = Message->new(
content => 'Mooは便利ですね!',
user => $user,
);
$msg->show; # [nqounet] Mooは便利ですね! (いいね: 0)
|
Messageクラスのuser属性には、文字列ではなく Userオブジェクト を格納します。
$self->user->display_nameのように、Userオブジェクトのメソッドを呼び出すことで、投稿者の名前を取得できます。これは「Messageオブジェクトが、Userオブジェクトを 持っている 」という関係です。
この設計には、いくつかの利点があります。
- MessageクラスはUserの内部実装を知らなくてよい
- Userクラスを変更しても、Messageクラスへの影響が少ない
- 同じUserオブジェクトを複数のMessageで共有できる
まとめ
- 関連するデータ群は、別のクラスに分離するとよい
- あるオブジェクトが別のオブジェクトを「持つ」設計を 複数クラスの連携 と呼ぶ
- クラスを分離することで、コードの見通しがよくなる
- 同じオブジェクトを複数箇所で共有できるようになる
次回予告
次回は、クラスの「親子関係」を学びます。共通の機能を持つクラスを作るとき、同じコードを何度も書くのは無駄ですよね。 継承 を使って、コードの重複を減らす方法を紹介します。お楽しみに。