Saturday, October 31, 2009

ADD A CATEGORY









































Prev don't be afraid of buying books Next






























ADD A CATEGORY






Add support for a
movie to have a single category, from a fixed list of
alternatives.





To add support for categories through the
application we will want these tests:






Test 35. A
movie that hasn't explicitly been given a category should answer
that it is uncategorized when asked for its category.










Test 36. If a
movie is given a category when it is created, it answers that when
asked for its category.










Test 37.
Trying to create a movie with an invalid category (i.e., not from
the predefined set) throws an exception.











We begin by adding support for a category to
Movie. Notice that the fleshed out task description
mentions that the categories are from a fixed list. That means that
the category of a movie can't be just anything (like an arbitrary
string), but has to be picked from a fixed set of alternatives. So
let's pick an initial list to work with: Science Fiction, Drama, Comedy, Horror,
Western
, and Fantasy. We
will also need an Uncategorized
setting.





Test 35: A movie that hasn't
explicitly been given a category should answer that it is
uncategorized when asked for its category



We'll start with the last case, since it doesn't
require actually setting the category of a Movie:




public void testUncategorized() {
assertEquals("starWars should be uncategorized.",
"Uncategorized",
starWars.getCategory());
}






Getting to green bar by doing the simplest thing
results in:




public String getCategory() {
return "Uncategorized";
}








Test 36: If a movie is given a
category when it is created, it answers that when asked for its
category



Now, when we are testing for the other
categories, we need a way to set the category of a Movie.
The simplest thing is to add a constructor that takes a category
string. So here is the test:




public void testScienceFiction() {
Movie alien = new Movie("Alien", "Science Fiction");
assertEquals("alien should be Science Fiction.",
"ScienceFiction",
alien.getCategory());
}






We know we'll have to store the category value
and return it from getCategory(), so let's take a bigger
step and just do it. And here's the new code:




private String category = null;

public Movie(String aName, String aCategory) {
this(aName,
-
1);
category = aCategory;
}

public Movie(Movie original) {
name = original.name;
rating = original.rating;
category = original.category;
}

public String getCategory() {
return (category != null) ? category : "Uncategorized";
}







Green bar. The
constructors of Movie are proliferating, so let's make a
more general one and retrofit our tests to use it:




public Movie(String aName, String aCategory, int aRating) {
checkNull(aName);
checkEmpty(aName);
name = aName;
category = (aCategory != null) ? aCategory : "Uncategorized";
rating = aRating;
}







While we're at it we notice that some
refactoring can be applied to TestMovie. We do that as
well, removing unneeded instance creation from the tests and using
starWars that is created in setUp().





Exercises



















1.

Refactor TestMovie to use a single
Constructor Method that we just added to Movie.



2.

Refactor the tests again, this time removing the
unneeded Movie creation.













Test 37: Trying to create a movie with
an invalid category (i.e., not from the predefined set) throws an
exception



Now, we need to enforce the constraint that only
a fixed set of alternatives can be used:




public void testBadCategory() {
try {
Movie alien = new Movie("Alien", "SciFi",
-
1);
fail("Bad category accepted");
} catch (IllegalArgumentException ex)
{

}
}






So how do we constrain the category values to a
closed, fixed set of alternatives? We could use guard statements
and the string comparison:




public Movie(String aName, String aCategory, int aRating) {
checkNull(aName);
checkEmpty(aName);
checkCategory(aCategory);
name = aName;
category = (aCategory != null) ? aCategory : "Uncategorized";
rating = aRating;
}

private void checkCategory(String aCategory) {
if (aCategory == null) return;
if (aCategory.equals("Uncategorized")) return;
if (aCategory.equals("Science Fiction")) return;
if (aCategory.equals("Horror")) return;
throw new IllegalArgumentException("Bad category: " +aCategory);
}






This smells! Imagine changing the spelling of a
category once we have a large, complex system built. We could
refactor the string literals to constants and use them
everywhere:




public static final String UNCATEGORIZED = "Uncategorized";
public static final String SCIFI = "Science Fiction";
public static final String HORROR = "Horror";

public Movie(String aName, String aCategory, int aRating) {
checkNull(aName);
checkEmpty(aName);
checkCategory(aCategory);
name = aName;
category = (aCategory != null) ? aCategory : UNCATEGORIZED;
rating = aRating;
}

private void checkCategory(String aCategory) {
if (aCategory == null) return;
if (aCategory.equals(UNCATEGORIZED)) return;
if (aCategory.equals(SCIFI)) return;
if (aCategory.equals(HORROR)) return;
throw new IllegalArgumentException("Bad category: " +aCategory);
}






The corresponding version of the tests look
like:




public void testUncategorized() {
assertEquals("starWars should be uncategorized.",
Movie.UNCATEGORIZED,
starWars.getCategory());
}

public void testScienceFiction() {
Movie alien = new Movie("Alien",Movie.SCIFI,
-
1);
assertEquals("alien should be Science Fiction.",
Movie.SCIFI,
alien.getCategory());
}






This still has a smell. It would be nice to
refactor this in a way that would let us leverage Java to enforce
the constraints for us. We can do that by refactoring to a
type-safe enumeration. Joshua Kerievsky discusses this refactoring
in [29].



First, we create the type-safe enumeration
class:




public class Category {
private String name=null;

private Category(String categoryName) {
name = categoryName;
}

public static final Category UNCATEGORIZED = new Category("Uncategorized");
public static final Category SCIFI = new Category("Science Fiction");
public static final Category HORROR = new Category("Horror");
}







Next, we update the related tests. One
advantage of using a type-safe enumeration is that we no longer
need to test for bad values; the compiler enforces that for us.
Here are the remaining tests:




public void testUncategorized() {
assertEquals("starWars should be uncategorized.",
Category.UNCATEGORIZED,
starWars.getCategory());
}

public void testScienceFiction() {
Movie alien = new Movie("Alien",Category.SCIFI,
-
1);
assertEquals("alien should be Science Fiction.",
Category.SCIFI,
alien.getCategory());
}






And, finally, here are the affected bits of
Movie (note that the string constants have been
deleted):




private Category category = Category.UNCATEGORIZED;

public Movie(String aName, Category aCategory, int aRating) {
checkNull(aName);
checkEmpty(aName);
name = aName;
category = (aCategory != null) ? aCategory : Category.UNCATEGORIZED;
rating = aRating;
}

public Category getCategory() {
return category;
}






Now we can go back to the customer and get a
list of the categories that should be supported and add them to
Category.





Exercises












3.

The customer identified these categories: Science Fiction, Horror, Comedy, Western,
Drama, Fantasy, Kids, Adult, Mystery, Thriller
. Add these to
Category.























































Amazon






No comments:

Post a Comment