patternphpMinor
Multiple inheritance Mixin class
Viewed 0 times
inheritancemixinclassmultiple
Problem
I've written a simple class that somewhat simulates multiple inheritance using mixins. It allows you to extend the functionality of multiple classes and manage any conflicts between them, should they arise.
```
abstract class Vm_Mixer {
protected $methods = array();
protected $mixins = array();
protected $priorities = array();
/**
* @description By adding a mixin, the class will automatically adopt all of a mixin's methods.
* @param object $mixin - The instantiated object that's methods should be adopted by the extending class.
*/
public function addMixin($mixin){
$name = get_class($mixin);
$this->mixins[$name] = $mixin;
$methods = get_class_methods($name);
$this->methods[$name] = $methods;
}
/**
* @description Gets the class's current mixins by name
* @return An array of mixin names
*/
public function getMixins(){
return array_keys($this->methods);
}
/**
* @description Manages conflicts for the mixins.
* @param array $priorities - The method name as the key, the class name that has priority in a conflict as the value.
*/
public function setPriorities(array $priorities){
$this->priorities = $priorities;
}
/**
* @description Magic method that calls the mixin methods automatically
* @param string $methodName - The name of the mixin method
* @param array $arguments - The arguments for the method
*/
public function __call($methodName, $arguments){
foreach ($this->methods as $className=>$methods){
if (in_array($methodName, $methods)){
if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
call_user_func_array(array($className, $methodName), $arguments);
break;
} else if (!in_array($methodName, array_keys($this->priorities))){
Mixer class```
abstract class Vm_Mixer {
protected $methods = array();
protected $mixins = array();
protected $priorities = array();
/**
* @description By adding a mixin, the class will automatically adopt all of a mixin's methods.
* @param object $mixin - The instantiated object that's methods should be adopted by the extending class.
*/
public function addMixin($mixin){
$name = get_class($mixin);
$this->mixins[$name] = $mixin;
$methods = get_class_methods($name);
$this->methods[$name] = $methods;
}
/**
* @description Gets the class's current mixins by name
* @return An array of mixin names
*/
public function getMixins(){
return array_keys($this->methods);
}
/**
* @description Manages conflicts for the mixins.
* @param array $priorities - The method name as the key, the class name that has priority in a conflict as the value.
*/
public function setPriorities(array $priorities){
$this->priorities = $priorities;
}
/**
* @description Magic method that calls the mixin methods automatically
* @param string $methodName - The name of the mixin method
* @param array $arguments - The arguments for the method
*/
public function __call($methodName, $arguments){
foreach ($this->methods as $className=>$methods){
if (in_array($methodName, $methods)){
if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
call_user_func_array(array($className, $methodName), $arguments);
break;
} else if (!in_array($methodName, array_keys($this->priorities))){
Solution
It's an interesting approach and it works as expected. My first reaction when reading the code was that you are trying to solve a problem that's commonly solved with the Composite pattern, but then I realised you aren't actually trying to solve a problem but present an idea.
Now as for the specifics:
How much will performance be an issue with this implementation (I'm mainly talking about method overloading here)?
Unless you find a realistic scenario, you can't discuss performance. Commonly method overloading and the Reflection API are considered to be very slow, but performance is not about being slow, it's about comparing solutions. Unless we find a realistic use of your architecture and at least one alternative, performance doesn't matter.
Are there any other issues that I might run into that could cause conflicts or problems?
You are dealing with the Diamond Problem with the priorities scheme but you don't do anything for cases of equal priority. These might be outside of your scope, but the following snippet shows that relying on a priority list is extremely vulnerable:
At any point the priority list can be changed, and the last one will be the actual one. This kind of dynamic behaviour can lead to myriads of extremely hard to find bugs, you should consider throwing an exception if a priority was already set.
Furthermore you don't check for priority validity and you could do something like:
since you don't check if the priority class actually exists and set the priority anyway, which will render
which would throw your robot into an infinite loop. So you haven't actually dealt with every aspect of the Diamond Problem. You should also check if the class exists in
How can I improve the existing code?
Here:
I'd refactor like:
And probably you should somehow deal with Exceptions thrown from the called functions.
What are some ways that this could be used? I know a few, but other ideas would be nice as well.
When should and shouldn't something like this be used?
I have absolutely no clue. I've trained myself to forget all about multiple inheritance, since my programming roots were in C++. It would be nice if you tell us about your ideas that made you come up with the approach.
PHP 5.4 will support traits, a concept very similar to mixins. So, unfortunately your approach will soon become obsolete.
Now as for the specifics:
How much will performance be an issue with this implementation (I'm mainly talking about method overloading here)?
Unless you find a realistic scenario, you can't discuss performance. Commonly method overloading and the Reflection API are considered to be very slow, but performance is not about being slow, it's about comparing solutions. Unless we find a realistic use of your architecture and at least one alternative, performance doesn't matter.
Are there any other issues that I might run into that could cause conflicts or problems?
You are dealing with the Diamond Problem with the priorities scheme but you don't do anything for cases of equal priority. These might be outside of your scope, but the following snippet shows that relying on a priority list is extremely vulnerable:
$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));
$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));At any point the priority list can be changed, and the last one will be the actual one. This kind of dynamic behaviour can lead to myriads of extremely hard to find bugs, you should consider throwing an exception if a priority was already set.
Furthermore you don't check for priority validity and you could do something like:
$robot->setPriorities(array('explode'=>'aaaa'));since you don't check if the priority class actually exists and set the priority anyway, which will render
explode() uncallable. And of course you could do something evil like:$robot->setPriorities(array('explode'=>'Robot'));which would throw your robot into an infinite loop. So you haven't actually dealt with every aspect of the Diamond Problem. You should also check if the class exists in
addMixin().How can I improve the existing code?
Here:
public function __call($methodName, $arguments){
foreach ($this->methods as $className=>$methods){
if (in_array($methodName, $methods)){
if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
call_user_func_array(array($className, $methodName), $arguments);
break;
} else if (!in_array($methodName, array_keys($this->priorities))){
call_user_func_array(array($className, $methodName), $arguments);
break;
}
}
}
}- it would be a lot nicer if you returned the value of the "Mixin" class instead of just calling it and breaking,
- you should deal with the case of a non existing function,
- the block is kind of unreadable.
I'd refactor like:
public function __call($methodName, $arguments){
foreach ($this->methods as $className => $methods) {
if(!in_array($methodName, $methods)) {
continue;
}
if(
in_array($methodName, array_keys($this->priorities))
&& $className == $this->priorities[$methodName]
) {
return call_user_func_array(array($className, $methodName), $arguments);
} else if (!in_array($methodName, array_keys($this->priorities))) {
return call_user_func_array(array($className, $methodName), $arguments);
}
}
throw new Exception("Function not found"); // or something like that
}And probably you should somehow deal with Exceptions thrown from the called functions.
What are some ways that this could be used? I know a few, but other ideas would be nice as well.
When should and shouldn't something like this be used?
I have absolutely no clue. I've trained myself to forget all about multiple inheritance, since my programming roots were in C++. It would be nice if you tell us about your ideas that made you come up with the approach.
PHP 5.4 will support traits, a concept very similar to mixins. So, unfortunately your approach will soon become obsolete.
Code Snippets
$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));
$robot->setPriorities(array('explode'=>'Defense'));
$robot->setPriorities(array('explode'=>'Weapons'));$robot->setPriorities(array('explode'=>'aaaa'));$robot->setPriorities(array('explode'=>'Robot'));public function __call($methodName, $arguments){
foreach ($this->methods as $className=>$methods){
if (in_array($methodName, $methods)){
if ((in_array($methodName, array_keys($this->priorities)))&&($className == $this->priorities[$methodName])){
call_user_func_array(array($className, $methodName), $arguments);
break;
} else if (!in_array($methodName, array_keys($this->priorities))){
call_user_func_array(array($className, $methodName), $arguments);
break;
}
}
}
}public function __call($methodName, $arguments){
foreach ($this->methods as $className => $methods) {
if(!in_array($methodName, $methods)) {
continue;
}
if(
in_array($methodName, array_keys($this->priorities))
&& $className == $this->priorities[$methodName]
) {
return call_user_func_array(array($className, $methodName), $arguments);
} else if (!in_array($methodName, array_keys($this->priorities))) {
return call_user_func_array(array($className, $methodName), $arguments);
}
}
throw new Exception("Function not found"); // or something like that
}Context
StackExchange Code Review Q#6306, answer score: 4
Revisions (0)
No revisions yet.