最近我偶然发现了面向对象设计的问题。
关于背景的一点点:
我决定创建一个很好的旧todo-app来学习更多
PHPUnit和可测试的PHP一般。要求是平常的
检索/存储/编辑任务。待办事项应该存储在MySQL中
数据库中。
我的想法是创建一个基类Todo
,它包含一个PDO实例
以及todo的id和title(相当于他们的
数据库中的表示。)
此外,它应该有两个工厂方法负责 创建不同待办事项的实例。这些是:
这样做的原因是存储的不同行为 数据库中的当前状态(标题/描述)。
我没有子类的第一次尝试看起来像这样:
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语句,我引入了子类NewTodo
和SavedTodo
。
最终我得到了以下结论:
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 中实现这一点而不返回新实例?
也许更重要的是:
是否合理才能实现这种行为?
答案 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的更多信息。