第7章 オブジェクト間での特性の移動
オブジェクトの設計の根幹は、責務をどこに配置するか。責務をはじめから正しい場所に配置することは難しいため、リファクタリングを通して適切な場所に配置する。
メソッドの移動
あるクラスのメソッドが他のクラスから多用されているとき、そのメソッドを多用するクラスに移動・作成する。
- 動機
- クラスの振る舞いが多い
- クラス間のやり取りが多く結合度が高いとき
<?php
class Account
{
@var
private $type;
@var
private $daysOverdrawn;
@return
public function overdraftCharge(): float
{
if ($this->type->isPremium()) {
$result = 10;
if ($this->daysOverdrawn > 7) $result += ($this->daysOverdrawn - 7) * 0.85;
return $result;
} else {
return $this->daysOverdrawn * 1.75;
}
}
@return
public function bankCharge(): float
{
$result = 4.5;
if ($this->daysOverdrawn > 0) $result += $this->overdraftCharge();
return $result;
}
}
新たな口座の種類ができるとして口座の種類ごとに手数料を計算しなければならない場合、overdraftCharge()がAccountTypeクラスにへの結合度が高くなってしまうのでリファクタリングをする。
口座の種類はAccountTypeで判断させるため、overdraftCharge()をAccountTypeクラスに移動する。
<?php
class Account
{
@var
private $type;
@var
private $daysOverdrawn;
@return
public function getType(): AccountType
{
return $this->type;
}
@return
public function getDaysOverdrawn()
{
return $this->daysOverdrawn;
}
public function overdraftCharge()
{
return $this->type->overdraftCharge($this->daysOverdrawn);
}
@return
public function bankCharge(): float
{
$result = 4.5;
if ($this->daysOverdrawn > 0) $result += $this->overdraftCharge();
return $result;
}
}
<?php
class AccountType
{
@param
@return
public function overdraftCharge(Account $account): float
{
if ($this->isPremium()) {
$result = 10;
if ($account->getdaysOverdrawn() > 7) $result += ($account->getdaysOverdrawn() - 7) * 0.85;
return $result;
} else {
return $account->getdaysOverdrawn() * 1.75;
}
}
}
フィールドの移動
ある週では正しく適正であった設計判断も、次の週にはそうではなくなる。それ自体は問題ではなく、それについて何もしないことが問題。リファクタリングを通して常に設計を修正する。
- 動機
- そのクラスのメソッドよりも別のクラスのメソッドのほうがそのフィールドを多く使っているとわかったとき。
- クラスの抽出を行うため。クラスの抽出を行う場合、先にフィールドを移動してからメソッドを移動させる。
<?php
class Account
{
@var
private $type;
@var
private $interestRate;
@param
@param
@return
public function interestForAmountDays(float $amount, int $days): float
{
return $this->interestRate * $amount * $days / 365;
}
}
Accountクラスの$interestRateのフィールドをAccountTypeクラスに移動する。interestForAmountDaysメソッドがこのフィールドを利用している。
<?php
class AccountType
{
@var
private $interestRate;
@return
public function getInterestRate(): float
{
return $this->interestRate;
}
@param
public function setInterestRate(float $interestRate): void
{
$this->interestRate = $interestRate;
}
}
<?php
class Account
{
@var
private $type;
@param
@param
@return
public function interestForAmountDays(float $amount, int $days): float
{
return $this->type->getInterestRate() * $amount * $days / 365;
}
}
Accountクラスの$interestRateフィールドを削除する。
クラスの抽出
「クラスはきっちりと抽象化されたものであり、少数の明確な責務を担うべきである」
実際には操作やデータを追加していくため、クラスは成長する。
クラスに責務を少しずつ追加していき、やがて手におえないほどクラスは複雑に成長する。。。
<?php
class Person
{
@var
private $name;
@var
private $officeAreaCode;
@var
private $officeNumber;
@return
public function getName(): string
{
return $this->name;
}
@param
public function setName(string $name): void
{
$this->name = $name;
}
@return
public function getOfficeAreaCode(): string
{
return $this->officeAreaCode;
}
@param
public function setOfficeAreaCode(string $officeAreaCode): void
{
$this->officeAreaCode = $officeAreaCode;
}
@return
public function getOfficeNumber(): string
{
return $this->officeNumber;
}
@param
public function setOfficeNumber(string $officeNumber): void
{
$this->officeNumber = $officeNumber;
}
public function getTelephoneNumber(): string
{
return "({$this->officeAreaCode}){$this->officeNumber}";
}
}
電話番号の振る舞いをクラスとして抽出する。
<?php
class Person
{
@var
private $name;
@var
private $officeTelephone;
@return
public function getName(): string
{
return $this->name;
}
@param
public function setName(string $name): void
{
$this->name = $name;
}
@return
public function getOfficeTelephone(): TelephoneNumber
{
return $this->officeTelephone;
}
@return
public function getTelephoneNumber(): string
{
return $this->officeTelephone->getTelephoneNumber();
}
}
<?php
class TelephoneNumber
{
@var
private $areaCode;
@var
private $number;
@return
public function getNumber(): string
{
return $this->number;
}
@param
public function setNumber(string $number): void
{
$this->number = $number;
}
@return
public function getAreaCode(): string
{
return $this->areaCode;
}
@param
public function setAreaCode(string $areaCode): void
{
$this->areaCode = $areaCode;
}
public function getTelephoneNumber(): string
{
return "({$this->areaCode}){$this->number}";
}
}
委譲の隠蔽
人クラス
<?php
class Person
{
@var
private $department;
@return
public function getDepartment(): Department
{
return $this->department;
}
@param
public function setDepartment(Department $department): void
{
$this->department = $department;
}
}
部門クラス
<?php
class Department
{
@var
private $chargeCode;
@var
private $manager;
@param
public function __construct(Person $manager)
{
$this->manager = $manager;
}
@return
public function getManager(): Person
{
return $this->manager;
}
}
例えば、会計部門の人を作成して、その部門のマネージャを取得したいとき、
<?php
$person = new Person($accountingDepartment);
$manager = $person->getDepartment()->getManager();
とする必要がある。
<?php
public function getManager(): Person
{
return $this->department->getManager();
}
委譲メソッドをPersonクラスに作り、Departmentクラスをクライアントから隠蔽することで結合度を低くする。
<?php
$person = new Person($accountingDepartment);
$manager = $person->getManager();