Featured image of post 第6回-掛け算・引き算も追加(OCP) - PerlとMooでダイス言語を作ってみよう

第6回-掛け算・引き算も追加(OCP) - PerlとMooでダイス言語を作ってみよう

引き算(SubExpr)と掛け算(MulExpr)を追加します。既存コードを変更せずに機能を拡張する「開放閉鎖の原則」を実践します。

前回の振り返り

前回は、ExpressionRoleを定義してすべての式クラスに共通のインターフェースを与えました。

今回は、引き算と掛け算に対応するクラスを追加します。重要なのは、既存のコードを変更せずに機能を拡張できることです。

除算(DivExpr)も同じ要領で追加できますが、今回はOCPの練習として引き算・掛け算に絞ります。

算術演算の拡張

引き算クラス: SubExpr

AddExprと同じパターンで、SubExpr(引き算)を作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package SubExpr {
    use Moo;
    with 'ExpressionRole';

    has left  => (is => 'ro', required => 1);
    has right => (is => 'ro', required => 1);

    sub eval($self) {
        return $self->left->eval - $self->right->eval;
    }
}

掛け算クラス: MulExpr

同様に、MulExpr(掛け算)を作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package MulExpr {
    use Moo;
    with 'ExpressionRole';

    has left  => (is => 'ro', required => 1);
    has right => (is => 'ro', required => 1);

    sub eval($self) {
        return $self->left->eval * $self->right->eval;
    }
}

開放閉鎖の原則(OCP)

ここで注目すべきは、NumberExpr、DiceExpr、AddExpr、ExpressionRoleのコードを一切変更していないことです。

これはSOLID原則の2番目、「開放閉鎖の原則」(Open-Closed Principle)の実践です。

  • 拡張に対して開いている: 新しい式クラスをいくらでも追加できる
  • 修正に対して閉じている: 既存のクラスを変更する必要がない

開放閉鎖の原則のイメージ

	classDiagram
    class ExpressionRole {
        <<role>>
        +eval()*
    }
    ExpressionRole <|.. NumberExpr
    ExpressionRole <|.. DiceExpr
    ExpressionRole <|.. AddExpr
    ExpressionRole <|.. SubExpr
    ExpressionRole <|.. MulExpr

完成コード

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/usr/bin/env perl
use v5.36;

package ExpressionRole {
    use Moo::Role;
    requires 'eval';
}

package NumberExpr {
    use Moo;
    with 'ExpressionRole';
    has value => (is => 'ro', required => 1);
    sub eval($self) { return $self->value; }
}

package DiceExpr {
    use Moo;
    with 'ExpressionRole';
    has count => (is => 'ro', required => 1);
    has sides => (is => 'ro', required => 1);
    sub eval($self) {
        my $total = 0;
        for (1 .. $self->count) {
            $total += int(rand($self->sides)) + 1;
        }
        return $total;
    }
}

package AddExpr {
    use Moo;
    with 'ExpressionRole';
    has left  => (is => 'ro', required => 1);
    has right => (is => 'ro', required => 1);
    sub eval($self) { return $self->left->eval + $self->right->eval; }
}

package SubExpr {
    use Moo;
    with 'ExpressionRole';
    has left  => (is => 'ro', required => 1);
    has right => (is => 'ro', required => 1);
    sub eval($self) { return $self->left->eval - $self->right->eval; }
}

package MulExpr {
    use Moo;
    with 'ExpressionRole';
    has left  => (is => 'ro', required => 1);
    has right => (is => 'ro', required => 1);
    sub eval($self) { return $self->left->eval * $self->right->eval; }
}

# (2d6-1)*2
my $expr = MulExpr->new(
    left => SubExpr->new(
        left  => DiceExpr->new(count => 2, sides => 6),
        right => NumberExpr->new(value => 1),
    ),
    right => NumberExpr->new(value => 2),
);
say "(2d6-1)*2: " . $expr->eval;

今回のまとめ

今回は、SubExprとMulExprを追加し、開放閉鎖の原則を実践しました。

次回は、文字列「2d6+3」から自動的に式ツリーを構築するパーサーを作り、ダイス言語インタプリタを完成させます。

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