What I wanted to create was a request object that's immutable once it's created. I have plans to use this request as a key to retrieve cached response, so it would be messy if someone was allowed to modify the attributes of the original request. So I've decided to have no setter methods.
I could create a constructor with all the parameters, but that would mean that most of the time the developer may have to create a SearchRequest object with lots of nulls. It's also very easy to misplace the arguments. If argument 1 is the rating and argument 2 is the offset, and they're both integers, I can imagine someone accidentally putting the value for offset in the ratings position and the value for ratings in the offset position.
The Builder pattern can be helpful in my situation. So I've prototyped the class here. Let's take a look at how I use this class first. Here's an example of a SearchRequest that query for high rating sports TV shows and episodes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SearchRequest request = new SearchRequest.SearchRequestBuilder() | |
.ofType(SearchRequest.MediaType.TV_SERIES) | |
.ofType(SearchRequest.MediaType.TV_EPISODE) | |
.inCategory(SearchRequest.MediaCategory.SPORTS) | |
.withRatingsHigherThan(4) | |
.toSearchRequest(); | |
System.out.println(request.getURL()); |
Running this code generates the following print:
03-01 20:00:59.849 1321-1321/com.example.app I/System.out﹕ http://my.server.com/search.json?offset=0&max_result=100&rating=4&categories=SPORTS&types=TV_EPISODE,TV_SERIES
I've restricted the creation of the SearchRequest object directly by making the constructor private. Instead the developer need to create a new SearchRequestBuilder. The builder then has small methods that are clearly named to add attributes. Once you have composed the request you want, you call the toSearchRequest method and you get the SearchRequest object.
And here's the source code for SearchRequest. I've seen variations of this pattern, but I've implemented it to satisfy my own requirements. I'd love to hear how you've used this pattern in the course of your career and how it differs from my interpretation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.app; | |
import java.util.HashSet; | |
import java.util.Set; | |
/** | |
* SearchRequest is an object to help create a valid REST URL for searching TV Shows and movies... | |
*/ | |
public class SearchRequest { | |
public static enum MediaType { | |
VIDEO_CLIP, | |
TV_SERIES, | |
TV_EPISODE, | |
MOVIE | |
} | |
public static enum MediaCategory { | |
NEWS, | |
SPORTS, | |
KIDS | |
} | |
public static class SearchRequestBuilder { | |
private SearchRequest request; | |
public SearchRequestBuilder() { | |
request = new SearchRequest(); | |
} | |
public SearchRequestBuilder ofType(SearchRequest.MediaType mediaType) { | |
request.mTypes.add(mediaType); | |
return this; | |
} | |
public SearchRequestBuilder inRange(int offset, int maxResult) { | |
request.mOffset = offset; | |
request.mMaxResult = maxResult; | |
return this; | |
} | |
public SearchRequestBuilder matchingTitle(String title) { | |
request.mTitleQuery = title; | |
return this; | |
} | |
public SearchRequestBuilder inCategory(SearchRequest.MediaCategory categories) { | |
request.mCategory.add(categories); | |
return this; | |
} | |
public SearchRequestBuilder withRatingsHigherThan(int rating) { | |
request.mRating = rating; | |
return this; | |
} | |
public SearchRequest toSearchRequest() { | |
return request; | |
} | |
} | |
private Set<MediaType> mTypes = new HashSet<MediaType>(); | |
private Set<MediaCategory> mCategory = new HashSet<MediaCategory>(); | |
private String mTitleQuery; | |
private int mRating = 0; | |
private int mOffset = 0; | |
private int mMaxResult = 100; | |
//constructors only accessible by the Builder | |
private SearchRequest() {} | |
public String getURL() { | |
StringBuilder builder = new StringBuilder(); | |
builder.append("http://my.server.com/search.json?"); | |
builder.append("offset=").append(mOffset); | |
builder.append("&max_result=").append(mMaxResult); | |
builder.append("&rating=").append(mRating); | |
if (mTitleQuery != null) builder.append("&titleQ=").append(this.mTitleQuery); | |
if (mCategory.size() > 0) builder.append("&categories=").append(toQueryValue(mCategory)); | |
if (mTypes.size() > 0) builder.append("&types=").append(toQueryValue(mTypes)); | |
return builder.toString(); | |
} | |
private String toQueryValue(Set<?> items) { | |
StringBuilder builder = new StringBuilder(); | |
for (Object item : items) { | |
if (builder.length() > 0) builder.append(","); | |
builder.append(item.toString()); | |
} | |
return builder.toString(); | |
} | |
} |
No comments:
Post a Comment