为什么单功能存取器似乎被视为坏习惯?

时间:2014-08-26 09:26:01

标签: php oop

我经常看到(即在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
    }
}

3 个答案:

答案 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

取决于州的类型:

但是有状态的对象类型呢?好吧,事实证明有两种基本类型的状态:

  1. 申请状态

    思考配置等等。基本上表示在构建时未定义,但在运行时已知。

    重要的是要注意,这个状态不应该在请求过程中发生变化。它也应该是合理相同的请求请求(除了部署等)。

  2. 用户状态

    考虑导出或依赖于用户输入的状态和数据。一个明显的例子是从表单提交的数据。

    但是,如果您对不同类型的表示使用不同的渲染器,那么一个不太明显的例子就是。因此,如果用户请求了JSON响应,则在属性中设置的JSON呈现代码将被视为用户状态。

  3. 我的断言:

    应用程序状态的属性访问者不好。没有理由认为应用程序状态应该在中期改变,因此没有理由你应该传播状态。

    用户状态 的属性访问者可能可以。但除此之外还有更多。

    取决于访问者的作用

    就你的例子而言:

    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,它会将数组转换为实际的字符串。

    对象应摘要

    如果您要使用访问者,则应该是抽象状态。不代表它。

    底线/ TLDR

    属性访问者是一个坏习惯。如果您要这样做,请将属性设为公共并使用验证程序对象。

    状态访问者不是一个坏习惯。它们对于保持有用的抽象非常有用。

答案 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