Design Model ClassesIn this document we will discuss how to design model classes. We will use an example taken from Homework 2 and 3 from CS520 Fall 2011, and we will mimic Suze Orman's writing style in her book The Road to Wealth by explaining the design process with a series of questions and answers. Sample RequirementsSuppose we want to design model classes to add rubric support to CSNS. A rubric, according to Merriam-Webster, is "a guide listing specific criteria for grading or scoring academic papers, projects, or tests". For example, the department program assessment page lists five rubrics that are used to evaluate the students in several areas. Create new model classes and/or modify existing ones based on the following requirements:
Design
Q. How do we design the model classes? Class design usually takes two steps: identify the entity classes and their fields (or in the terminology of the Entity-Relationship Model, the entity sets and their attributes), and then add the associations between classes (or in the ER terminology, the relationships).
Q. So how do we identify the entity classes and their fields? Usually the nouns in the requirement description are your classes and fields. In our example, they are "rubrics", "rubric name", "rating scale", "criterion", "criterion name", "description", "ratings", "rubric score". Most of the time which noun should be a class and which one should be a field are pretty clear. For example, rubric, criterion, and rubric score are clearly classes because they contain other things, while name, description, ratings are clearly fields because they are of simple types (i.e string, number, or date). However, sometimes things are not that cut and dry. For example, rating scale in our example could be either a class or a field: Design A. Create a RatingScale class and add a RatingScale reference to the Rubric class. Design B. Create a minRating and a maxRating field in the Rubric class. Because minRating is always 1, we could actually drop it and only keep the maxRating field for rating scale. The advantage of Design A is that it's easier to ensure that a rubric only uses a valid rating scale (1-3, or 1-4, or 1-5) because on the OO side a Rubric object must reference an existing RatingScale object, and on the database side there's a foreign key constraint. For Design B, it's possible that a user sets the value of maxRating to something other than 3, 4, or 5. With that said, I actually prefer Design B for its simplicity. Design A creates one more class, one more table, and requires some additional code when creating a Rubric object. Design B is quite a bit simpler, and can still ensure the validity of the rating scales with a little care to the UI (e.g. let the user select a rating scale from a list instead of entering it in a text field) and a check() constraint in the database.
Q. What's the difference between a component class and an entity class? A component class is basically some fields from an entity class grouped into their own class. An entity class is mapped to a table in database, while a component class is mapped to some columns in the table of the entity class it is embedded to. A classical example of component class is the Address class we discussed during lecture. Personally I think component classes bring little benefit so don't use them.
Q. How do we decide the types of the class fields? There are a couple of decisions to made when you decide the type for a field:
The main difference between class types and primitive types is that class types allow null values, while primary types always have a default value. So for fields that could be null, use class types, and for fields that cannot be null, use primitive types (and add an annotation @Column(nullable=false) so you have a not-null constraint on the database side). Note that you should always use class type for Id fields because ORM uses whether id is null to determine whether an object is new. As for collections, the main difference between lists and sets is that lists keep the order of the elements while sets do not. You probably would want to use lists over sets most of the time because order is usually important, even just for display purpose. You may want to use a set if the order is really not important and you want fast element-in-collection check (HashSet lets you do element-in-collection check in O(1)). Between list and array, use list if the number of elements in the collection may change. Personally I always feel mapping a map to database is a little weird, so I'd recommend against using maps unless you feel the application demands it.
Q. So how do the classes look like after we complete the first step of the design? Here they are:
Note that we have some additional fields that are not in the requirement description. This is not uncommon because most of the time your clients can only give you a rough requirement description and you are supposed to fill in some missing pieces. In our example we added the Id fields because Hibernate/JPA wants them and it's a good practice to have an integer primary key column. The reason we chose Long instead of Integer is because Hibernate creates new ids from the same PosgreSQL sequence, so the id values could get quite large. We added the author field because we'll need the information to implement security measures, and a timestamp field is always useful for things like sorting the rubrics from the latest to the oldest and so on.
Q. How do we add the associations? The second step of the design process is to add the associations to the entity classes we identified earlier. This step involves several things:
Q. What are the associations in our example? Our example is a little unusual in the sense that not all entity classes are mentioned in the requirement description. Specifically, we need to add User, Assignment, and Submission class into the mix. As for the associations:
The associations involving RubricScore are a bit tricky. Note that a RubricScore includes the ratings for a particular student on a particular rubric of a particular assignment, which means that RubricScore should be associated with User, Rubric, and Assignment. Normally this would be the end of the story, but if you take a look at the Submission class, you'll see that Submission includes references to both student and assignment, and in fact it'd be a perfect place to store RubricScores because they are just like submission grades, so instead of associating RubricScore to User and Assignment separately, we can simply associate it with Submission. To summarize, here are the associations:
Q. What are the types of the associations?
You could make them many-to-many, which would mean the same criterion can be used in multiple rubrics - although it sounds like a reasonable design, it's actually not, because when a user edits a criterion, he or she may unknowingly change other rubrics that share the same criterion, and that's not good.
Q. So bi-directional or uni-directional? Although bi-directional association is usually more convenient, sometimes uni-directional association just feels more natural from a design perspective.
The User class is associated with many things. If you always use bi-directional association, the User class will become rather unwieldy.
When we display an assignment, we should show all the rubrics associated with it, but when we display a rubric, we probably don't need/want to display the assignments it is associated with, so a uni-directional association is enough.
RubricScore should definitely has a Rubric reference. Whether Rubric should keep a collection of RubricScore depends on whether we'll provide some function to calculate the score statistics for a rubric.
Q. So how do the classes look like?
|