📑

phpでのimmutableなクラスの実装方法

2022/11/16に公開

immutable(不変)なクラスの実装について、本を読んだことをきっかけに知った。
php での実装方法について興味があったので、調べてみたり自分で考えた結果を記事にしてみる。

immutable とは

そもそも immutable ってなに?コトバンクには以下のように記述がある。

変わらない, 変えられない, 不変の, 不易の

プログラミング的に言えば、「中身を変えることのできない」という意味。
つまり、変数の中身を変えることができないということ。

変数に対して再代入をすることができない。不変状態のことを指す。

実装方法

では実際に実装をしてみたので、紹介していく。

今回は名前と年齢を持った Person クラスを実装する想定で記述していく。

まずはコンストラクタで受け取った引数をインスタンス変数に代入する。

<?php

class person
{
    private $name;
    private $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

ゲッターでは受け取った値を単純に return するだけ。

    /**
     * return name
     *
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * return age
     *
     * @return integer
     */
    public function getAge(): int
    {
        return $this->age;
    }

最後にセッター。
セッターでは値を再代入するのではなく、新しいインスタンスを作成し return するようにしている。
再代入することを「破壊的代入」と呼ぶ。

新しいインスタンスを作成する理由としては、この破壊的代入を防ぐため。
新しいインスタンスを返すことで、元のインスタンスから状態が変わることがなくなる。

/**
     * set name
     *
     * @param string $name
     * @return self
     */
    public function setName(string $name): self
    {
        return new self($name, $this->age);
    }

    /**
     * set age
     *
     * @param integer $age
     * @return self
     */
    public function setAge(int $age): self
    {
        return new self($this->name, $age);
    }

以下に実装の全体像を置いておく。

person.php
<?php

class person
{
    private $name;
    private $age;

    public function __construct(string $name, int $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    /**
     * return name
     *
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * return age
     *
     * @return integer
     */
    public function getAge(): int
    {
        return $this->age;
    }

    /**
     * set name
     *
     * @param string $name
     * @return self
     */
    public function setName(string $name): self
    {
        return new self($name, $this->age);
    }

    /**
     * set age
     *
     * @param integer $age
     * @return self
     */
    public function setAge(int $age): self
    {
        return new self($this->name, $age);
    }
}

さいごに

今回実装した方法だと、バリデーションなどがないため$age にマイナスの値などが入ってしまう。
それを防ぎたい場合は、コンストラクタにバリデーションを追加すれば、より安全なクラスを実装することができるようになる。

また、そもそもセッター/ゲッターの使用についても賛否両論あるかもしれない。
しかし、それはまた別のお話。

Discussion