【问题标题】:How to use local and instance variables to improve the application design of a program如何使用局部变量和实例变量来改进程序的应用程序设计
【发布时间】:2016-08-09 04:03:24
【问题描述】:

我了解局部变量仅限于声明它们的范围,只要对象存在,实例变量就存在。但是假设我有两个类:

个人信息.php

<?php

class PersonalInformation {

    private $name;
    private $surname;
    private $gender;
    private $birthday;

    function getName(){...}
    function setName(){...}

    //Other getter and setter

}

和 PersonalInformationController.php,它从表单中获取输入,创建 PersonalInformation 对象并设置其属性:

class PersonalInformationController {

    private $personalInformation;

    function __construct() {
        $this->personalInformation = new PersonalInformation();
    }

    function doPost() {
        $this->setPersonalDetails();
        $this->setResidence();
        $this->setContact();
    }

    private function setPersonalDetails() {

        $name = filter_input(INPUT_POST, "name");
        $surname = filter_input(INPUT_POST, "surname");
        $gender = filter_input(INPUT_POST, "gender");
        $birthday = filter_input(INPUT_POST, "birthday");
        $nationality = filter_input(INPUT_POST, "nationality");


        if (empty($name) || empty($surname)) {
            throw new RequiredFieldException("Name and surname can't be empty");
        } else if (!is_string($name) || !is_string($surname)) {
            throw new InvalidFieldException('Input must be a string!');         
        } else {
            $this->personalInformation->setName($name);
            $this->personalInformation->setSurname($surname);
        }

        if (!empty($gender) && is_string($gender)) {
            $this->personalInformation->setGender($gender);
        } else {
            throw new InvalidFieldException('Input must be a string!');       
       }

         if (!empty($birthday) && is_string($birthday)) {
        $this->personalInformation->setBirthday($birthday);
        }
        if (!empty($nationality) && is_string($nationality)) {
            $this->personalInformation->setNationality($nationality);
        }
    }

    private function setResidence() {
        $address = filter_input(INPUT_POST, "address");
        $zipCode = filter_input(INPUT_POST, "zipCode");
        $city = filter_input(INPUT_POST, "city");
        $nation = filter_input(INPUT_POST, "nation");

        if (!empty($address) && is_string($address)) {
            $this->personalInformation->setAddress($address);
        }

        //...//
    }

    private function setContact() { ... }
}

这个类有三个主要方法(setPersonalDetails() - setResidence() - setContact()),它们从表单中获取输入,在 html 页面中,将它们放入局部变量中(即 $name、$surname 等..) 并检查类型以便在 PersonalInformation 对象中设置它们。

我的问题是:“从代码设计(尤其是可扩展性和可读性)的角度来看,这些局部变量的使用或将它们声明为实例变量之间存在一些差异,以便将这三种方法仅用于检查这些变量的类型(而不是输入)?” .所以做这样的事情:

class PersonalInformationController {

    private $personalInformation;
    private $name;
    private $surname;
    private $gender;
    private $birthday;
    private $nationality;
    private $cellphone;
    //Other instance variables


    function __construct() {
        $this->personalInformation = new PersonalInformation();
    }

    function doPost() {
        $name = filter_input(INPUT_POST, "name");
        $surname = filter_input(INPUT_POST, "surname");
        $gender = filter_input(INPUT_POST, "gender");
        $birthday = filter_input(INPUT_POST, "birthday");
        $nationality = filter_input(INPUT_POST, "nationality");
        //...
        $address = filter_input(INPUT_POST, "address");
        $zipCode = filter_input(INPUT_POST, "zipCode");
        $city = filter_input(INPUT_POST, "city");
        $nation = filter_input(INPUT_POST, "nation");
    }

