通过方法调用更改对象的类型,而不返回新实例

时间:2014-05-21 09:08:02

标签: php oop polymorphism

最近我偶然发现了面向对象设计的问题。

关于背景的一点点:
我决定创建一个很好的旧todo-app来学习更多 PHPUnit和可测试的PHP一般。要求是平常的 检索/存储/编辑任务。待办事项应该存储在MySQL中 数据库中。

我的想法是创建一个基类Todo,它包含一个PDO实例 以及todo的id和title(相当于他们的 数据库中的表示。)

此外,它应该有两个工厂方法负责 创建不同待办事项的实例。这些是:

  1. 使用应用程序前端定义的新待办事项
  2. 已经保存了待办事项。
  3. 这样做的原因是存储的不同行为 数据库中的当前状态(标题/描述)。

    我没有子类的第一次尝试看起来像这样:

    class Todo {
        [...] // fields and other methods
    
        public function save() {
            $statement = '';
            $parameters = [];
    
            if (is_null($this->_id)) {
                $statement = 'insert into todos (title) values(?)';
                $parameters = [$this->_title];
            } else {
                $statement = 'update todos set title = ? where id = ?';
                $parameters = [$this->_title, $this->_id];
            }
    
            $prepared_statement = $this->_pdo->prepare($statement);
            $result = $prepared_statement->execute($parameters);
    
            return $result;
        }
    }
    

    if语句显示我基本上只是检查了它的类型 待办事项。换句话说:todo是我想要的新东西 使用insert语句存储,或者已经是todo 数据库,以便我必须使用update-statement来处理它。

    为了摆脱if语句,我引入了子类NewTodoSavedTodo。 最终我得到了以下结论:

    abstract class Todo {
        [...] // fields and other methods
    
        public abstract function save();
    }
    
    class NewTodo extends Todo {
        public function save() {
            $statement = 'insert into todos (title) values(?)';
            $parameters = [$this->getTitle()];
    
            $prepared_statement = $this->getPdo()->prepare($statement);
            $result = $prepared_statement->execute($parameters);
    
            return $result;
        }
    }
    
    class SavedTodo extends Todo {
        public function save() {
            $statement = 'update todos set title = ? where id = ?';
    
            $parameters = [
                $this->getTitle(),
                $this->getId()
            ];
    
            $prepared_statement = $this->getPdo()->prepare($statement);
            $result = $prepared_statement->execute($parameters);
    
            return $result;
        }
    }
    

    虽然这对我来说似乎更合理,但我现在面对的是实际情况 这种方法存在问题。

      

    我 - 以一种天真的方式 - 期望一个NewTodo的实例调用   save()方法之后成为SavedTodo的实例。

    显然,在我的实施中并非如此。

    在这个“介绍之墙”之后,最后我的问题:

      

    有没有办法在PHP 中实现这一点而不返回新实例

    也许更重要的是:

      

    是否合理才能实现这种行为?

1 个答案:

答案 0 :(得分:0)

我不认为尝试通过其类型表示对象状态是个好主意。正如你所指出的,当你保存它时你会做什么,然后它应该是SavedTodo。即使你实现了它,它也将是一个奇怪的结构。例如:

$t = new NewTodo();
$t->content = 'testing';
$savedTodo = $t->save();
$t = $savedTodo; // need to overwrite the original Todo with the SavedTodo

正如您所看到的,如果您从外人的角度来看待代码,那么这不是您希望必须做的事情。

第一种情况下的if语句不是需要解决的问题。在OOP中,继承是一项功能,而不是一项要求,因此如果它不适合工作,请不要使用它。


OOP中一个好的经验法则是loosley遵循SRP (Single Responsibility Principle),这基本上是为了让你的课程没有做太多的明确角色。理想情况下,他们应该承担一项责任。

就个人而言,我不会将保存方法放在Todo上,因为它是一个实体,其职责是在系统中表示Todo。给Todo保存自己的能力可以被认为是另一个责任,所以我更希望有一个TodoMapper类来处理提取,插入,更新和删除。

此外,系统中的Todo实体不应该关心系统使用什么方法的数据存储,数据库的类型等等,甚至根本不存储。

例如:

$todoMapper = new TodoMapper($pdo);

$todo = new Todo();
$todo->content = 'testing';

$todoMapper->save($todo);

在这个系统中,映射器可以检查Todo是否有ID,并相应地进行UPDATE或INSERT。或者,如果您愿意,可以在mapper类上使用insert()update()方法。 这种设计的另一个好处是Todo不需要PDO对象,因为它不关心CRUD操作。

Martin Fowler提供了有关Data Mapper classes here的更多信息。