Рефакторинг: выделение класса

Сегодня мы рассмотрим технику рефакторинга, противоположную встраиванию класса. Рассматриваемая техника нужна, чтобы поделить работу, которую выполняет один класс, между двумя классами. Для чего это делать? Ну вообще-то класс должен представлять собой четкую абстракцию, которая выполняет лишь определенные операции и ничего более. Т.е. ничего лишнего в классе быть не должно. Со временем, когда добавляются новые функции в класс, он растет. И он может разрастить до таких размеров, что станет слишком сложным для понимания. В этом случае и приходит на помощь техника выделения класса.

Порядок применения техники:

  1. Определить, как будут разделены обязанности класса.
  2. Создать новый класс, который будет выполнять отдельные обязанности. Старый класс следует переименовать, если его обязанности перестанут соответствовать его имени.
  3. Организовать ссылку из старого класса в новый. Тут может потребоваться даже двусторонняя ссылка, но не стоит делать обратную ссылку без особой необходимости.
  4. Применить прием перемещения поля ко всем полям, которые нужно переместить.
  5. 5. После каждого перемещения крайне желательно выполнить тестирование.
  6. Применить прием перемещения метода ко всем методам, которые мы перемещаем из старого класса в новый. Тут важно начать с методов более низкого уровня (которые вызываются, а не вызывают).
  7. После каждого перемещения крайне желательно выполнить тестирование.
  8. Пересмотреть интерфейсы каждого класса и сократить их. Если ранее была создана двусторонняя ссылка, нужно постараться сделать из нее одностороннюю.
  9. Определить доступность нового класса. Если новый класс будет выставлен наружу, то нужно решить, как это сделать: сделать объект доступным по ссылке или по значению.

Перейдем к примерам. Рассмотрим класс, описывающий некую личность (какого-нибудь клиента, например):

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, можно сделать телефонный номер неизменяемым и т.п. Но это решение нужно принимать в засимости от конкретной ситуации.

На этом статья завершена, спасибо за внимание! :)





Читайте также:



4 Ответов на “Рефакторинг: выделение класса”

  1. Игорь

    Хорошая статья.

  2. А почему в последнем примере новый класс магическим образом поменял название с PhoneNumber на TelephoneNumber?

  3. novice

    Наверное это ошибка :) Но суть не меняется

  4. Ну, по смыслу понятно, что это скорее описка, но “как-то не аккуратненько” (с) бородатый анекдот


© Copyright. . I-Novice. All Rights Reserved. Terms | Site Map