Рефакторинг: выделение класса
Сегодня мы рассмотрим технику рефакторинга, противоположную встраиванию класса. Рассматриваемая техника нужна, чтобы поделить работу, которую выполняет один класс, между двумя классами. Для чего это делать? Ну вообще-то класс должен представлять собой четкую абстракцию, которая выполняет лишь определенные операции и ничего более. Т.е. ничего лишнего в классе быть не должно. Со временем, когда добавляются новые функции в класс, он растет. И он может разрастить до таких размеров, что станет слишком сложным для понимания. В этом случае и приходит на помощь техника выделения класса.
Порядок применения техники:
- Определить, как будут разделены обязанности класса.
- Создать новый класс, который будет выполнять отдельные обязанности. Старый класс следует переименовать, если его обязанности перестанут соответствовать его имени.
- Организовать ссылку из старого класса в новый. Тут может потребоваться даже двусторонняя ссылка, но не стоит делать обратную ссылку без особой необходимости.
- Применить прием перемещения поля ко всем полям, которые нужно переместить.
- 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, можно сделать телефонный номер неизменяемым и т.п. Но это решение нужно принимать в засимости от конкретной ситуации.
На этом статья завершена, спасибо за внимание!
Хорошая статья.
А почему в последнем примере новый класс магическим образом поменял название с PhoneNumber на TelephoneNumber?
Наверное это ошибка Но суть не меняется
Ну, по смыслу понятно, что это скорее описка, но “как-то не аккуратненько” (с) бородатый анекдот