Featured image of post 第6回-内部実装を外から触らせない - Mooで覚えるオブジェクト指向プログラミング

第6回-内部実装を外から触らせない - Mooで覚えるオブジェクト指向プログラミング

いいね数がマイナスになってしまった…それは内部状態を直接いじられたから。Mooで内部属性を隠蔽し、公開メソッド経由でのみ安全に操作する「カプセル化」の基本を学びます。

@nqounetです。

「Mooで覚えるオブジェクト指向プログラミング」シリーズの第6回です。

前回の振り返り

前回は、requiredで必須パラメータを強制し、defaultで安全なデフォルト値を設定する方法を学びました。

like_count属性を追加して、いいね機能を実装しましたね。

今回は、 内部実装を外から触らせない方法 を学びます。オブジェクト指向の重要な概念、 カプセル化 の入門編です。

内部状態を直接いじられる問題

前回のコードでは、like_countis => 'rw'で定義しました。これは便利ですが、実は危険な落とし穴があります。

1
2
3
my $msg = Message->new(content => 'おはよう');
$msg->like_count(-100);  # マイナスのいいね数!?
$msg->show;              # 投稿: おはよう (いいね: -100)

いいね数がマイナスになってしまいました。現実世界では、いいね数がマイナスになることはありませんよね。

is => 'rw'だと、外部から自由に値を設定できてしまいます。小さなプログラムなら「変な値を入れないように気をつければいい」で済みますが、大きなプログラムではそうはいきません。

「誰がこんな値を設定したんだ?」と追跡するのは大変です。そもそも、 不正な値を設定できないようにする のが正しい設計です。

解決策:アンダースコアで内部属性を示す

Perlでは慣習として、 アンダースコア(_)で始まる属性やメソッドは「内部用」 という意味を持ちます。これは「直接触らないでね」というサインです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package Message {
    use Moo;
    has content     => (is => 'ro', required => 1);
    has _like_count => (is => 'rw', default => 0);  # 内部用

    sub show {
        my $self = shift;
        print "投稿: " . $self->content . " (いいね: " . $self->_like_count . ")\n";
    }
};

my $msg = Message->new(content => 'おはよう');
$msg->show;  # 投稿: おはよう (いいね: 0)

like_count_like_countに変更しました。アンダースコアで始まる属性名は、「このクラスの内部でのみ使う属性ですよ」という宣言になります。

ただし、これは 約束事 であり、Perlが強制するものではありません。$msg->_like_count(100)と書けば、外部からでも呼び出せてしまいます。しかし、アンダースコアで始まる名前を見れば、多くのPerlプログラマは「これは触ってはいけないものだ」と理解します。

解決策:公開メソッド経由で安全に操作

内部属性を隠したら、次は 安全に操作できる公開メソッド を用意します。このメソッド内でバリデーション(検証)を行うことで、不正な値を防ぎます。

 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
package Message {
    use Moo;
    has content     => (is => 'ro', required => 1);
    has _like_count => (is => 'ro', default => 0);  # 内部用、読み取り専用に変更

    sub show {
        my $self = shift;
        print "投稿: " . $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;  # 読み取り専用の公開メソッド
    }
};

my $msg = Message->new(content => 'おはよう');
$msg->show;       # 投稿: おはよう (いいね: 0)
$msg->add_like;   # いいねを追加(公開メソッド経由)
$msg->add_like;
$msg->show;       # 投稿: おはよう (いいね: 2)

print $msg->like_count . "\n";  # 2(読み取りは可能)
# $msg->like_count(100);        # これはできない(rwではないから)

このコードのポイントは以下の通りです。

  • _like_countis => 'ro'に変更し、外部からの書き込みを禁止
  • add_likeメソッドでのみ、いいね数を増やせる
  • like_countメソッドで、現在のいいね数を読み取れる

これで、いいね数を 増やすことはできるが、減らしたり任意の値に設定したりはできない という設計になりました。

「いいねを取り消す機能が必要なら?」という場合は、remove_likeメソッドを追加して、その中で「0未満にはしない」というロジックを書けばいいのです。ルールを一箇所に集約できるので、バグを防ぎやすくなります。

まとめ

  • _で始まる属性名は「内部用」という慣習(Perlが強制するわけではない)
  • 内部属性はis => 'ro'にして、外部からの直接変更を禁止する
  • 公開メソッド経由でのみ操作することで、不正な値を防げる
  • この設計パターンを カプセル化 と呼ぶ

次回予告

次回は、掲示板に新しいクラスを追加します。メッセージを投稿する「ユーザー」を表すUserクラスを作り、複数のクラスが連携する設計を学びます。お楽しみに。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。