|< Free Open Study >|
3.3 The God Class Problem (Behavioral Form)
The behavioral form of the god class problem is caused by a common error among action-oriented developers in the process of moving to the object-oriented paradigm. These developers attempt to capture the central control mechanism so prevalent in the action-oriented paradigm within their object-oriented design. The result is the creation of a god object that performs most of the work, leaving minor details to a collection of trivial classes. There are a number of heuristics that work together cooperatively toward the avoidance of these classes.
Violations of these heuristics imply the creation of a behavioral god object. As Heuristic 3.2 states, watch out for classes with names like Driver, System, the BlahBlahManager, the BlahBlahSystem, the BlahBlahSubsystem, etc. Another symptom of this problem is the creation of many get and set functions in the public interfaces of your applications' classes. These get and set functions are called accessor methods. While it may be reasonable to walk up to an apple and ask for its weight, many such functions imply that some larger class is getting a great deal of data from other classes, performing computations on this data, and then setting the states of many objects to reflect the updated information. These classes are examples of behavioral god classes. Their design also violates the heuristic (heuristic 2.9) stating that related data and behavior should be kept in one place.
To further illustrate this point, I refer to a discussion that ensued on an Internet forum that normally deals with design patterns. This particular discussion revolved around the role of accessor methods and started with an entry that lamented the fact that a point class that has get_x(), set_x(), get_y(), and set_y() methods is giving away implementation details. Other entries answered that this is true, unfortunate, but necessary. I chimed in that, first of all, by definition methods cannot possibly give away implementation details. Are get_x() and get_y() simply returning data members, or are they multiplying a radius by the cosine or sine of some angle theta (i.e., is the point stored in rectangular or polar coordinates)? Who knows? The fact that a point can give you its position in rectangular coordinates is no reflection on the actual representation. Now, if you tell me that when the implementation of a point is modified you will modify the gets and sets in the public interface of the class, then I agree that your accessor methods are giving away implementation details. However, it is your convention, and not the accessor methods themselves, that is giving away the implementation details.
Second, accessor methods are not dangerous because they give away implementation details梩hey are dangerous because they indicate poor encapsulation of related data and behavior. Why is someone getting the x- and y-values of the point, what are they doing with them, and why isn't the point class doing the work for them? It is clear that the behavior working on the results of get_x() and get_y() is related to the point class, but it is not attached to that class. Why not? Often, the class whose method is using the point's accessor methods is a god class. It is capturing centralized control and requires data from the mindless point class. There are two reasonable explanations for the need for accessor methods. Either the class performing the gets and sets is implementing a policy between two or more classes, or it is in the interface portion of a system consisting of an object-oriented model and a user interface.
The first reasonable explanation can be best illustrated by looking at an example from a course-scheduling system domain. In this domain we have course objects, course offering objects, and student objects. The course class captures static information about the course objects (e.g., their number, description, duration, minimum and maximum numbers of students, list of prerequisites). The course offering class captures static and dynamic information related to a particular section of a given course (e.g., the course being offered, the room and schedule for the course, the instructor, the list of attendees). The student class captures information such as the student's name, social security number, and the list of courses he or she has taken.
As Figure 3.5 shows, one of the operations on the course offering is add_student(). The course offering is given a student and told to add it to the attendee list. The course offering would like to check that the student has fulfilled the necessary prerequisites. How does it perform this function? The problem is that the student knows which courses he or she has taken, but the course knows the prerequisites required. We now have a piece of policy information that requires the data of two classes to make a decision. At a minimum, we need to get the information from one class and give it to the other class. The course offering could ask the student for its list of courses and give them to the course, asking, "Is this course list sufficient to fulfill your prerequisites?" Or the course offering could ask the course for its list of prerequisites and give it to the student, asking, "Have you fulfilled the necessary prerequisites?" In either case, we need an accessor method on either the student class or the course class.
It is interesting to note that both of these designs are in direct violation of heuristics implied by Jacobson's Objectory methodology (i.e., an object-oriented analysis/design methodology) [3,4,5]. This methodology argues that policy information should not be placed inside of classes involved in the policy decision because it renders them unreusable by binding them to the domain that set the policy. The methodology's solution is either to have the course offering get both lists of courses and perform the check, or to create a PrerequisiteChecker class to do that operation. The PrerequisiteChecker class is a special type of class called a controller class (see Figure 3.6). By definition, such classes only contain behavior. They get their data from outside the class and are used to decouple classes from their policy. While it is true that controller classes do render their host classes more reusable, it is because those classes are mindless. What do a student and course do if we remove all policy information from them? Nothing but get and set operations. The host classes become clumps of mindless data with an interface of gets and sets while the controller classes possess all of the behavior. I view this type of design as the artificial separation of data and behavior into separate classes. The object-oriented paradigm strives to keep both data and behavior in a bidirectionally related package. It is true that there may exist classes possessing lots of meaningful, domain-independent behavior with a small amount of policy behavior that would lock them into one domain. In these cases, a controller class is clearly a useful facility; however, I believe these cases to be fairly rare outside of our second rationale for having accessor methods.
The second rationale for using accessor methods revolves around domains whose architecture involves an object-oriented model interacting with a user interface. By definition, user interfaces display the internals of a model, allow a user to update those internals, and put the internals back into the model. The heuristic here is that a model should be independent of its user interface. In order to accomplish this goal, the interface must be allowed to extract and replace details from the model via accessor methods. The use of the accessor methods should be restricted to classes within the interface portion of the code. It is important to note that the model classes of these types of systems rarely have any interesting behavior. Typical of information management-type systems, they revolve around a model of getting a record, chewing it up, and spitting it out in some new form. This is not to trivialize the development of information management systems. I have seen many very complex, difficult systems within these domains, but the complexity is in the area of data modeling and user interface design, not interesting behaviors between classes in the model.
For example, consider a course-grading system that maintains many courses each having a number of students, a number of assignments, etc. while each of the assignments has a grade for each student. The user of such a system clearly would like to view/change grades for assignments, add/delete/modify assignments with respect to courses, and add/delete students to/from courses. If we want to follow the heuristic of keeping data and behavior in one place, then it seems clear that we need to have the assignment, course, and student classes display their data on a screen and take values from the user to be used for modifying the states of the respective classes. Of course this would violate Heuristic 3.5, which is concerned with modifying or replacing the interface without affecting the model. In order to follow this heuristic, we need to have classes in the interface portion of the design getting and setting the states of objects in the model. This need for accessor methods is unfortunate, but no less necessary. Although the classes of the model will have accessor methods, the other classes in the model should not use them. That portion of a class's public interface should be reserved solely for the classes in the user interface portion of the design.
The literature is only beginning to reflect the inherent dangers of controller classes in object-oriented design. See the article by Rebecca Wirfs-Brock on the two very different designs for a brewery control system as an example .
|< Free Open Study >|