Reputation: 800
I have a situation that, despite a fair amount of googling/SO'ing, seems to escape me. Here's the issue in a nutshell:
PHP 5.3+ (but not specific yet - I'm actually running 5.5/6 but the environment is aiming to be flexible).
I'm attempting to write a solid implementation that will force other developers into best practice.
I have a parent class:
class A {
public function doSomething() {
// does something
}
}
The standard use case is to use A::doSomething
.
In special cases, an extension is required.
class B extends A {
public function doSomething() {
// does something .. AND
$this->doSomethingElse()
}
private function doSomethingElse() {
// does something else
}
}
If I want to do something, then I can A::doSomething
.
If I want to do something and do something else, then I can B::doSomething
.
In order to fit the remit of forc[ing] other developers into best practice, I have implemented an interface:
interface C {
public function doSomething() {
// does something else
}
public function doSomethingElse() {
// does something else
}
}
Class B then implements Interface C. Sounds simple enough?
class B implements C {
public function doSomething() {
// does something .. AND
$this->doSomethingElse()
}
private function doSomethingElse() {
// does something else
}
}
I don't want to assume that the child B
implements C
.
There could be a number of completely valid reasons to extend A
to E
, where E
does not want to implement C
.
If I do the following, and don't implement the doSomething
method, the following has no errors:
class B implements C {
private function doSomethingElse() {
// does something else
}
}
.. which is as you'd expect; A
provides doSomething
for B
to satisfy the requirement for C
.
The issue is that this would allow someone to write an incomplete method that would fail to throw errors. In the above case B::doSomething
does not call B::doSomethingElse
.
Is it possible for a method, interface (or similar technique) to require a child to implement a method at the current inheritance level?
Of course I can write notes, documentation etc... but the point of the interface is to make people do it correctly!
Other people who've asked similar questions were either (sometimes understandibly) misunderstood or the stock answer was "the architecture is wrong"... Which I'd like to challenge as the example below illustrates:
Here's an example with real world items:
A might have a method called, say... A::attemptToBuy
C
would enforce methods like ID required.
Beer
and Vegetable
are both a type of ShopItem.
This is a standard and logical inheritance.
Beer
needs to use BoozeInterface
(or whatever is appropriate). This would also be true of the not yet implement, but possibly future requirement of Wine
, Spirit
etc etc.
Some items fit perfectly in to a generic ShopItem
class. They need no extra functionality.
ShopItem::attemptToBuy
is a normal use case.
However - I have to rely on someone remembering to override the Wine::attemptToBuy
and Spirit::attemptToBuy
.
As you can see - if I want to really lock it down, I'd ideally be able to force an override at the current level of inheritance.
(I'm sure there are better examples of this, but I've tried to make it as obvious as possible).
I'm happy if the answer is "no, you can't do that"... I just want to know if you can. I have the code working, but just via a direct override that is unforced. But I want it to be forced!
Thanks in advance. Rick
Upvotes: 4
Views: 4504
Reputation: 8472
I think you're going about it backwards - I'm not sure you want to enforce an override (easily done with abstract methods) in the client-developer created sub-classes, you want to provide a method that cannot be overridden in your provided abstract classes to ensure it does what you want it to?
If you provide the abstract classes ShopItem
and BoozeItem
thus:
<?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";
}
}
?>
The client developer can then happily create their Beer
class extending the BoozeItem
sub-class.
<?php
class Beer extends BoozeItem { }
$oBevvy = new Beer();
$oBevvy->attemptToBuy();
?>
Outputs: buying BoozeItem
Since BoozeItem::attemptToBuy()
has been declared final
it can't be overridden by Booze::attemptToBuy()
(trying to create that method will cause an error) - so your object functionality is locked down.
Beer
can be extended to Lager
or Ale
(for instance) and those will inherit attemptToBuy()
from BoozeItem
(since Beer
extends BoozeItem
) but they won't be allowed to override that method as it's declared final
- it'll simply be inherited with the defined behavior. This isn't what you want but it's probably the closest you'll get I think.
Upvotes: 0
Reputation: 1137
Why not make class A abstract and require those methods that way:
abstract class A
{
abstract protected function doSomething() {}
abstract protected function doSomethingElse() {}
}
Now any class that extends class A must define those two methods.
Cheers!
Upvotes: 2