OOP design question
I'm relatively new using OOP in PHP. It's helped immensely in the organization and maintenance of my code, but I'd like to get better at designing my classes and using OOP as efficiently as I can. I've read the Gang of Four Design Patterns book, but still need some help. After building a few small apps, here's one thing I keep running across.
Let's say I'm building an application that keeps track of enrollment information for a school.
The way I would currently approach this is to have a class called student
, and methods within that class for CRUD on an individual student's record. It seems logical that I would have a constructor method for this class that took the student_id
as an argument, so I could reference it from within the object for all of those different CRUD operations.
But then, as I continue building the app, I run across situations where I need to run queries that return multiple students. For instance, something like, get_all_students_from_grade($grade)
, get_dropdown_of_all_students()
, etc. These methods don't apply to just one student, so it seems odd that I would 开发者_JAVA技巧have them as methods in my student
class, since I instantiated the object with one student_id
in mind. Obviously I can make it work this way, but it seems like I'm 'doing it wrong.' What is the best way to approach this?
Separate the student
class (which is a domain class) from the operations on it (the business logic or data access, depending on the case) like:
student
- the domain object contains only datastudent_service
orstudent_dao
(Data Access Object) - performs operations
This is sometimes considered as breaking the encapsulation, but it is an accepted best practice.
Here's more information on the matter. It provides more drawbacks from OOP point of view than the breaking of encapsulation. So even though it appears to be an accepted practice, it is not quite OOP.
Break it into two classes:
- student
- student_repository
Your student class knows nothing about how it is stored relationally.
$students = student_repository.get_all_students_from_grade($grade)
I don't pretend to know "the best" way, but it may help to approach the problem differently. Instead of making one class to represent an individual student, you could make the class represent a data interface between your app and the database.
This class would know how to retrieve a bunch (possibly one) of student rows from the db, cache them in a local array, allow the app to browse through the cached records, allow modifications on the cached records, and when done, write the cached modifications back to the db (by generating SQL to account for the changes).
This way, you avoid firing a single SQL stateement for each change (you still work with a set of rows instead) and at the same time, offer access to individual objects (by maintaining an index to the current location in the cache, and allow this "pointer" to be advanced by the app, as it calls methods of your class)
There is always a starting point. In your case it would be WHAT you are getting the students from (i.e. school, class, etc..).
$class = new Model_Class;
$students = $class->students;
foreach($students as $student)
{
print $student->name. ' is in class '. $class->name;
}
I've come accross this same problem, I'm guessing your using MySQL? this is one of the common OOP design challenges becuase SQL has a tendency to flatten everything.
I've solved this by doing the following
1.) make a class that has three forms of instantiation,
one where it's new
$myStudent = new $Student();
another where you know the id but need ids data
$myStudent = new $Student($student_id);
and another where you already have it's data in an associative array
$data = array('id'=13,'name' => 'studentname', 'major' => 'compsci');
$myStudent = new $Student($data['id'], $data);
This allows you to make a factory class that can run a query from mysql, get an associative array of the data and then create instances of student from that array data without hitting the database for each instance of student.
here is the constructor for such a class:
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);
}
You could go with the factory method and have the factory decide what the new student's ID should be.
Of course this factory would have to read a database to see where to start the index at (based upon how many students are in your database) and you'd have to decide on what to do about deleted students (if that's a possibility).
As for the methods you described, I don't see why you have to include those in your student class. You could but it should be a static method and not a member method.
Like Neil said in his comment (not sure why we didn't make it an answer), you should probably have Course
and School
classes as well. You could have school-specific methods (getting all students in a given grade, with a given number of days absent, etc) in School
and course-specific methods (all students in a course with a certain grade, etc) in the Course
class.
You are correct in thinking that in standard CRUD you don't want to give a class that represents an individual (i.e. the Student
class) the responsibility of loading multiples of itself. On the other hand, if you were going for more of an ActiveRecord style of data loading (which is what Ruby on Rails employs), then you would actually make all the Student
loader methods static methods on the Student
class itself. It just depends on how you want to style it, which depends in part on how complex your data model is going to become.
You should make them class methods on Student.
精彩评论