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 基于卷积神经网络的声纹识别
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 CSAPPattacklab
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图
  • ¥15 stm32开发clion时遇到的编译问题