我知道局部变量仅限于声明它们的作用域,并且只要对象存在,实例变量就存在。但是假设我有两个类:
个人信息.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
的域对象。域对象只有一个用途:存储有关thing
或abstract 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;
}
}
请注意,应用程序现在使用的变量和函数更少。它只存储每个类中所需的尽可能多的信息。因此,您的应用程序将体验到更好的性能并使用更少的内存。代码干净、简单且易于维护。
希望这有帮助!