@nqounetです。
「Mooで覚えるオブジェクト指向プログラミング」シリーズの第8回です。
前回の振り返り
前回は、関連するデータを別のクラスに分離し、複数のクラスを連携させる方法を学びました。
MessageクラスからUserクラスを抽出して、user属性でUserオブジェクトを保持する設計にしましたね。
今回は、 似ているクラスのコード重複を解消する方法 を学びます。オブジェクト指向の重要な概念、 継承 の入門編です。
似たクラスでコードが重複する問題
掲示板を発展させて、「管理者からのお知らせ」機能を追加することになりました。管理者のメッセージには「重要」というラベルを付けたいとします。
通常のMessageクラスとは別に、AdminMessageクラスを作ってみましょう。
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
| package Message {
use Moo;
has content => (is => 'ro', required => 1);
has _like_count => (is => 'ro', default => 0);
has user => (is => 'ro', required => 1);
sub show {
my $self = shift;
my $author = $self->user->display_name;
print "[$author] " . $self->content . "\n";
}
sub add_like {
my $self = shift;
$self->{_like_count}++;
}
};
package AdminMessage {
use Moo;
has content => (is => 'ro', required => 1); # 重複!
has _like_count => (is => 'ro', default => 0); # 重複!
has user => (is => 'ro', required => 1); # 重複!
has is_important => (is => 'ro', default => 0); # 管理者専用
sub show { # ほぼ重複!
my $self = shift;
my $author = $self->user->display_name;
my $label = $self->is_important ? '【重要】' : '';
print "$label\[$author] " . $self->content . "\n";
}
sub add_like { # 完全に重複!
my $self = shift;
$self->{_like_count}++;
}
};
|
よく見ると、content、_like_count、user、add_likeがまったく同じです。これはコードの重複であり、問題があります。
- 同じコードを2箇所に書いているので、修正時に両方を変更する必要がある
- 片方だけ修正し忘れると、バグになる
- クラスが増えるたびに、重複も増えていく
解決策:継承で共通部分を親クラスに
この問題を解決するのが 継承 です。共通の機能を「親クラス」にまとめ、「子クラス」がそれを受け継ぐ仕組みです。
Mooでは、extendsを使って継承を実現します。
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
44
45
| 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);
sub show {
my $self = shift;
my $author = $self->user->display_name;
print "[$author] " . $self->content . "\n";
}
sub add_like {
my $self = shift;
$self->{_like_count}++;
}
};
package AdminMessage {
use Moo;
extends 'Message'; # Messageを継承!
has is_important => (is => 'ro', default => 0); # 管理者専用の属性
};
my $user = User->new(name => 'admin');
my $msg = AdminMessage->new(
content => 'システムメンテナンスのお知らせ',
user => $user,
is_important => 1,
);
print $msg->content . "\n"; # システムメンテナンスのお知らせ
$msg->show; # [admin] システムメンテナンスのお知らせ
$msg->add_like; # 親クラスのメソッドが使える!
|
extends 'Message'と書くだけで、AdminMessageクラスはMessageクラスのすべての属性とメソッドを引き継ぎます。
content、_like_count、user属性はMessageから継承show、add_likeメソッドもMessageから継承- AdminMessageには独自の
is_important属性だけを追加
これで、重複コードがなくなりました!Messageクラスを修正すれば、AdminMessageにも自動的に反映されます。
親クラスと子クラスの関係
継承では、元になるクラスを 親クラス (または基底クラス、スーパークラス)、継承するクラスを 子クラス (または派生クラス、サブクラス)と呼びます。
今回の例では、Messageが親クラス、AdminMessageが子クラスです。
子クラスは、親クラスの機能をすべて持った上で、独自の機能を追加できます。これは「AdminMessageは 一種の Messageである」という関係を表しています。
- AdminMessageはMessageである(content、userを持ち、showできる)
- しかし、AdminMessageには追加でis_importantという属性がある
この「AはBの一種である」(is-a関係)が継承の本質です。
まとめ
extends 'ParentClass'で親クラスを継承できる- 子クラスは、親クラスの属性とメソッドをすべて引き継ぐ
- 子クラスでは独自の属性やメソッドを追加できる
- 継承により、コードの重複を解消し、保守性が向上する
次回予告
次回は、親クラスのメソッドを子クラスで「上書き」する方法を学びます。AdminMessageのshowメソッドで「【重要】」ラベルを表示するには? オーバーライド の出番です。お楽しみに。