Featured image of post 第1回-モンスターを量産したい!new()の限界 - mass-producing-monsters

第1回-モンスターを量産したい!new()の限界 - mass-producing-monsters

スライムを10体作りたい!でもnew()で毎回全属性を指定するのは面倒すぎる。もっと効率的にモンスターを量産する方法はないの?

@nqounetです。

新シリーズ「PerlとMooでモンスター軍団を量産してみよう」を始めます。全6回の連載で、モンスターを効率的に大量生産する技術を学びます。

このシリーズについて

このシリーズは、「Mooで覚えるオブジェクト指向プログラミング」シリーズの続編です。前シリーズでMooの基本を学んだ方を対象としています。

まだ読んでいない方は、先に以下の記事をご覧ください。

学習ゴール

  • 既存オブジェクトをコピーして新しいオブジェクトを効率的に生成する方法を習得
  • 「浅いコピー」と「深いコピー」の違いを実体験で理解
  • MooX::CloneやStorable::dclone()を使いこなせるようになる

対象読者

  • Perl入学式を卒業したばかりの方
  • Mooの基本(hasnewextends)を理解している方
  • 「Mooで覚えるオブジェクト指向プログラミング」シリーズを読了した方

シリーズ全体の目次は以下をご覧ください。

今回のゴール

モンスターを10体作りたいとき、new()で毎回全属性を指定するとコードが冗長になる問題を実感します。次回以降で解決策を学ぶための布石です。

シナリオ:スライム軍団を作りたい!

あなたはRPGゲームのモンスターデータを管理するプログラムを作っています。まずは基本のモンスター「スライム」をたくさん作りたいと考えました。

「スライムを10体作ろう!」

気合十分でコードを書き始めます。

Monsterクラスを定義する

まず、モンスターを表すMonsterクラスを作りましょう。

 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
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)

use v5.36;

package Monster {
    use Moo;

    # 名前(例: スライム)
    has name => (is => 'ro', required => 1);
    # HP(ヒットポイント)
    has hp => (is => 'rw', required => 1);
    # 攻撃力
    has attack => (is => 'rw', required => 1);
    # 防御力
    has defense => (is => 'rw', required => 1);
    # 色
    has color => (is => 'rw', default => '緑');

    sub show_status ($self) {
        say "【" . $self->name . "】HP:" . $self->hp
            . " 攻撃:" . $self->attack
            . " 防御:" . $self->defense
            . " 色:" . $self->color;
    }
}

# 1体だけ作ってみる
my $slime = Monster->new(
    name    => 'スライム',
    hp      => 10,
    attack  => 3,
    defense => 2,
);

$slime->show_status;
# 出力: 【スライム】HP:10 攻撃:3 防御:2 色:緑

1体作るのは簡単ですね。では、10体に増やしてみましょう。

問題:10体作るとコードが膨れ上がる

同じスライムを10体生成するコードを書いてみます。

 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
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)

use v5.36;

package Monster {
    use Moo;

    has name    => (is => 'ro', required => 1);
    has hp      => (is => 'rw', required => 1);
    has attack  => (is => 'rw', required => 1);
    has defense => (is => 'rw', required => 1);
    has color   => (is => 'rw', default => '緑');

    sub show_status ($self) {
        say "【" . $self->name . "】HP:" . $self->hp
            . " 攻撃:" . $self->attack
            . " 防御:" . $self->defense
            . " 色:" . $self->color;
    }
}

# 10体のスライムを生成
my @slimes = (
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
    Monster->new(name => 'スライム', hp => 10, attack => 3, defense => 2),
);

for my $slime (@slimes) {
    $slime->show_status;
}

これは辛い……。同じ内容を10回も書いています。

この方法の問題点

  1. 冗長: 同じパラメータを10回書くのは面倒
  2. 修正が大変: HPを10から15に変えたい場合、10箇所を修正する必要がある
  3. タイポのリスク: どこかでパラメータを間違えても気づきにくい
  4. スケールしない: 100体、1000体になったら現実的ではない

「量産型ザクを3行で作りたい」のに、現状では30行以上必要です。

ループで解決できる?

「ループを使えばいいのでは?」と思うかもしれません。確かに以下のように書けます。

1
2
3
4
5
6
7
8
9
my @slimes;
for (1..10) {
    push @slimes, Monster->new(
        name    => 'スライム',
        hp      => 10,
        attack  => 3,
        defense => 2,
    );
}

だいぶマシになりましたが、まだ課題があります。

  • バリエーションを作りたいとき: 赤スライム、青スライム、金スライムなど色違いを作りたい場合、結局パラメータを全部書き直す必要がある
  • ベースを複製したいとき: 「この強いスライムをベースに、少しだけ変えた個体を作りたい」という場面で、元のオブジェクトからコピーできない

「既存のスライムをコピーして、色だけ変えたい」そんな便利な方法があればいいのに……。

今回の完成コード

今回は問題提起がメインなので、完成コードは「問題が発生している状態」を示します。

 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
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)
#
# 問題: 10体のスライムを作るのにコードが冗長
# 次回: clone()メソッドを使って効率的に量産する方法を学ぶ

use v5.36;

package Monster {
    use Moo;

    has name    => (is => 'ro', required => 1);
    has hp      => (is => 'rw', required => 1);
    has attack  => (is => 'rw', required => 1);
    has defense => (is => 'rw', required => 1);
    has color   => (is => 'rw', default => '緑');

    sub show_status ($self) {
        say "【" . $self->name . "】HP:" . $self->hp
            . " 攻撃:" . $self->attack
            . " 防御:" . $self->defense
            . " 色:" . $self->color;
    }
}

# 10体のスライムを量産(冗長なコード)
my @slimes;
for (1..10) {
    push @slimes, Monster->new(
        name    => 'スライム',
        hp      => 10,
        attack  => 3,
        defense => 2,
    );
}

say "=== スライム軍団 ===";
for my $slime (@slimes) {
    $slime->show_status;
}

まとめ

  • new()でオブジェクトを作成する際、毎回すべての属性を指定する必要がある
  • 同じパラメータを何度も書くのは冗長でミスの温床になる
  • 既存オブジェクトを「コピー」して新しいオブジェクトを作る方法があると便利
  • 次回は、この問題を解決するclone()メソッドを学ぶ

次回予告

次回は、MooX::Cloneというモジュールを使って、既存オブジェクトをコピーする方法を学びます。「3行でスライム軍団を量産する」夢が叶います。お楽しみに。

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