Spring Web Flow. Тесты. Часть 2. Модель и Контроллер

января
16
2012
Метки: spring spring web flow

Содержание

C помощью простого примера вряд ли удастся показать всю мощь Spring Web Flow, однако сложную программу не стоит использовать в качестве учебного примера. Поэтому в основе примера будет что-то среднее, а именно - программа для проверки знаний пользователя с помощью тестов. То есть, пользователю задаются вопросы и предлагаются несколько вариантов ответа на каждый из них. Чтобы как можно лучше показать преимущества использования SWF, мы немного усложнили задачу. Итак:

  1. Авторизация. Перед прохождением тестов пользователю предлагается войти в систему под своим именем. Если имя в системе не существует, то программа просит пользователя зарегистрироваться.
  2. Ошибки. Во время прохождения тестов пользователь имеет право допустить 3 ошибки. После каждой из первых двух ошибок пользователю отображается окно с информацией об этом. После 3-й ошибки пользователю сообщается, что тест завершился неудачей.
  3. Результат. После успешного прохождения тестов, пользователю отображается список всех вопросов, напротив каждого из которых стоит пометка о правильности ответа.

Теперь перейдем к непосредственному созданию приложения. Представим классы модели.

Модель

Из предыдущей части нам понадобится класс User, содержащий всего одно свойство name:

User.java

package com.seostella.swftests.domain;

import java.io.Serializable;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class User implements Serializable {
   private static final long serialVersionUID = 1L;
   
   private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Класс Test будет содержать всю информацию по одному тесту: вопрос, 3 варианта ответа, правильный ответ и вариант ответа пользователя:

Test.java

package com.seostella.swftests.domain;

import java.io.Serializable;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class Test implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String question;
    private String answer1;
    private String answer2;
    private String answer3;
    private int userAnswer;
    private int correctAnswer;

    public Test(
            String question, 
            String answer1, 
            String answer2, 
            String answer3, 
            int correctAnswer) {
        this.question = question;
        this.answer1 = answer1;
        this.answer2 = answer2;
        this.answer3 = answer3;
        this.correctAnswer = correctAnswer;
    }

    public String getAnswer1() {
        return answer1;
    }

    public String getAnswer2() {
        return answer2;
    }

    public String getAnswer3() {
        return answer3;
    }

    public String getQuestion() {
        return question;
    }

    public int getUserAnswer() {
        return userAnswer;
    }

    public int getCorrectAnswer() {
        return correctAnswer;
    }

    public void setAnswer1(String answer1) {
        this.answer1 = answer1;
    }

    public void setAnswer2(String answer2) {
        this.answer2 = answer2;
    }

    public void setAnswer3(String answer3) {
        this.answer3 = answer3;
    }

    public void setQuestion(String question) {
        this.question = question;
    }

    public void setCorrectAnswer(int correctAnswer) {
        this.correctAnswer = correctAnswer;
    }
    
    public void setUserAnswer(int result) {
        this.userAnswer = result;
    }
    
    public boolean isCorrectAnswer(){
        if( this.userAnswer == this.correctAnswer ){
            return true;
        }
        return false;
    }
    
    public String getUserAnswerAsText() {
        switch( this.userAnswer ){
            case 1:
                return answer1;
                
            case 2:
                return answer2;
                
            case 3:
                return answer3;
        }
        return "n/a";
    }
}

Класс TestList содержит список тестов и количество неправильных вариантов ответа:

TestList.java

package com.seostella.swftests.domain;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class TestList implements Serializable {

    private static final long serialVersionUID = 1L;
    private List<Test> testAnswers;
    private int wrongAnswerCount = 0;

    public TestList() {
        testAnswers = new ArrayList<Test>();
    }

    public List<Test> getTests() {
        return testAnswers;
    }

    public void setTests(List<Test> results) {
        this.testAnswers = results;
    }
    
    public void addTest(Test test) {
        testAnswers.add(test);
        if( !test.isCorrectAnswer() ){
            wrongAnswerCount++;
        }
    }

    public int getWrongAnswerCount() {
        return wrongAnswerCount;
    }
   
}

Класс TestChecker содержит экземпляры классов User и TestList:

