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


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

我知道局部变量仅限于声明它们的作用域,并且只要对象存在,实例变量就存在。但是假设我有两个类:

个人信息.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等(中并检查类型以便在PersonInformation对象中设置它们。

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

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应用程序中,每个请求可能需要几百毫秒才能完成,这几乎不值得付出努力。

长答案

我创建了一个简短的 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 微秒进行一百万次迭代!

而这个故事的寓意...

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

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

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

实例变量(也称为类"属性"(

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

局部变量

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

域对象

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

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

通过执行此操作,可以从控制器中删除验证逻辑。

控制器

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

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

重构代码

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

个人信息控制者.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. */
        }
    }
}

个人信息.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;
    }
}

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

希望这有帮助!