douyan8266 2012-12-05 23:01
浏览 73
已采纳

构造函数重载

Normally when I want to create a class constructor that accepts different types of parameters, I'll use a kludgy overloading principle of not defining any args in the constructor definition: e.g. for an ECEF coordinate class constructor, I want it to accept either $x, $y and $z arguments, or to accept a single array argument containg x, y and z values, or to accept a single LatLong object I'd create a constructor looking something like:

function __construct()
    {
        //  Identify if any arguments have been passed to the constructor
        if (func_num_args() > 0) {
            $args = func_get_args();
            //  Identify the overload constructor required, based on the datatype of the first argument
            $argType = gettype($args[0]);
            switch($argType) {
                case 'array' :
                     //  Array of Cartesian co-ordinate values
                     $overloadConstructor = 'setCoordinatesFromArray';
                     break;
                case 'object' :
                     //  A LatLong object that needs converting to Cartesian co-ordinate values
                     $overloadConstructor = 'setCoordinatesFromLatLong';
                     break;
                default :
                     //  Individual Cartesian co-ordinate values
                     $overloadConstructor = 'setCoordinatesFromXYZ';
                     break;
            }
            //  Call the appropriate overload constructor
            call_user_func_array(array($this,$overloadConstructor),$args);
        }
    }   //  function __construct()

I'm looking at an alternative: to provide a straight constructor with $x, $y and $z as defined arguments, and to provide static methods of createECEFfromArray() and createECEFfromLatLong() that handle all the necessary extraction of x, y and z; then create a new ECEF object using the standard constructor, and return that

Which option is cleaner from an OO purists perspective?

  • 写回答

