i'm having some trouble with some part of the architecture of an API i am doing:
I have a parent class - Vehicle
Vehicle
: Car
, Truck
, Bicicle
, Motorcycle
I want some of the classes to have a property, doors
, that is an array of Door objects.
it's going to imply a private var $doors
, and the methods getDoors()
, getDoor($id)
, addDoor(Door $door)
, removeDoor($id)
As many of the classes won't have doors
, i don't want to implement this in the Vehicle
Class, but i don't want to repeat myself in all the classes that have doors
.
So I thought using the magic method __call
in Vehicle
and a helper class Statics
like this:
class Vehicle {
...
public function __call($method, $args){
if(!Statics::$method($this, $args)){
throw new Exception('class ' . $this->get_class() . ' does not implement method ' . $method);
}
}
}
class Car extends Vehicle {
...
public $doors = array();
}
class Motorcycle extends Vehicle {
...
}
class Statics {
function addDoor($vehicle, $args){
try{
array_push($ve开发者_如何转开发hicle->doors, $args[0]);
} catch {
return false;
}
}
}
In this way, if we try the addDoor
method in the a Motorcycle object, the exception will be thrown.
How can I turn the #doors array accessible to the Statics
class, but totally private to the programmer?
Your on the correct way. You've found an issue where the standard inheritance model doesn't work (unless you had multiple inheritance support). The correct answer in this case is to go to a composite model.
Instead of using a Statics:: class, you should simply create a 'doors' object. The problem with this Static:: approach is that you cannot subclass it and override features, if needed. If you turned 'Doors' into an object, and create it upon instantiation of any Vehicle that requires it, you retain the flexibility OOP provides.
This is a very abstract problem though, which you've probably intended. For the actual design problem you're facing the design may be more obvious.
The simplest solution is to move all the static functions of the Statics
class into the abstract Vehicle
class and change them to protected
. Also, change all the private members into protected.
class Vehicle {
...
protected function addDoor($args){
try{
array_push($this->doors, $args[0]);
} catch {
return false;
}
}
...
}
class Car {
...
protected $doors = array();
...
}
class Motorcycle extends Vehicle {
...
}
It is kind of inverted logic here, because the abstract base class has knowledge of the internals of its subclasses, but strangely it works in PHP. The correct OOP way would be to create protected abstract methods to access these members.
abstract class Vehicle {
...
protected abstract function &GetDoors();
protected function addDoor($args){
try{
array_push($this->GetDoors(), $args[0]);
} catch {
return false;
}
}
...
}
class Car {
...
protected function &GetDoors(){ return $this->doors; }
...
}
class Motorcycle extends Vehicle {
...
protected function &GetDoors(){ throw new Exception(); }
...
}
Yet, you you are trying to achieve (not repeating the code) can be implemented directly by using Traits, the new feature of PHP 5.4. Maybe you should wait a little bit...
https://wiki.php.net/rfc/horizontalreuse
instead of using __call, you can create a new class VehicleWithDoor which extends Vehicle, and contains door methods you wish to implement :
class VehicleWithDoor extends Vehicle {
protected $_doors = array();
// door methods
}
class Car extends VehicleWithDoor {
}
-----------------------------------------
EDIT 2 (BETTER SOLUTION i hope) :
u can add and implements some another interface to your properties
interface Vehicle_Property {
public function getIndex();
}
class Vehicle_Property_Doors implements Vehicle_Property {
protected $_index = "doors";
public function getIndex()
{
return (string)$this->_index;
}
public function open()
{
}
public function close()
{
}
}
class Vehicle_Property_Wings {
protected $_index = "wings";
public function getIndex()
{
return (string)$this->_index;
}
public function fly()
{
}
public function backToTheFuture()
{
}
}
class Vehicle {
protected $_properties = array();
public function addProperty(Vehicle_Property $property)
{
$this->_properties[$property->getIndex()] = $property;
}
public function removeProperty($key)
{
if (!array_key_exists($key, $this->_properties)
{
return false;
}
return unset($this->_properties[$key]);
}
public function getProperty($key)
{
if (!array_key_exists($key, $this->_properties)
{
return false;
}
return $this->_properties[$key];
}
protected function getProperties()
{
return (array)$this->_properties;
}
}
The previous solutions like using an interface, restricting "doors" to a subclasss, or a composite class are ok.
Sometimes, when defining a class hierarchy, you may find a feature (method or property) that maybe implemented, or may not be implemented, in descendant classes. My proposed solution is, add that feature in the base class as an "abstract or virtual feature" and let each class to decide to override or not.
// check that in base classes,
// is common to have most stuff private or protected, not public
class Vehicle {
...
// using a protected variable field for properties
protected $_doors = array();
protected $_wings = array();
// you may want to use the methods or the "__call" way,
// Important, these are intentionally "protected", not "public"
protected /* array */ getDoors()
{
return $this->_doors;
} // /* array */ getDoors(...)
protected /* array */ setDoors(/* array */ p_doors)
{
$this->_doors = p_doors;
} // /* array */ SetDoors(...)
protected /* void */ function addDoor(/* array */ $args)
{
array_push($this->doors, $args[0]);
} // /* void */ function addDoor(...)
// you may want to use the methods or the "__call" way,
// Important, these are intentionally "protected", not "public"
protected /* array */ getWings()
{
return $this->_wings;
} // /* array */ getWings(...)
protected /* array */ setWings(/* array */ p_wings)
{
$this->_wings = p_wings;
} // /* array */ SetWings(...)
protected /* void */ function addWing(/* array */ $args)
{
array_push($this->wings, $args[0]);
} // /* void */ function addWing(...)
// these one is always public in all classes
public /* bool */ function supportsDoors()
{
return false;
}
// these one is always public in all classes
public /* bool */ function supportsWings()
{
return false;
}
} // class Vehicle
class Car extends Vehicle {
// these one is always public in all classes
public /* bool */ function supportsDoors()
{
return true;
}
public /* array */ getDoors()
{
return $this->_doors;
} // /* array */ getDoors(...)
// promoted from "protected" to "public"
public /* array */ setDoors(/* array */ p_doors)
{
$this->_doors = p_doors;
} // /* array */ SetDoors(...)
// promoted from "protected" to "public"
public /* void */ function addDoor(/* array */ $args)
{
array_push($this->doors, $args[0]);
} // /* void */ function addDoor(...)
} // class Car
class JetPack extends Vehicle {
// these one is always public in all classes
public /* bool */ function supportsWings()
{
return true;
}
// promoted from "protected" to "public"
public /* array */ getWings()
{
return $this->_wings;
} // /* array */ getWings(...)
// promoted from "protected" to "public"
public /* array */ setWings(/* array */ p_wings)
{
$this->_wings = p_wings;
} // /* array */ SetWings(...)
public /* void */ function addWing(/* array */ $args)
{
array_push($this->wings, $args[0]);
} // /* void */ function addWing(...)
} // class JetPack
class Boeing extends Vehicle {
// these one is always public in all classes
public /* bool */ function supportsDoors()
{
return true;
}
// these one is always public in all classes
public /* bool */ function supportsWings()
{
return true;
}
public /* array */ getDoors()
{
return $this->_doors;
} // /* array */ getDoors(...)
// promoted from "protected" to "public"
public /* array */ setDoors(/* array */ p_doors)
{
$this->_doors = p_doors;
} // /* array */ SetDoors(...)
// promoted from "protected" to "public"
public /* void */ function addDoor(/* array */ $args)
{
array_push($this->doors, $args[0]);
} // /* void */ function addDoor(...)
// promoted from "protected" to "public"
public /* array */ getWings()
{
return $this->_wings;
} // /* array */ getWings(...)
// promoted from "protected" to "public"
public /* array */ setWings(/* array */ p_wings)
{
$this->_wings = p_wings;
} // /* array */ SetWings(...)
public /* void */ function addWing(/* array */ $args)
{
array_push($this->wings, $args[0]);
} // /* void */ function addWing(...)
} // class JetPack
Resume: "Doors" & "Wings" are declared as a "protected virtual" in the base class, and hence, all descendant classes have it as protected, but only some classes implement that feature, and promote to public the features, wheter methods, or properties.
As an additional remark, I personally dislike using the "quick & dirty virtual" properties and methods, way of PHP, and instead, using explicit "getMyProperty" & "setMyProperty" methods, or "myMethod()", because are a best practice. I suggest to avoid using these common "_call" feature._
精彩评论