    private function setPersonalDetails() {
    // NOW THIS METHOD ONLY CHECKS THE TYPE OF THE INPUT
        if (empty($this->name) || empty($this->surname)) {
            throw new RequiredFieldException("Name and surname can't be empty");
        } else if (!is_string($this->name) || !is_string($this->surname)) {
            throw new InvalidFieldException('Input must be a string!');         
        } else {
            $this->personalInformation->setName($this->name);
            $this->personalInformation->setSurname($this->surname);
        }

        if (!empty($this->gender) && is_string($this->gender)) {
            $this->personalInformation->setGender($this->gender);
        } else {
            throw new InvalidFieldException('Input must be a string!');       
       }

         if (!empty($this->birthday) && is_string($this->birthday)) {
        $this->personalInformation->setBirthday($this->birthday);
        }
        if (!empty($this->nationality) && is_string($this->nationality)) {
            $this->personalInformation->setNationality($this->nationality);
        }
    }

   //setResidence() and setContact() are like the previous method.
}

【问题讨论】:

    标签: php instance-variables local-variables


    【解决方案1】:

    简答

    对我来说,这闻起来是premature optimization。像这样的优化最多会缩短几微秒;在 PHP 应用程序中,每个请求可能需要几百毫秒才能完成,这是不值得的。

    长答案

    我创建了一个简短的PhpBench 基准测试来测试它。测试脚本简单地计算了一个局部变量,一个类实例变量100万次:

    /**
     * @Revs(16)
     * @Iterations(1)
     */
    class VariableAccess
    {
      private $var = 0;
    
      public function benchLocalVar()
      {
        $var = 0;
        for ($i = 0; $i < 1000000; $i ++) {
          $var ++;
        }
      }
    
      public function benchInstanceVar()
      {
        $this->var = 0;
        for ($i = 0; $i < 1000000; $i ++) {
          $this->var ++;
        }
      }
    }
    

    结果如下:

    $ ./vendor/bin/phpbench run test.php --report=default
    PhpBench 0.11-dev (@git_sha@). Running benchmarks.
    
    \VariableAccess
    
        benchLocalVar                 I0 P0     [μ Mo]/r: 111,398.125 111,398.125 (μs)  [μSD μRSD]/r: 0.000μs 0.00%
        benchInstanceVar              I0 P0     [μ Mo]/r: 96,092.250 96,092.250 (μs)    [μSD μRSD]/r: 0.000μs 0.00%
    
    2 subjects, 2 iterations, 32 revs, 0 rejects
    (best [mean mode] worst) = 96,092.250 [103,745.188 103,745.188] 96,092.250 (μs)
    ⅀T: 207,490.375μs μSD/r 0.000μs μRSD/r: 0.000%
    suite: 1339fa11bf3f46fe629a65f838db6a37419e7cfe, date: 2016-04-17, stime: 16:20:01
    +----------------+------------------+--------+--------+------+------+-----+----------+---------------+---------+---------+
    | benchmark      | subject          | groups | params | revs | iter | rej | mem      | time          | z-value | diff    |
    +----------------+------------------+--------+--------+------+------+-----+----------+---------------+---------+---------+
    | VariableAccess | benchLocalVar    |        | []     | 16   | 0    | 0   | 273,240b | 111,398.125μs | 0.00σ   | +13.74% |
    | VariableAccess | benchInstanceVar |        | []     | 16   | 0    | 0   | 273,296b | 96,092.250μs  | 0.00σ   | 0.00%   |
    +----------------+------------------+--------+--------+------+------+-----+----------+---------------+---------+---------+
    

    在这个基准测试中,使用实例变量实际上比使用局部变量更快。但是,我们说的是 15 秒进行一百万次迭代!

    这个故事的寓意...

    不要让过早的优化影响您的程序设计。不要仅仅为了一些晦涩难懂的性能优化而牺牲可读性或可扩展设计,除非您确定您在特定代码段上遇到了性能瓶颈。

    【讨论】:

    • 谢谢。我可以写错标题吗?我的意思是如何改进我的应用程序的设计。不过你的回答对我很好,我从来没有考虑过你刚才说的话,所以我会记住的:)
    【解决方案2】:

    这个问题似乎更多地与应用程序设计相关,而不是性能。

    与其考虑局部变量与实例变量的性能,不如考虑每个变量的用途,并问问自己Does it belong here?。类可以变得非常大。最后,您只想存储尽可能多的信息。

    实例变量(也称为类“属性”

    实例变量是对象的属性(名称、生日、宽度、高度等)。它们要么描述一个对象,要么包含该对象必须知道的信息。不满足这些要求之一的变量应该是实例变量。相反,您可以将它们作为参数传递给您的方法。

    局部变量

    在面向对象的应用程序中,局部变量通常被限制在它们所在的函数(又名方法)的范围内。这些变量通常在函数完成后被销毁或不再使用执行。

    域对象

    您的应用程序包含一个名为PersonalInformation 的域对象。域对象只有一个目的:存储有关thingabstract thing 的信息(例如个人信息)。因此,它应该只包含属性,例如name, surname, birthday, ...,以及直接操作这些属性的方法,例如getter 和setter。

    域对象也是您应该放置验证逻辑的地方。如果您在多个控制器中使用PersonalInformation 对象,您会发现自己在许多文件中重写了相同的输入验证规则。这可以通过为对象的属性设置值,然后通过validate() 方法在内部验证它们来避免。

    通过这样做,您的验证逻辑可以从您的控制器中删除。

    控制器

    控制器旨在接受请求,告诉应用程序的其他部分处理 POST/DELETE/PUT 数据,然后呈现视图(例如 HTML 模板或 JSON 字符串)或重定向到应用程序的其他部分。

    大多数情况下,您的控制器不应处理域对象的验证逻辑。

    重构你的代码

    我已经重构了您的代码以满足这些原则,并添加了 cmets 来解释我所做更改的目的。

    个人信息控制器.php

    <?php
    
    class PersonalInformationController
    {
        /**
         * Though this controller interacts with one or more PersonalInformation objects, those objects
         * do not describe this controller. Nor do they provide any additional information that the
         * controller MUST know about.
         *
         * This variable should be local, not a class property.
         */
        //private $personalInformation;
    
        /**
         * Because $personalInformation is no longer a class property,
         * it does not need to be set in the constructor.
         */
        function __construct() {}
    
        function doPost()
        {
            /**
             * The try/catch block will catch any validation exceptions thrown by the validate()
             * method withing the $personalInformation object.
             */
            try {
                /**
                 * This instance of the PersonalInformation object is only being used within the scope of
                 * this function, so it should be a local variable.
                 *
                 * @var PersonalInformation personalInformation
                 */
                $personalInformation = new PersonalInformation();
                $personalInformation->setName(filter_input(INPUT_POST, "name"));
                $personalInformation->setSurname(filter_input(INPUT_POST, "surname"));
                $personalInformation->setGender(filter_input(INPUT_POST, "gender"));
                $personalInformation->setBirthday(filter_input(INPUT_POST, "birthday"));
                $personalInformation->setNationality(filter_input(INPUT_POST, "nationality"));
                /** Set other properties of personalInformation ... */
    
                /**
                 * This validate() method will check the integrity of the data you passed into the setter
                 * methods above. If any of the domain object's properties are invalid, an exception will be thrown.
                 */
                $personalInformation->validate();
    
                /** save the object / show view / other controller logic */
    
            } catch(RequiredFieldException $e) {
                /** A field was empty. Error handling logic here. */
            } catch(InvalidFieldException $e) {
                /** A field value was incorrect. Error handling logic here. */
            }
        }
    }
    

    PersonalInformation.php(域对象)

    /**
     * Class PersonalInformation
     * 
     * This class is a domain object. Notice that it only contains properties that describe PersonalInformation.
     * All of the logic contained in this class manipulates the properties, and is "unaware" of any outside entities.
     */
    class PersonalInformation {
    
        private $name;
        private $surname;
        private $gender;
        private $birthday;
        private $nationality;
    
        /**
         * Try to avoid duplicate code. If all the validation for your domain object is the same,
         * use a method, such as this, to store all of the validation logic.
         * 
         * @throws \InvalidFieldException
         * @throws \RequiredFieldException
         */
        public function validate() {
            if(empty($this->name) || empty($this->surname)) {
                throw new RequiredFieldException("Name and surname can't be empty");
            } else if(!is_string($this->name) || !is_string($this->surname)) {
                throw new InvalidFieldException('Name and surname must be a strings!');
            }
    
            if(empty($this->gender) || !is_string($this->gender)) {
                throw new InvalidFieldException('Gender must be a string!');
            }
    
            if(empty($this->birthday) || !is_string($this->birthday)) {
                throw new InvalidFieldException('Birthday must be a string!');
            }
    
            if(empty($this->nationality) || !is_string($this->nationality)) {
                throw new InvalidFieldException('Nationality must be a string!');
            }
    
            /** other validation rules **/
        }
    
        /**
         * @return mixed
         */
        public function getName()
        {
            return $this->name;
        }
    
        /**
         * @param mixed $name
         */
        public function setName($name)
        {
            $this->name = $name;
        }
    
        /**
         * @return mixed
         */
        public function getSurname()
        {
            return $this->surname;
        }
    
        /**
         * @param mixed $surname
         */
        public function setSurname($surname)
        {
            $this->surname = $surname;
        }
    
        /**
         * @return mixed
         */
        public function getGender()
        {
            return $this->gender;
        }
    
        /**
         * @param mixed $gender
         */
        public function setGender($gender)
        {
            $this->gender = $gender;
        }
    
        /**
         * @return mixed
         */
        public function getBirthday()
        {
            return $this->birthday;
        }
    
        /**
         * @param mixed $birthday
         */
        public function setBirthday($birthday)
        {
            $this->birthday = $birthday;
        }
    
        /**
         * @return mixed
         */
        public function getNationality()
        {
            return $this->nationality;
        }
    
        /**
         * @param mixed $nationality
         */
        public function setNationality($nationality)
        {
            $this->nationality = $nationality;
        }
    }
    

    请注意,应用程序现在使用较少的变量和函数。它只存储每个类所需的尽可能多的信息。因此,您的应用程序将体验到更好的性能并使用更少的内存。代码干净、简单、易于维护。

    希望这会有所帮助!

    【讨论】:

    • 谢谢@MParson,这是一个非常明确的答案。我很清楚,但是如果控制器应该检查每个字段的长度(如 strln($name) > 0 && strln($name)
    • 输入验证的目的是确保应用程序数据的完整性。在这种情况下,您的应用程序希望您的所有PersonalInformation 对象都包含一个name,该name 具有1 到30 个字符的字符串值。由于该验证要求与对象而非控制器相关联,因此它应位于域对象内部。
    • 在某些情况下,验证规则不属于域对象。例如,大多数用户注册表单都有passwordconfirm_password 字段。两个字段必须匹配,但您的 user 对象可能没有 confirm_password 字段。根据应用程序的设计,将逻辑放在控制器中可能是可以接受的,因为您将域对象属性 (password) 与特定于该控制器方法的一段数据 (`confirm_password') 进行比较形式。
    • 我明白了。非常感谢!
    • 虽然您的问题更多地与设计相关而不是性能,但我仍然强烈建议您也阅读@helmbert 的回答。过早的优化会导致不必要的设计约束和令人头疼的问题。专注于使用可扩展设计构建应用程序。然后确定是否需要优化。
    猜你喜欢
    • 2018-05-18
    • 2014-07-12
    • 1970-01-01
    • 2018-11-02
    • 2015-05-26
    • 1970-01-01
    • 2010-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多