Рефакторинг: перемещение метода

В прошлой статье из цикла рефакторинга про встраивание класса я писал, что расскажу о таких приемах, как «Перемещение метода» и «Перемещение поля». Сегодня время пришло написать об одном из них - о первом.

Иногда метод чаще использует функции другого класса (или наоборот - используется ими), чем своего собственного. Выход из такой ситуации здесь состоит в том, чтобы создать аналогичный метод в том классе, функции которого этот метод чаще использует.

Но этот прием не всегда удается провести. Если нет уверенности в его необходимости - то лучше не стоит, особенно в крупных проектах. Почему? Сами знаете :)

Порядок применения приема состоит в следующем.

Сначала нужно изучить все функции, которые используются исходным методом в исходном классе. Если какая-то функция используется только исходным методом, то ее тоже лучше переместить, а если она используется и другими методами, то нужно также проверить, нельзя ли переместить и их. Просто иногда проще переместить целую группу методов, чем перемещать их по одному.

Затем необходимо проверить, нет ли в родительских классах и подклассах исходного класса других объявлений исходного метода. Если есть, то перемещение становится невозможным из-за полиморфизма. Если отразить полиморфизм в целевом классе, то перемещение становится возможным.

Теперь нужно объявить перемещаемый метод в целевом классе. Можно даже выбрать для метода другое имя, которое будет лучше соответствовать целевому классу.

Затем копируем код исходного метода в целевой, после чего приспосабливаем скопированный код под новое место. Например, перемещаемому методу может потребоваться его исходный объект. В этом случае нужно как-то передать этот объект (например, через параметр). Если метод содержит обработчики исключительных ситуаций, то следует определить, какому из классов - целевому или исходному - логичнее будет обрабатывать exceptions.

Теперь можно сделать из исходного метода делегирующий, т.е. который не будет содержать код, а просто будет вызывать целевой метод. Можно оставить метод как делегирующий в исходном классе, а можно его удалить, предварительно заменив все обращения к нему обращениями к созданному в другом классе методу.

Пример

В качестве примера представим себе, что у нас есть программа управления банковскими счетами. Пусть она начисляет дополнительную плату владельцу счета за превышение срока кредита по определенному алгоритму:

class Account {
	// ...
	function overdraftCharge() {
		if ($this->_type->isPremium()) {
			$result = 10;
			if ($this->_daysOverdrawn > 7) {
				$result += ($this->_daysOverdrawn - 7) * 0.85;
			}
			return $result;
		}
		return $this->_daysOverdrawn * 1.75;
   }
   function bankCharge() {
		$result = 4.5;
		if ($this->_daysOverdrawn > 0) {
			$result += $this->overdraftCharge();
		}
		return $result;
   }
   private $_type; // AccountType
   private $_daysOverdrawn;
   // ...
}

А теперь рассмотрим такую ситуацию: нам нужно ввести несколько новых типов счетов со своими собственными правилами начислений за превышение срока кредита (овердрафт называется). Метод начисления у нас - это overdraftCharge. Его нужно перенести в класс AccountType.

Посмотрим, какие функции использует метод overdraftCharge, и решим, переносить ли нам один только метод или переносить всю группу. Заметим, что в нашем случае поле _daysOverdrawn (на сколько дней превышен срок кредита) должно остаться в классе Account, т.к. оно будет принимать разные значения у разных счетов.

Скопируем тело метода overdraftCharge в класс AccountType и подгоним его под требования нового класса:

class AccountType {
	// ...
	function overdraftCharge($daysOverdrawn) {
		if ($this->isPremium()) {
			$result = 10;
			if ($this->daysOverdrawn > 7) {
				$result += ($this->daysOverdrawn - 7) * 0.85;
			}
			return $result;
		}
		return $this->daysOverdrawn * 1.75;
	}
	// ...
}

Теперь заменим метод overdraftCharge в классе Account так, чтобы он использовал код, созданный нами в классе AccountType, т.е. сделаем делегирующий метод:

function overdraftCharge() {
	return $this->_type->overdraftCharge($this->_daysOverdrawn);
}

Можно оставить метод делегирующим, а можно удалить overdraftCharge из исходного класса Account. Но в этом случае конечно нужно заменить все соответствующие вызовы:

class Account {
	// ...
	function bankCharge() {
		$result = 4.5;
		if ($this->_daysOverdrawn > 0) {
			$result += $this->_type->overdraftCharge($this->_daysOverdrawn);
		}
		return $result;
	}
	// ...
}

После замены во всех точках вызовов мы можем смело удалять объявление метода overdraftCharge из класса Account.

Заметьте, что я передал в метод overdraftCharge параметр _daysOverdrawn. Я смог это сделать, но если бы внутри overdraftCharge использовался какой-нибудь метод из класса Account, то мне бы пришлось передавать в качестве параметра объект класса Account:

class AccountType {
	// ...
	function overdraftCharge($account) {
		if ($this->isPremium()) {
			$result = 10;
			if ($account->getDaysOverdrawn() > 7) {
				$result += ($account->getDaysOverdrawn() - 7) * 0.85;
			}
			return $result;
		}
		return $account->getDaysOverdrawn() * 1.75;
	}
	// ...
}

На этом позволю себе закончить :) В следующий раз поговорим о перемещении полей.





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




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