面向对象设计问题

6

我在使用PHP中的面向对象编程方面相对较新。它在组织和维护我的代码方面非常有帮助,但我想更好地设计我的类并尽可能高效地使用面向对象编程。我已经阅读了《设计模式》一书,但仍需要一些帮助。在构建了几个小应用程序后,我发现一个问题。

假设我正在构建一个跟踪学校招生信息的应用程序。

目前我会采用以下方式:创建一个名为student的类,并在该类中编写用于CRUD单个学生记录的方法。显然,我需要在这个类中的构造函数方法中以student_id作为参数,这样我就可以在对象内部引用它以进行所有不同的CRUD操作。

但是,当我继续构建应用程序时,我会遇到需要运行返回多个学生的查询的情况。例如,像get_all_students_from_grade($grade)get_dropdown_of_all_students()等。这些方法不仅适用于一个学生,因此似乎很奇怪我会将它们作为我的student类中的方法,因为我实例化对象时只考虑了一个student_id。显然,我可以这样做,但似乎我是“做错了”。有什么最佳方法来解决这个问题?


2
如果你有“学生”,你可能也想要“课程”和“学校”,这似乎是“get_students”方法的好位置。 - anon
8个回答

4

将其分为两个类:

  1. 学生(student)
  2. 学生仓库(student_repository)

你的学生类不知道它是如何在关系数据库中存储的。

$students = student_repository.get_all_students_from_grade($grade)

4
student 类(一个领域类)与对它的操作(根据情况是业务逻辑或数据访问)分开,如下所示:
  • student - 领域对象仅包含数据
  • student_servicestudent_dao(数据访问对象)- 执行操作
这有时被认为是破坏封装,但它是一种被接受的最佳实践。 这里 提供了更多相关信息。它提供了比破坏封装更多的 OOP 观点的缺点。因此,即使它看起来是一种被接受的实践,它也不完全符合 OOP。

这听起来是处理它的最简单方法。你能描述一下或提供一个链接,描述“域”类/对象的概念吗?我在谷歌上没有找到任何相关信息。 - Matthew
@Matthew:这在Martin Fowler的书(应用UML和模式)中有很好的描述,但你可以在这里得到一个想法: http://martinfowler.com/eaaCatalog/domainModel.html 以及这里 http://en.wikipedia.org/wiki/Domain_model - wj.
@Matthew:如果你想要针对SQL表的面向对象接口,我建议使用像Active Records等类似的东西。控制器类可以使用这些对象进行CRUD操作(它们还支持手动SQL查询)。 你的想法是正确的。我只是认为你不应该再重新发明轮子,但对于所有非数据库操作,你应该使用@Bozho所谈论的方法。另外,我有一个快速问题;为什么人们不更多地使用静态类方法来编写控制器类? - Kristinn Örn Sigurðsson

1

总有一个起点。在你的情况下,它将是你从哪里获取学生(例如学校、班级等)。

$class = new Model_Class;

$students = $class->students;

foreach($students as $student)
{
    print $student->name. ' is in class '. $class->name;
}

1
我曾遇到过相同的问题,我猜你在使用MySQL?这是面向对象设计中常见的挑战之一,因为SQL有将所有内容浓缩为一维的倾向。
我通过以下方式解决了这个问题:
1)创建一个具有三种实例化形式的类,
其中之一是创建新的实例;
$myStudent = new $Student();

另一个场景是你已知道ID,但需要获取该ID的数据。

$myStudent = new $Student($student_id);

还有一种情况是你已经有了一个关联数组存储了数据

$data = array('id'=13,'name' => 'studentname', 'major' => 'compsci');
$myStudent = new $Student($data['id'], $data);

这样可以让您创建一个工厂类,该类可以从mysql运行查询,获取数据的关联数组,然后从该数组数据创建学生实例,而无需为每个学生实例访问数据库。

以下是这种类的构造函数:

public function __construct($id=FALSE, $data=FALSE)
{
    if(!$id) $this->is_new = true;
    else if($id && !$data) $this->get_data_from_db($id);
    else if($id && $data) $this->set_data($data);

}

这是一个有趣的方法,但如果您总是知道要处理的类,那么使用该类不是更容易吗?如果我在代码中看到对象,除非找到实例化它的那一行,否则我无法确定它是哪种类型的学生对象。 - Matthew
我不确定我理解你的问题,上面的例子中它们都是相同类型的,哦,我忘记展示可选参数了,我会在我的解释中加上它。 - Fire Crow
我现在明白你的意思了。但是在这三个例子中,类被实例化为一个学生,而我遇到的问题是将对一个学生的CRUD活动与涉及多个学生的方法分开。 - Matthew
请仔细看一下我回答中的“这样可以让您创建一个工厂类,可以从mysql运行查询...”部分。 - Fire Crow

1
我并不自诩知道“最好”的方法,但采用不同的方式来解决问题可能会有所帮助。您可以将一个类用于表示一个学生,也可以将其用于表示应用程序和数据库之间的数据接口。
这个类将了解如何从数据库中检索一组(可能是一个)学生行,将它们缓存到本地数组中,允许应用程序浏览缓存记录,在缓存记录上进行修改,并在完成后将缓存的修改写回到数据库(通过生成SQL以解决更改)。
这样,您就可以避免为每个更改触发单个SQL语句(仍然使用一组行进行操作),同时,通过维护对缓存中当前位置的索引并允许应用程序调用您的类的方法来将此“指针”向前移动,提供对单个对象的访问。

0

你可以使用工厂方法,让工厂决定新学生的ID应该是什么。

当然,这个工厂必须读取数据库以查看从哪里开始索引(基于数据库中有多少学生),并且您必须决定如何处理已删除的学生(如果可能)。

至于您描述的方法,我不明白为什么您要将它们包含在学生类中。您可以这样做,但它应该是一个静态方法而不是成员方法。


0

就像尼尔在他的评论中所说(不确定为什么我们没有将其作为答案),您可能还应该有CourseSchool类。您可以在School中拥有特定于学校的方法(获取给定年级,缺席天数等的所有学生),并在Course类中拥有特定于课程的方法(具有某个成绩的课程中的所有学生等)。

您正确地认为,在标准CRUD中,您不希望给代表个人的类(即Student类)加载多个实例的责任。另一方面,如果您想要更多的ActiveRecord风格的数据加载(这是Ruby on Rails采用的方式),那么您实际上会将所有Student加载器方法作为Student类本身的静态方法。这取决于您想要如何设计它,这在一定程度上取决于您的数据模型将变得多么复杂。


-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接