Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
For example, consider a simple scenario when using Turbo Tax:
Q. Choose your filing status:
If you choose "married, filing jointly," you must enter information for both spouses, as opposed to information for one spouse, which you would enter if you choose "married, filing separately." Similarly, you would not be further bothered with questions regarding stocks or bonds if, by answering a question, you told Turbo Tax that you do not own any. However, if you do own some, you must answer more questions about your holdings. By providing only one question at a time in successive order, the questionnaire would pose only relevant questions associated with the chosen answer.
If we use a survey design where an answer to a question does not determine the next question, then Turbo Tax users who are single or married but not filing jointly would be quite frustrated. Additionally, when new laws and regulations cause the logic and data to change from year to year, Turbo Tax code would have to be modified. It would be more cost effective if such logic and data was independent and maintained separately from the codebase. This article shows you how to develop an application that decouples the surveying content and logic from the application.
Note: Download this article's code from Resources.
A good solution should determine the appropriate actions (logic) based on various data (survey content) without coupling the object structure with the decision and evaluation logic so both the data and logic can be maintained separately from the code. Thus, our problem requirements are:
With such requirements, we have a class diagram similar to the one in Figure 1.
Figure 1. Class diagram shows the design. Click on thumbnail to view full-size image.
Figure 2 illustrates a typical object structure.
Figure 2. Object diagram shows a possible object interaction. Click on thumbnail to view fill-size image.
From the structure, the major pieces are:
If your survey requires data entry, instruction, or other capabilities, it extends the action abstraction and performs the appropriate function.
The UML diagrams shown above give us an object structure of how successive questions and their answers make up a decision tree. The structure does not depend on the actual content and how the decision tree is built.
In implementing this solution, I find it helpful to apply various design patterns. For example, using the Visitor pattern,
I can abstract the detail implementation of answering questions behind the QuestionInteraction interface. Also, I use the Template Method pattern to delay specific details about how each action is performed.
Listing 1 shows a possible implementation of the action abstraction. It provides only the basic but necessary structure dictated
from the design diagram in Figure 1. In essence, the abstract class Action provides the necessary wiring while leaving the detail implementation for the subclasses:
Listing 1. Action.java
public abstract class Action
{
//Constructor is protected - should always have a subclass object
protected Action(String id) { id_ = id; }
public String getId() { return id_; }
public boolean isDone() { return done_; }
public void setPreviousAction(Action prev) { prevAction_ = prev; }
public String getNextActionId() { return nextActionId_; }
public Action getPreviousAction() { return prevAction_; }
public abstract void perform();
}
Listing 2 shows the structure of a question with various possible answers. The perform() method requires an available QuestionInteraction interface, shown in Listing 3, for answering the question, regardless of how this interaction is implemented:
Listing 2. Question.java
public abstract class Question extends Action
{
//......
// The actual question itself
public String getText() { return questionText_; }
// The explanation or instruction text for answering the question
public String getExplanation() { return explanation_; }
// Allow multiple choices
public void addAnswerChoice(Answer ans) { ansChoices_.add(ans); }
// Set the interaction method to be used
public void setQuestionInteraction(QuestionInteraction qInt) {
interaction_ = qInt;
}
/* The question is answered */
public void setAnswer(int index) {
Answer ans = (index >= 0 && index < ansChoices_.size()) ?
(Answer) ansChoices_.get(index) : null;
setAnswer(ans);
}
public void setAnswer(Answer ans) {
answer_ = ans;
if (answer_ != null) {
setDone(true); //Question has been answered
Action consequence = answer_.getConsequenceAction();
if (consequence != null)
setNextActionId(consequence.getId());
}
else { // Not answered, or remove previous answer
setDone(false);
}
}
public void perform() {
// Don't perform anything if there's no way of getting the answer
if (interaction_ == null || isDone())
return;
// Use of double dispatch (first dispatch)
interaction_.answer(this);
}
}
Listing 3 shows an interface for providing a question-answer interaction mechanism. With this interface, we can have any kind of interaction implementation, a standard console I/O (input/output) or a colorful GUI (graphical user interface):
Archived Discussions (Read only)