如何使用局部变量和实例变量来改进程序的应用程序设计

How to use local and instance variables to improve the application design of a program

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

PersonalInformation.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() { ... }
}

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

这是我的问题:"From point of view of code design(especially extensibility and readability), there exists some differences between the use of these local variables or declare them as instance variables in order to leave the three methods only for checking the type of these variables (and not take the input) ?"。所以做这样的事情:

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

简答

对我来说,这闻起来有 premature optimization 的味道。像这样的优化最多会减少几微秒;在每个请求可能需要几百毫秒才能完成的 PHP 应用程序中,这几乎不值得付出努力。

长答案

我创建了一个简短的 PhpBench 基准来测试它。测试脚本简单地把一个局部变量和一个class实例变量加起来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 秒进行一百万次迭代!

这个故事的寓意...

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

这个问题似乎与应用程序设计比性能更相关。

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

实例变量(也称为class"properties"

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

局部变量

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

域对象

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

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

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

控制器

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

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

重构您的代码

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

PersonalInformationController.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;
    }
}

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

希望对您有所帮助!