TestChecker.java

package com.seostella.swftests.domain;

import com.seostella.swftests.domain.TestList;
import com.seostella.swftests.domain.User;
import java.io.Serializable;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class TestChecker implements Serializable {

    private static final long serialVersionUID = 1L;
    private User user;
    private TestList testList;

    public TestChecker() {
        testList = new TestList();
    }

    public TestChecker(User user) {
        this.user = user;
    }

    public TestChecker(User user, TestList testList) {
        this.user = user;
        this.testList = testList;
    }

    public User getUser() {
        return user;
    }

    public TestList getTestList() {
        return testList;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void setTestList(TestList testList) {
        this.testList = testList;
    }
}

Далее представим обработку 4-х действий (getUser, createUser, getNextTest и addTestResult), которые будут происходить во время выполнения веб-приложения и два вспомогательных java-bean'а - QuestionService и UserService.

Обработка действий

Часть логики по обработке событий tests-flow находится в классе TestFlowActions, вторая часть - в классе QuestionService. Листинг TestFlowActions представлен ниже:

TestsFlowActions.java

package com.seostella.swftests.flow;

import com.seostella.swftests.domain.User;
import com.seostella.swftests.flow.exception.user.TooShortUsernameException;
import com.seostella.swftests.flow.exception.user.UserNotFoundException;
import com.seostella.swftests.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 *
 * @author seostella.com
 */
@Component
public class TestsFlowActions {
    @Autowired
    private UserService userService;

    public TestsFlowActions() {
    }
    
    public User getUser(String name) throws TooShortUsernameException,
            UserNotFoundException{
        return userService.getUser(name);
    }
    
    public User createUser(String name) throws TooShortUsernameException{
        return userService.createUser(name);
    }
    
}

В этом классе появилась новая сущность - это объект userService. Он имеет аннотацию @Autowired, которая означает, что значением этого объекта будет java-bean UserService. Этот класс является синглтоном, поэтому во время создания приложения создается только один экземпляр данного класса.

Метод getUser возвращает пользователя по указанному имени. Если пользователя нет в списке предопределенных пользователей (Sheldon, Leonard, Penny, Howard, Raj, Amy, Bernadette, Stuart, Priya или Leslie), то генерируется исключение UserNotFoundException. Если пользователь ввел меньше 3-х символов, то генерируется исключение TooShortUsernameException.

Метод createUser создает пользователя с указанным именем. Если длина имени меньше 3-х символов, то возвращается ошибка TooShortUsernameException.

Класс UserService содержит реализацию описаных выше методов getUser и createUser. Заметим, что в конструкторе класса создается несколько объектов класса User, которые инициализируются при создании объекта UserService. Это пользователи со следующими именами: Sheldon, Leonard, Penny, Howard, Raj, Amy, Bernadette, Stuart, Priya и Leslie.

UserService.java

package com.seostella.swftests.service;

import com.seostella.swftests.domain.User;
import com.seostella.swftests.flow.exception.user.TooShortUsernameException;
import com.seostella.swftests.flow.exception.user.UserNotFoundException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;

/**
 *
 * @author seostella.com
 */
@Component
public class UserService {
    private List<User> userList = null;

    public UserService() {
        userList = new ArrayList<User>();
        userList.add( new User("Sheldon") );
        userList.add( new User("Leonard") );
        userList.add( new User("Penny") );
        userList.add( new User("Howard") );
        userList.add( new User("Raj") );
        userList.add( new User("Amy") );
        userList.add( new User("Bernadette") );
        userList.add( new User("Stuart") );
        userList.add( new User("Priya") );
        userList.add( new User("Leslie") );
    }
    
    public User getUser(String name) throws TooShortUsernameException,
            UserNotFoundException{
        if (name != null && name.length() > 2) {
            for ( User user : userList ){
                if( user.getName().equals(name) ){
                    return user;
                }
            }
        } else {
            throw new TooShortUsernameException();
        }
        throw new UserNotFoundException();
    }
    
    public User createUser(String name) throws TooShortUsernameException{
        if (name != null && name.length() > 2) {
            User user = new User(name);
            userList.add( user );
            return user;
        } else {
            throw new TooShortUsernameException();
        }
    } 
}

Класс QuestionService не содержит аннотации @Component, потому что нам необходимо чтобы экземпляры этого класса создавались при каждом старте flow, в отличие от UserService, который создается один на все запущенные экземпляры tests-flow:

QuestionService.java

package com.seostella.swftests.service;

import com.seostella.swftests.domain.Test;
import com.seostella.swftests.domain.TestList;
import com.seostella.swftests.flow.exception.test.LastTestException;
import com.seostella.swftests.flow.exception.test.MaxWrongAnswersException;
import com.seostella.swftests.flow.exception.test.WrongAnswerException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class QuestionService implements Serializable{
    private static final int MAX_WRONG_ANSWER = 3;
    private List<Test> tests;
    private int testIndex = 0;

    public QuestionService() {
        tests = new ArrayList<Test>();
        tests.add(new Test(
                "Сколько оборотов вокруг Солнца делает Земля за один год?", 
                "1", "4", "365", 
                1 
                ));
        tests.add(new Test(
                "Сколько лет длилась Столетняя война?", 
                "86", "100", "116", 
                1 
                ));
        tests.add(new Test(
                "Сколько игроков в футбольной комманде?", 
                "9", "10", "11", 
                3
                ));
        tests.add(new Test(
                "Какой океан омывает Индию", 
                "Индийский", "Тихий", "Атлантический", 
                1
                ));
        tests.add(new Test(
                "Столица Англии?", 
                "Париж", "Барселона", "Лондон", 
                3
                ));
    }
    
    public Test getNextTest(){
        return tests.get(testIndex++);
    }
    
    public boolean isLastQuestion(){
        if( testIndex >= tests.size() ){
            return true;
        }
        return false;
    }
    
    public void addAnswer( TestList testList, Test test )
            throws LastTestException, WrongAnswerException, MaxWrongAnswersException{
        testList.addAnswer(test);
        
        if ( !test.isCorrectAnswer() ) {
            if (testList.getWrongAnswerCount() >= MAX_WRONG_ANSWER) {
                throw new MaxWrongAnswersException();
            }
            checkTestIsLast();
            throw new WrongAnswerException();
        }
        checkTestIsLast();
    }
    
    private void checkTestIsLast() throws LastTestException{
        if ( isLastQuestion() ) {
            testIndex = 0;
            throw new LastTestException();
        }        
    }
}

В конструкторе QuestionService создается список из пяти предопределенных вопросов с вариантами ответов и номером правильного ответа. Также в классе присутствует свойство testIndex, которое отвечает за контроль текущего номера вопроса.

Метод getNextTest возвращает следующий тест в списке, а индекс-позицию текущего вопроса testIndex увеличивает на единицу.

Метод addAnswer добавляет в список ответов пользователя тест, полученный в параметре. Также в этом методе проверяется или заданы все вопросы. Если все вопросы уже были представлены пользователю, то генерируется исключение LastTestException. Если пользователь совершил первую или вторую ошибку, то генерируется исключение WrongAnswerException. Если же пользователь совершил третью ошибку, то генерируется исключение MaxWrongAnswersException.

В подпакетах test и user пакета com.seostella.swftests.flow.exception содержится 5 классов ошибок: LastTestException, MaxWrongAnswersException, WrongAnswerException, TooShortUsernameException и UserNotFoundException. Листинги всех их приводить не будем, так как они идентичны, не считая названия. Приведем листинг только одного класса UserNotFoundException:

UserNotFoundException.java

package com.seostella.swftests.flow.exception.user;

/**
 *
 * @author seostella.com
 */
@SuppressWarnings("serial")
public class UserNotFoundException extends Exception {
  public UserNotFoundException() {}
  
  public UserNotFoundException(String message) {
    super(message);
  }
}

Переходим к основной теме этой статьи - описание flow.

< Spring Web Flow. Тесты. Часть 1. Настройка проекта Spring Web Flow. Тесты. Часть 3. Обзор flow >

Напишите первое сообщение!

Вы должны войти под своим аккаунтом чтобы оставлять комментарии