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

січня
16
2012
Мітки: spring spring web flow

Зміст

За допомогою простого прикладу навряд чи вдасться показати всю міць 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 >

Напишіть перше повідомлення!

Ви повинні увійти під своїм аккаунтом щоб залишати коментарі