1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

getterの命名について(PHPの場合)

Posted at

TL;DR

getterの命名とか、悩みを吐き出して冷静になります

「これgetterだからgetHoge()の方がわかりやすくないですか?」と言われ、明確にNoと言えなかったので言語化しておきたいのです

※ setterにはあまり触れません

結論

hogeプロパティを参照する際は

  • __get()を実装し
  • @property-read hogeを定義し
  • getterとしてのgetHoge()は撲滅

発端

ゲームとかで、IDと経験値を持った以下のPlayerクラスを考えます

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }
}

何も考えずにgetterをつけてやると、おそらく以下です

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }
}

要件

このPlayerクラスに「経験値を取得する」メソッドを定義したいですが、どういう命名が適切でしょうか?

いわゆるsetterとしてsetExp()を定義すべきでしょうか?

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function setExp(int $exp): self
    {
        $this->exp = $exp;
        return $this;
    }
}

あるいはプロパティのexpに対して加算するので、addExp()を定義すべきでしょうか?

class Player
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function addExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

違和感

addExp()にしたとして、利用する側を考えると以下のようになりそうです

$player = new Player($id, $exp);
// 現在の経験値
echo $player->getExp() . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->addExp($addExp);
// 加算後の経験値
echo $player->getExp() . PHP_EOL;

でも「経験値を取得する」って、本来はPlayerから見た振る舞いではないでしょうか?

setExp()addExp()は誰かがPlayerに「取得させてる」って感じの振る舞いです

「経験値を取得する」をPlayerから見たら、それこそgetExp()なのでは?と思うわけです

class User
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getExp(): int
    {
        return $this->exp;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

でもこれは当然、本来のgetterとしてのgetExp()と重複してるのでエラーになります

そもそも

Playerの現在の経験値を参照するメソッドがgetExp()っていうのも、やっぱり「誰かから見た」振る舞いです

オブジェクト指向って、もっとオブジェクトが自身の振る舞いを定義するべきでは?と思うわけです

そう考えると、Playerの現在の経験値を参照するメソッドは、なんならtakeExp()とかshowExp()とかの方がよっぽどオブジェクト指向的だと思うわけです

でも文脈によってtakeshowは適切でない場合が多々あると思うので、プロパティそのものが参照できれば一番無難そうです

解決

PHPではマジックメソッド__get()を実装することで、存在しないプロパティへアクセスした際の挙動を制御できます

「経験値を取得する」をgetExp()として、合わせて定義すると以下です

/**
 * @property-read int $exp
 */
class User
{
    private $id;

    private $exp;

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function __get(string $property)
    {
        if (!property_exists($this, $property)) {
            throw new \Exception("Invalid access: {$property}");
        }

        return $this->$property;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

利用する側は以下のようになります

$player = new Player($id, $exp);
// 現在の経験値
echo $player->exp . PHP_EOL;
$addExp = someFunction(); // 獲得したい経験値
$player->getExp($addExp);
// 加算後の経験値
echo $player->exp . PHP_EOL;

@property-readアノテーションを付けておくことで、PhpStorm等のIDEでも認識してくれるようになります

しかしこの場合、@property-readアノテーションを付けていないidプロパティも同様にアクセスできてしまいます

@property-readしたものしか見せたくないという場合は、__get()でバリデーションするしかなさそうです

/**
 * @property-read int $exp
 */
class User
{
    private $id;

    private $exp;

    private const VISIBLE_PROPERTIES = [
        'exp',
    ];

    public function __construct(int $id, int $exp)
    {
        $this->id = $id;
        $this->exp = $exp;
    }

    public function __get(string $property)
    {
        if (!property_exists($this, $property)) {
            throw new \Exception("Invalid access: {$property}");
        }

        if (!in_array($property, self::VISIBLE_PROPERTIES)) {
            throw new \Exception("Cannot access: {$property}");
        }

        return $this->$property;
    }

    public function getExp(int $exp): self
    {
        $this->exp += $exp;
        return $this;
    }
}

まとめ

さすがにgetExp()は紛らわしいですよ…という意見はありそうですが、それはそれとして

脳死でgetter定義せずに、もっともっとオブジェクティブな振る舞いを定義していきたいなと思う次第です

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?