我经常看到(即在Slim框架内)单个函数访问器样式(如下面的[1]中)不赞成使用传统的Java-ish 2函数访问器(get / set) )(如下面[2]中所述)。 我个人更喜欢更少的代码行(在[1]中)和更少的输入(get / set),而不是像[3]中那样链接setter调用(我觉得很糟糕)。
我错过了什么吗?
class Test {
protected $body;
// [1] single function accessor
public function body($body = null)
{
if (!is_null($body))
$this->body=$body;
return $this->body;
}
// [2] dual function accessors
public function getBody()
{
return $this->body;
}
// [2] dual function accessors
public function setBody($body)
{
$this->body=$body;
//[3] ok, you could return $this for chaining
}
}
答案 0 :(得分:15)
他们不是个好主意。原因很简单:
他们有多重责任(设置和获取数据)。良好的职能有一个单一的责任,做得很好。
他们掩盖意图。您无法查看方法调用并了解将要发生的情况。
您认为body()
方法有什么作用?好吧,body
是一个名词。有一个方法(应该是一个动词)是一个名词是令人困惑的。
但是如果方法是age()
怎么办?年龄既是动词又是名词。所以当你看到$obj->age()
时,你是否告诉对象给你它的年龄?或者你是告诉对象要自己老化吗?
尽管$obj->getAge()
清楚地知道你正在尝试做什么。 $obj->getBody()
也同样明确。
它增加了方法的复杂性。
您的整体复杂性将保持不变(因为存在相同的逻辑),但它转移到更少的方法。这意味着你有一个更复杂的方法,而不是有两个简单的方法。
所以,是的,我避免它。
但是让我们退后一分钟。而不是询问问题"单功能访问器"这是一个坏习惯,让我们一般问一些关于访问者的问题......
我的回答是:是的。
它取决于对象的作用以及特定属性的作用。这是一个个案的基础。
有大量不同类型的对象(域对象,服务,代理,集合等)。有些是有状态的,有些则不是。
如果对象没有状态,则它没有属性,因此我们可以忽略它。
对于那些有状态的对象,他们为什么会有这种状态?
如果因为他们代表国家,那么国家应该公开(不是说财产应该是公开的,但国家应该暴露给外部世界)。因此,代表商业实体的域模型应该具有公共状态。
class User {
public $name;
}
但是如果对象的角色不代表状态,而是用它来做某事,那么就不应该暴露它。
class UserMapper {
protected $pdo;
public function __construct(Pdo $connection) {
$this->pdo = $connection;
}
public function findUserById($id) {
...
}
在映射器案例中,$pdo
是偶然状态。映射器的工作不是表示数据库连接的数据,而是需要它才能工作。
底线是从不公开对象不直接表示的状态。
仅仅因为你有财产,并不意味着你应该暴露它。事实上,你经常不应该暴露它。这称为Information Hiding。
但是有状态的对象类型呢?好吧,事实证明有两种基本类型的状态:
申请状态
思考配置等等。基本上表示在构建时未定义,但在运行时已知。
重要的是要注意,这个状态不应该在请求过程中发生变化。它也应该是合理相同的请求请求(除了部署等)。
用户状态
考虑导出或依赖于用户输入的状态和数据。一个明显的例子是从表单提交的数据。
但是,如果您对不同类型的表示使用不同的渲染器,那么一个不太明显的例子就是。因此,如果用户请求了JSON响应,则在属性中设置的JSON呈现代码将被视为用户状态。
应用程序状态的属性访问者不好。没有理由认为应用程序状态应该在中期改变,因此没有理由你应该传播状态。
用户状态 的属性访问者可能可以。但除此之外还有更多。
就你的例子而言:
public function setBody($body)
{
$this->body=$body;
}
您基本上将$body
作为公共财产。
并且没有错误。但为什么你需要一种方法呢?首先出现了什么问题:public $body;
?
有些人会说"属性是邪恶的,他们绝不能公开!"。那就是那种混蛋,因为那正是你对那个访问者的所作所为。
现在,如果访问者做了一些输入信息(通过类型提示)或其他验证逻辑(最小长度等),那么这是一个不同的故事......
或者是吗?
让我举个例子。
class Person {
public $name;
public $age;
}
vs
class StrictPerson {
protected $name;
protected $age;
public function setName($name) {
if (!is_string($name)) throw new BlahException();
if (strlen($name) < 10) throw new InvalidException();
$this->name = $name;
}
public function getName() {
return $this->name;
}
public function setAge($age) {
if (!is_int($age)) throw new ....
if ($age < 0 || $age > 150) throw new ...
$this->age = $age;
}
public function getAge() {
return $this->age;
}
}
现在,显而易见的是,这些属性始终有效。对?对?正确?
嗯,不。如果我创建了一个孩子会发生什么:
class LoosePerson extends StrictPerson {
public function setName($name) {
$this->name = $name;
}
public function setAge($age) {
$this->age = $age;
}
}
突然之间,我们所有的验证都消失了。现在你可以说这是有意的,而且是程序员的问题。或者您可以简单地将属性更改为private
,以保持它们始终有效。对?对?正确?
嗯,不。如果我这样做会发生什么:
$a = new StrictPerson;
$r = new ReflectionProperty($a, 'name');
$r->setAccessible(true);
$r->setValue($a, 'Bob');
我只是将一个无效值设置到一个应该始终验证的对象上。
只有当始终使用访问者时,才能使用访问者作为验证器。您使用的每个工具总是使用它们。像mysqli和PDO以及Doctrine和PHPUnit之类的东西直接设置属性而不是调用setter会导致大量问题。
相反,您可以使用外部验证器:
class PersonValidator {
public function validate(Person $person) {
if (!is_string($person->name)) {
throw new Blah...
}
if (strlen($person->name) < 10) {
throw new Blah...
}
if (!is_int($age)) throw new ....
if ($age < 0 || $age > 150) throw new ...
return true;
}
}
我认为是的,往往是一种坏习惯。
现在,在某些情况下你应该使用它们:
当必须在界面中表示该状态时
接口中无法指定属性。因此,如果必须表示对象在接口中公开状态,那么您应该使用getter和setter。
我强烈强烈建议您考虑创建界面的原因。对于简单的数据对象,通常更好地依赖核心对象类型(因为多态性不会因为数据对象上没有逻辑而受益)。
当您因某种原因需要隐藏内部表示时。
一个示例是表示unicode字符串的类。您可能有一个访问器方法来获取原始值。但是您希望这是一种方法,以便您可以将内部表示转换为正确字符集的字符串。
创建访问者时,不应创建属性访问者。您应该创建一个状态访问器。区别在于属性访问者必须始终返回属性或设置它。
另一方面,状态访问者可以“伪造”&#34;如果需要的话。因此,上面关于unicode字符串类的示例可以在内部将字符串表示为代码点数组。然后当你访问&#34;字符串&#34; state,它会将数组转换为实际的字符串。
如果您要使用访问者,则应该是抽象状态。不代表它。
属性访问者是一个坏习惯。如果您要这样做,请将属性设为公共并使用验证程序对象。
状态访问者不是一个坏习惯。它们对于保持有用的抽象非常有用。
答案 1 :(得分:1)
在构造函数中传递所有对象依赖项,只需让getters访问它们,大多数时候都不需要setter。
答案 2 :(得分:-6)
除了@Kleskowy和@marty答案之外,这样做的最佳解决方案如下:
class Test {
protected $body;
public function body()
{
$args=func_get_args();
if (count($args)==1) //simple setter
$this->body=$args[0];
elseif (count($args)) //setter for an array
$this->body=$args;
else //getter
return $this->body;
}
}
@ marty的想法最终以一种很好的方式创建了一个等同于PHP 5.4 []语法的数组(除了assoc数组)。但仍然兼容PHP 5到5.3 ;-) 然后你可以写:
$a->body("a"); //a value
$a->body(null); //set to null is possible
$a->body("this","is","an","array"); //convenient way to initialize $body as an array
$a->body(); //get the value