开发者

PHP OOP paradigm private array

开发者 https://www.devze.com 2023-04-05 01:02 出处:网络
i\'m having some trouble with some part of the architecture of an API i am doing: I have a parent class - Vehicle

i'm having some trouble with some part of the architecture of an API i am doing:

I have a parent class - Vehicle

i have several classes that extend 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.

This works like a charm, i just have a question:

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._

0

精彩评论

暂无评论...
验证码 换一张
取 消