Рефакторинг: выделение класса
Сегодня мы рассмотрим технику рефакторинга, противоположную встраиванию класса. Рассматриваемая техника нужна, чтобы поделить работу, которую выполняет один класс, между двумя классами. Для чего это делать? Ну вообще-то класс должен представлять собой четкую абстракцию, которая выполняет лишь определенные операции и ничего более. Т.е. ничего лишнего в классе быть не должно. Со временем, когда добавляются новые функции в класс, он растет. И он может разрастить до таких размеров, что станет слишком сложным для понимания. В этом случае и приходит на помощь техника выделения класса.
Порядок применения техники:
- Определить, как будут разделены обязанности класса.
- Создать новый класс, который будет выполнять отдельные обязанности. Старый класс следует переименовать, если его обязанности перестанут соответствовать его имени.
- Организовать ссылку из старого класса в новый. Тут может потребоваться даже двусторонняя ссылка, но не стоит делать обратную ссылку без особой необходимости.
- Применить прием перемещения поля ко всем полям, которые нужно переместить.
- 5. После каждого перемещения крайне желательно выполнить тестирование.
- Применить прием перемещения метода ко всем методам, которые мы перемещаем из старого класса в новый. Тут важно начать с методов более низкого уровня (которые вызываются, а не вызывают).
- После каждого перемещения крайне желательно выполнить тестирование.
- Пересмотреть интерфейсы каждого класса и сократить их. Если ранее была создана двусторонняя ссылка, нужно постараться сделать из нее одностороннюю.
- Определить доступность нового класса. Если новый класс будет выставлен наружу, то нужно решить, как это сделать: сделать объект доступным по ссылке или по значению.
Перейдем к примерам. Рассмотрим класс, описывающий некую личность (какого-нибудь клиента, например):
class Person {
public function getName() {
return $this->_name;
}
public function getPhoneNumber() {
return '(' + $this->_officeAreaCode + ') ' + $this->_officeNumber;
}
private function getOfficeAreaCode() {
return $this->_officeAreaCode;
}
private function setOfficeAreaCode($arg) {
$this->_officeAreaCode = $arg;
}
private function getOfficeNumber() {
return $this->_officeNumber;
}
private function setOfficeNumber($arg) {
$this->_officeNumber = $arg;
}
private $_name;
private $_officeAreaCode;
private $_officeNumber;
}
Здесь мы видим, что можно выделить в отдельный класс методы, которые относятся к телефонным номерам. Определим класс телефонного номера:
class PhoneNumber {
}
Создадим ссылку из класса Person в новый класс телефонного номера:
class Person {
// ...
private $_officePhone;
function __construct() {
$this->_officePhone = new PhoneNumber();
}
}
Применим теперь прием перемещения поля к одному из полей класса Person (например, к полю _officeAreaCode):
class PhoneNumber {
public function getAreaCode() {
return $this->_areaCode;
}
public function setAreaCode($arg) {
$this->_areaCode = $arg;
}
private $_areaCode;
}
class Person {
// ...
public function getPhoneNumber() {
return '(' + $this->getOfficeAreaCode() + ') ' + $this->_officeNumber;
}
private function getOfficeAreaCode() {
return $this->_officePhone->getAreaCode();
}
private function setOfficeAreaCode($arg) {
$this->_officePhone->setAreaCode($arg);
}
}
После этого можно перенести другое поле (_officeNumber) и применить прием перемещения метода для номера телефона:
class Person {
// ...
function __construct() {
$this->_officePhone = new PhoneNumber();
}
public function getName() {
return $this->_name;
}
public function getPhoneNumber() {
return $this->_officePhone->getPhoneNumber();
}
private function getOfficePhone() {
return $this->_officePhone;
}
private $_name;
private $_officePhone;
}
class TelephoneNumber {
// ...
public function getPhoneNumber() {
return '(' + $this->_areaCode + ') ' + $this->_number;
}
public function getAreaCode() {
return $this->_areaCode;
}
public function setAreaCode($arg) {
$this->_areaCode = $arg;
}
public function getNumber() {
return $this->_number;
}
public function setNumber($arg) {
$this->_number = $arg;
}
private $_number;
private $_areaCode;
}
После всего этого нам остается решить, сделать ли новый класс доступным для клиентов и в какой мере. Можно обеспечить доступ к телефонному номеру только через класс Person, можно сделать телефонный номер неизменяемым и т.п. Но это решение нужно принимать в засимости от конкретной ситуации.
На этом статья завершена, спасибо за внимание!


Декабрь 29th, 2008
Хорошая статья.
Май 19th, 2009
А почему в последнем примере новый класс магическим образом поменял название с PhoneNumber на TelephoneNumber?
Май 19th, 2009
Наверное это ошибка
Но суть не меняется
Май 19th, 2009
Ну, по смыслу понятно, что это скорее описка, но “как-то не аккуратненько” (с) бородатый анекдот