У меня есть ситуация, которая, несмотря на значительное количество поисков в Google / SO’ing, кажется, ускользает от меня. Вот проблема в двух словах:
PHP 5.3+ (но пока не конкретный — на самом деле я использую 5.5 / 6, но среда стремится быть гибкой).
Я пытаюсь написать надежную реализацию, которая заставит других разработчиков использовать лучшие практики.
У меня есть родительский класс:
class A {
public function doSomething() {
// does something
}
}
Стандартный вариант использования заключается в использовании A::doSomething
,
В особых случаях требуется расширение.
class B extends A {
public function doSomething() {
// does something .. AND
$this->doSomethingElse()
}
private function doSomethingElse() {
// does something else
}
}
Если я захочу сделай что-нибудь, тогда я могу A::doSomething
,
Если я захочу делать что-то и делать что-то еще, тогда я могу B::doSomething
,
Для того, чтобы соответствовать сфере компетенции принуждение других разработчиков к лучшей практике, Я реализовал интерфейс:
interface C {
public function doSomething() {
// does something else
}
public function doSomethingElse() {
// does something else
}
}
Класс B затем реализует интерфейс C. Звучит достаточно просто?
class B implements C {
public function doSomething() {
// does something .. AND
$this->doSomethingElse()
}
private function doSomethingElse() {
// does something else
}
}
Я не хочу предполагать, что ребенок B
инвентарь C
,
Там может быть ряд вполне обоснованных причин, чтобы продлить A
в E
, где E
не хочет реализовывать C
,
Если я сделаю следующее, и не реализую doSomething
Метод, следующий не имеет ошибок:
class B implements C {
private function doSomethingElse() {
// does something else
}
}
.. что и следовало ожидать; A
обеспечивает doSomething
за B
удовлетворить требование для C
,
Проблема в том, что это позволило бы кому-то написать неполный метод, который не смог бы выдавать ошибки. В приведенном выше случае B::doSomething
не звонит B::doSomethingElse
,
Возможно ли для метода, интерфейса (или аналогичного метода) требовать ребенок, чтобы реализовать метод на текущем уровне наследования?
Конечно, я могу писать заметки, документацию и т. Д., Но смысл интерфейса — заставить людей делать это правильно!
Другие люди, которые задавали подобные вопросы, были либо (иногда понятно) неправильно поняты или же стандартный ответ был «архитектура неверна» … Что я хотел бы оспорить, как показано в примере ниже:
Вот пример с реальными предметами:
Возможно, есть метод, называемый, скажем … A::attemptToBuy
C
будет применять методы, такие как ID требуется.
Beer
а также Vegetable
оба типа ShopItem.
Это стандартное и логичное наследование.
Beer
необходимо использовать BoozeInterface
(или что там уместно). Это также относится к еще не выполненному, но, возможно, будущему требованию Wine
, Spirit
и т. д.
Некоторые предметы идеально вписываются в общий ShopItem
учебный класс. Им не нужна дополнительная функциональность.
ShopItem::attemptToBuy
это нормальный вариант использования.
Однако — я должен положиться на кого-то, кто помнит, чтобы переопределить Wine::attemptToBuy
а также Spirit::attemptToBuy
,
Как видите — если я хочу действительно заблокируйте его, я бы в идеале мог принудительно переопределить текущий уровень наследования.
(Я уверен, что есть лучшие примеры этого, но я попытался сделать это как можно более очевидным).
Я счастлив, если ответ «нет, ты не можешь этого сделать» … Я просто хочу знать, если ты можешь. У меня есть код работает, но только через прямое переопределение, которое является принудительным. Но я хочу, чтобы это было принудительно!
Заранее спасибо.
стог
Почему бы не сделать класс A абстрактным и не использовать эти методы следующим образом:
abstract class A
{
abstract protected function doSomething() {}
abstract protected function doSomethingElse() {}
}
Теперь любой класс, который расширяет класс A, должен определять эти два метода.
Ура!
Я думаю, что вы идете в обратном направлении — я не уверен, что вы хотите применить переопределение (легко сделать с помощью абстрактных методов) в созданных клиентом-разработчиком подклассах, вы хотите предоставить метод, который не могу быть переопределенным в предоставленных вами абстрактных классах, чтобы гарантировать, что он делает то, что вы хотите?
Если вы предоставляете абстрактные классы ShopItem
а также BoozeItem
таким образом:
<?php
// base ShopItem with default `attemptToBuy` functionality
abstract class ShopItem {
public function attemptToBuy() {
echo "buying ShopItem";
}
}
// Booze sub-class, with an override on `attemptToBuy`
// (e.g. applying age restrictions check)
abstract class BoozeItem extends ShopItem {
final public function attemptToBuy() {
echo "buying BoozeItem";
}
}
?>
Клиент-разработчик может затем счастливо создать Beer
класс, расширяющий BoozeItem
суб-класс.
<?php
class Beer extends BoozeItem { }
$oBevvy = new Beer();
$oBevvy->attemptToBuy();
?>
Выходы: покупка BoozeItem
поскольку BoozeItem::attemptToBuy()
был объявлен final
это не может быть отменено Booze::attemptToBuy()
(попытка создать этот метод приведет к ошибке) — так что функциональность вашего объекта заблокирована.
Beer
может быть расширен до Lager
или же Ale
(например), и те унаследуют attemptToBuy()
от BoozeItem
(поскольку Beer
продолжается BoozeItem
) но им не разрешат переопределить этот метод, поскольку он объявлен final
— это просто будет наследоваться с определенным поведением. Это не то, что вы хотите, но это, вероятно, ближе всего, я думаю.