1条回答 默认 最新

  • dongpang4470 2012-12-08 12:51
    关注

    I've been thinking of the suggestions offered here and by others to clean up the overloading of the object constructor. The method I've decided upon is, I think, OO elegant, simple to implement and intuitive to use.

    As a solution, I decided to implement a common interface for the constructor arguments: I began by creating an interface.

    interface Geodetic_XyzFormat
    {
        public function getX();
        public function getY();
        public function getZ();
    }
    

    I added an abstract to implement the getters defined in the interface, together with setters and a few other methods that would be common across several child classes.

    abstract class Geodetic_ECEF_Coordinates implements Geodetic_XyzFormat
    {
        protected $_xCoordinate;
        protected $_yCoordinate;
        protected $_zCoordinate;
    
        protected function setX($xCoordinate)
        {
            $this->_xCoordinate = $xCoordinate;
        }
    
        public function getX()
        {
            return $this->_xCoordinate;
        }
    
        protected function setY($yCoordinate)
        {
            $this->_yCoordinate = $yCoordinate;
        }
    
        public function getY()
        {
            return $this->_yCoordinate;
        }
    
        protected function setZ($zCoordinate)
        {
            $this->_zCoordinate = $zCoordinate;
        }
    
        public function getZ()
        {
            return $this->_zCoordinate;
        }
    
        protected function setCoordinates($xDistance,
                                          $yDistance,
                                          $zDistance,
                                          $uom)
        {
            $this->setX(
                ($xDistance instanceof Geodetic_Distance) ? $xDistance : new Geodetic_Distance($xDistance, $uom)
            );
    
            $this->setY(
                ($yDistance instanceof Geodetic_Distance) ? $yDistance : new Geodetic_Distance($yDistance, $uom)
            );
    
            $this->setZ(
                ($zDistance instanceof Geodetic_Distance) ? $zDistance : new Geodetic_Distance($zDistance, $uom)
            );
        }
    
    }
    

    For my main class constructor, I type hinted for it to accept classes that extended the interface definition:

    class Geodetic_ECEF_TestClass
    {
        protected $_xCoordinate;
        protected $_yCoordinate;
        protected $_zCoordinate;
    
        public function __construct(Geodetic_XyzFormat $xyzCoordinates = NULL)
        {
            if (!is_null($xyzCoordinates)) {
                $this->_xCoordinate = $xyzCoordinates->getX();
                $this->_yCoordinate = $xyzCoordinates->getY();
                $this->_zCoordinate = $xyzCoordinates->getZ();
                return;
            }
    
            //    Defaults
              $this->_xCoordinate = new Geodetic_Distance();
               $this->_yCoordinate = new Geodetic_Distance();
               $this->_zCoordinate = new Geodetic_Distance();
        }
    }
    

    Finally, I created a couple of classes extending my abstract that would each handle the different options for the constructor arguments; in this case, an array of values, and individual values... I'll write the LatLong variant later, but it will use the same basic principles and extend Geodetic_ECEF_Coordinates in the same way.

    class Geodetic_ECEF_CoordinateArray extends Geodetic_ECEF_Coordinates
    {
        public function __construct(array $coordinates = NULL, $uom = Geodetic_Distance::METRES)
        {
            if (is_null($coordinates))
                throw new Geodetic_Exception('An array of vector coordinates must be passed');
            if (count($coordinates) == 3) {
                list ($xDistance, $yDistance, $zDistance) = array_values($coordinates);
            } else {
                throw new Geodetic_Exception('Invalid number of vectors in array');
            }
    
            $this->setCoordinates($xDistance, $yDistance, $zDistance, $uom);
        }
    
    }
    
    
    class Geodetic_ECEF_CoordinateValues extends Geodetic_ECEF_Coordinates
    {
        public function __construct($xDistance = NULL,
                                    $yDistance = NULL,
                                    $zDistance = NULL,
                                    $uom = Geodetic_Distance::METRES)
        {
            $this->setCoordinates($xDistance, $yDistance, $zDistance, $uom);
        }
    
    }
    

    So now, when I instantiate an ECEF object, I pass it the appropriate Geodetic_XyzFormat object:

    //    Nothing passed to constructor
    $dummyECEF1 = new Geodetic_ECEF_TestClass();
    var_dump($dummyECEF1);
    
    //    Array of values passed to constructor
    $dummyECEF2 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateArray(
            array(1.2, 3.4, 5.6)
        )
    );
    var_dump($dummyECEF2);
    
    //    Individual values passed to constructor
    $dummyECEF3 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateValues(7.8, 9.1, 2.3)
    );
    var_dump($dummyECEF3);
    
    //    Individual values passed to constructor (including a NULL, which should be treated as a 0)
    $dummyECEF4 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateValues(4.5, NULL, 6.7)
    );
    var_dump($dummyECEF4);
    
    $xDistance = new Geodetic_Distance(11.11, Geodetic_Distance::MILES);
    $yDistance = new Geodetic_Distance(22.22, Geodetic_Distance::MILES);
    $zDistance = new Geodetic_Distance(33.33, Geodetic_Distance::MILES);
    //    Array of distances passed to constructor
    $dummyECEF5 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateArray(
            array($xDistance, $yDistance, $zDistance)
        )
    );
    var_dump($dummyECEF5);
    
    $xDistance = new Geodetic_Distance(44.44, Geodetic_Distance::MILES);
    $yDistance = new Geodetic_Distance(55.55, Geodetic_Distance::MILES);
    $zDistance = new Geodetic_Distance(66.66, Geodetic_Distance::MILES);
    //    Individual distances passed to constructor
    $dummyECEF6 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateValues($xDistance, $yDistance, $zDistance)
    );
    var_dump($dummyECEF6);
    
    $xDistance = new Geodetic_Distance(11.11, Geodetic_Distance::MILES);
    $yDistance = new Geodetic_Distance(22.22, Geodetic_Distance::KILOMETRES);
    $zDistance = new Geodetic_Distance(33.33, Geodetic_Distance::MILES);
    //    Array of mixed values and distances passed to constructor
    $dummyECEF7 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateArray(
            array(11, $yDistance, 33), 
            Geodetic_Distance::MILES
        )
    );
    var_dump($dummyECEF7);
    
    $xDistance = new Geodetic_Distance(44.44, Geodetic_Distance::MILES);
    $yDistance = new Geodetic_Distance(55.55, Geodetic_Distance::KILOMETRES);
    $zDistance = new Geodetic_Distance(66.66, Geodetic_Distance::INCHES);
    //    Individual mixture of distances and values passed to constructor
    $dummyECEF8 = new Geodetic_ECEF_TestClass(
        new Geodetic_ECEF_CoordinateValues($xDistance, 55, $zDistance, Geodetic_Distance::NAUTICAL_MILES)
    );
    var_dump($dummyECEF8);
    

    It doesn't require all the kludgy tests for different argument types (whether in a factory, or in my main class), nor the use of statics (so it should be pretty easy writing unit tests for it)

    Thanks for everyone who offered suggestions and gave me food for thought

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题
  • ¥15 如何用Python爬取各高校教师公开的教育和工作经历
  • ¥15 TLE9879QXA40 电机驱动
  • ¥20 对于工程问题的非线性数学模型进行线性化
  • ¥15 Mirare PLUS 进行密钥认证?(详解)
  • ¥15 物体双站RCS和其组成阵列后的双站RCS关系验证
  • ¥20 想用ollama做一个自己的AI数据库
  • ¥15 关于qualoth编辑及缝合服装领子的问题解决方案探寻
  • ¥15 请问怎么才能复现这样的图呀