首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一个小型但可扩展的考试系统

一个小型但可扩展的考试系统
EN

Code Review用户
提问于 2018-01-24 17:42:53
回答 2查看 221关注 0票数 5

我试图在Java中实践OO设计和OOP,所以我创建了一个尝试面向对象和可扩展的考试系统。到目前为止,我还没有从Java中学到以下内容: Java 8、接口、集合、IO、Swing、异常

代码语言:javascript
复制
public abstract class Question {
    protected int score;
    protected boolean isAnswerRight;

    public Question(int puntuacion) {
        this.score = puntuacion;
    }

    public int getScore() {
        return score;
    }

    public abstract void ask();

    public abstract void showClue();

}

这个班有两个孩子

代码语言:javascript
复制
import javax.swing.*;

public class MultipleOptionQuestion extends Question {
    private static final int MULTIPLE_OPTION_SCORE = 1;
    private String statement;
    private String[] options;
    private String clue;
    private int rightOption;

    public MultipleOptionQuestion(int score, String statement, String[] options, int rightOption, String clue) {
        super(MULTIPLE_OPTION_SCORE);
        this.statement = statement;
        this.options = options;
        this.clue = clue;
        this.rightOption = rightOption;
    }

    @Override
    public void ask() {
        String answer = (String) JOptionPane.showInputDialog(null, statement, "Question Test", JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
        if (answer.equals(options[rightOption])) {
            this.isAnswerRight = true;
        }
    }

    @Override
    public void showClue() {
        JOptionPane.showMessageDialog(null, clue);

    }
}

和一个抽象类来表示简单的数学操作(+,-,*,/)

代码语言:javascript
复制
import javax.swing.*;

public abstract class ArithmeticQuestion extends Question {


    //additions may have a wider interval in order to make them harder
    // than multiplications, for example, and we want divisions to be exact so
    //child classes provide operands
    protected int first;
    protected int second;
    protected int result;


    public int getFirstOperand() {
        return first;
    }

    public int getSecondOperand() {
        return second;
    }

    public int getResult() {
        return result;
    }

    public abstract char getOperator();


    public ArithmeticQuestion(int score) {
        super(score);
    }

    public String stringQuestion() {
        return "How much is it? " +
                getFirstOperand() +
                getOperator() +
                getSecondOperand();
    }

    @Override
    public void ask() {
        String answer = null;
        while (answer == null || answer.equals("")) {
            answer = JOptionPane.showInputDialog(null, this.stringQuestion());
            if (answer != null) {
                if (Integer.parseInt(answer) == getResult()) {
                    isAnswerRight = true;
                }
            }
        }
        System.out.println(isAnswerRight ? "Right" : "Wrong"); //For debugging
    }

}

这里我不确定我是否做了一个好的设计(我不知道我是否有太多的课程)

代码语言:javascript
复制
import javax.swing.*;

public class AdditionQuestion extends ArithmeticQuestion {

    private static final int ADDITION_MINIMUM = 100;
    private static final int ADDITION_INTERVAL = 200;
    private static final int ADDITION_SCORE = 1;

    public AdditionQuestion() {
        super(ADDITION_SCORE);
        this.first = (int) (Math.random() * ADDITION_INTERVAL) + ADDITION_MINIMUM;
        this.second = (int) (Math.random() * ADDITION_INTERVAL) + ADDITION_MINIMUM;
        this.result = first + second;
    }

    @Override
    public char getOperator() {
        return '+';
    }

    @Override
    public void showClue() {
        JOptionPane.showMessageDialog(null, "Be careful when you add more than 10 units");
    }
}

减法类

代码语言:javascript
复制
import javax.swing.*;

public class SubtractionQuestion extends ArithmeticQuestion {
    private static final int SUBTRACTION_MINIMUM = 0;
    private static final int SUBTRACTION_INTERVAL = 100;
    private static final int SUBTRACTION_SCORE = 1;

    public SubtractionQuestion() {
        super(SUBTRACTION_SCORE);
        this.first = (int) (Math.random() * SUBTRACTION_INTERVAL) + SUBTRACTION_MINIMUM;
        this.second = (int) (Math.random() * SUBTRACTION_INTERVAL) + SUBTRACTION_MINIMUM;
        this.result = first - second;
    }

    @Override
    public char getOperator() {
        return '-';
    }

    @Override
    public void showClue() {
        JOptionPane.showMessageDialog(null, "The answer may be a negative number");
    }

}

用于乘法

代码语言:javascript
复制
import javax.swing.*;

public class MultiplicationQuestion extends ArithmeticQuestion {
    private static final int MULTIPLICATION_MINIMUM = 1;
    private static final int MULTIPLICATION_INTERVAL = 10;
    private static final int MULTIPLICATION_SCORE = 3;


    public MultiplicationQuestion() {
        super(MULTIPLICATION_SCORE);
        this.first = (int) (Math.random() * MULTIPLICATION_INTERVAL) + MULTIPLICATION_MINIMUM;
        this.second = (int) (Math.random() * MULTIPLICATION_INTERVAL) + MULTIPLICATION_MINIMUM;
        this.result = first * second;
    }

    @Override
    public char getOperator() {
        return '*';
    }

    @Override
    public void showClue() {
        String output = "RECALL THE MULTIPLICATION TABLE\n";
        for (int i = 1; i < 10; i++) {
            output += i + "x" + getFirstOperand() + "=" + i * getSecondOperand() + "\n";
        }

        JOptionPane.showMessageDialog(null, output);
    }
}

最后,考试:

代码语言:javascript
复制
import javax.swing.*;

public abstract class Exam {
    protected Question[] questions = new Question[50];
    protected int currentNumberOfQuestions = 0;
    protected int totalScore = 0;

    public void addQuestion(Question p) {
        questions[currentNumberOfQuestions++] = p;
    }

    public void increaseScore(int score) {
        this.totalScore += score;
    }

    public int maximumPossibleScore() {
        int total = 0;
        for (int i = 0; i < currentNumberOfQuestions; i++) {
            total += questions[i].getScore();
        }
        return total;
    }

    public void examResult() {
        JOptionPane.showMessageDialog(null, "You've got " + this.totalScore + " points out of: " + maximumPossibleScore());
    }

    public abstract void doExam();
}

模拟考试(做同样的问题直到你是对的)

代码语言:javascript
复制
public class mockExam extends Exam {
    //The student only scores if they are right in their first attempt
    //If they are wrong, a clue is shown

    @Override
    public void doExam() {
        for (int i = 0; i < this.currentNumberOfQuestions; i++) {
            Question p = this.questions[i];
            p.ask();
            if (p.isAnswerRight) {
                this.increaseScore(p.score);
            }
            while (!p.isAnswerRight) {
                p.showClue();
                p.ask();
            }
        }
        examResult();
    }
}

一次真正的考试

代码语言:javascript
复制
public class RealExamen extends Exam {
    //Only asks each question once
    @Override
    public void doExam() {
        for (int i = 0; i < this.currentNumberOfQuestions; i++) {
            Question p = this.questions[i];
            p.ask();
            if (p.isAnswerRight) {
                this.increaseScore(p.score);
            }
        }
        examResult();
    }
}

希望听到您对我如何改进和管理代码的反馈。

EN

回答 2

Code Review用户

回答已采纳

发布于 2018-01-24 22:20:58

谢谢你分享你的代码。

OOP并不意味着将代码“拆分”成随机类.

OOP的最终目标是减少代码重复、提高可读性和支持重用以及扩展代码。

执行OOP意味着您遵循某些原则(除其他外):

  • 信息隐藏/封装
  • 单一责任
  • 分离关注点
  • 亲吻(保持简单)愚蠢。)
  • 干(不要重复自己)。
  • “告诉我!别问。”
  • 德米特定律(“不要和陌生人说话!”)
  • 用多态性替换分支

信息隐藏

这是一个主要原则(不仅仅是在面向对象程序设计中)。在OOP中,这意味着没有其他类(甚至子类)知道某个类的内部结构。

您违反了这一原则,将Question的子类直接访问其成员变量scoreisAnswerRight

这也违反了说,不要问!原则。

更好的方法是在类Question中添加一个方法来管理score值本身:

代码语言:javascript
复制
public abstract class Question {
    private final /*hopefully the score never change during runtime */
                   int score; 

    public Question(int puntuacion) {
        this.score = puntuacion;
    }

   /** public entry point, do not override */
    public final int ask(){
          boolean isAnsweredRight = askUser();
          if(!isAnsweredRight) {// may fail once
              showClue();
              isAnsweredRight = askUser();
           }
           return isAnsweredRight? score : 0; // no score if failed
    };

    /** ask the question and report success/failure */
    protected abstract boolean askUser();

    public abstract void showClue();
}

类设计

正如注释中提到的那样,Question的一些子类引起了人们的怀疑:

在OOP中,当我们需要改变行为时,我们创建新的(子)类。也就是说:我们重写了一个超类的方法来做一些不同的或额外的计算。(返回值并不是计算.)

我在评论中提出的理由是:一个糟糕的设计是在其他地方完成的,这不应该成为这样做的借口。

因此,我将只有两个Question子类:

代码语言:javascript
复制
public class MultipleOptionQuestion extends Question {

代码语言:javascript
复制
public class ArithmeticQuestion extends Question {

我将介绍另一个接口Operation

代码语言:javascript
复制
interface Operation{
    int calculate(int first, int second);
}

ArithmeticQuestion看起来会是这样的:

代码语言:javascript
复制
public class ArithmeticQuestion extends Question {
    private final int first;
    private final int second;
    private final Operation operation;
    private final String operator;
    private final String clue;

    public ArithmeticQuestion(String operator, Operation operation, in first, int second, int score, String clue)
       super(score);
       // constructors do no work, they just assign values to members
       this.operation=operation;
       this.operator=operator;
       this.first=first;
       this.second=second;
       this.clue=clue;
   }
    public String stringQuestion() {
        return "How much is it? " +
                first +
                operator +
                second;
    }    

    public void showClue() {
        JOptionPane.showMessageDialog(null, clue);
    }   

    @Override
    public boolean askUser() {
        int result = operation.calculate(first,second);
        String answer = null;
        while (answer == null || answer.equals("")) {
            answer = JOptionPane.showInputDialog(null, this.stringQuestion());
            if (answer != null) {
                return Integer.parseInt(answer) == result;
            }
        }
        return false; // maybe marked as unreachable code...
    }

}

这将导致这个exsam类:

代码语言:javascript
复制
public class Exam {
    private static final int MULTIPLE_OPTION_SCORE = 1;
    private static final int MULTIPLICATION_MINIMUM = 1;
    private static final int MULTIPLICATION_INTERVAL = 10;
    private static final int MULTIPLICATION_SCORE = 3;
    private static final int SUBTRACTION_MINIMUM = 0;
    private static final int SUBTRACTION_INTERVAL = 100;
    private static final int SUBTRACTION_SCORE = 1;
    private static final int ADDITION_MINIMUM = 100;
    private static final int ADDITION_INTERVAL = 200;
    private static final int ADDITION_SCORE = 1;
    private static final int QUESTION_TYPE_COUNT = 4;


    public void doExam(int numberOfQuestions) {
        Random random = new Random();
        int maxScore =0;
        int userScore =0;
        for (int i = 0; i < numberOfQuestions; i++) {
            int questionType =random.nextInt(QUESTION_TYPE_COUNT);
            Question question;
            switch(questionType){
              case 0: // Addition
                  question = new ArithmeticQuestion("+",
                     (first,second)->first+second,
                     random.nextInt(ADDITION_INTERVAL)+ADDITION_MINIMUM,
                     random.nextInt(ADDITION_INTERVAL)+ADDITION_MINIMUM,
                     ADDITION_SCORE,
                     "Be careful when you add more than 10 units");
                   maxScore+=ADDITION_SCORE;
                   break;
                case 1: // Subtraction
                  question = new ArithmeticQuestion("-",
                     (first,second)->first-second,
                     random.nextInt(SUBTRACTION_INTERVAL)+SUBTRACTION_MINIMUM,
                     random.nextInt(SUBTRACTION_INTERVAL)+SUBTRACTION_MINIMUM,
                     SUBTRACTION_SCORE,
                     "The answer may be a negative number");
                   maxScore+=SUBTRACTION_SCORE;
                   break;
                case 2: // Multiplication
                  // ...
                default: // MultiOption
                  // ...
              }      
              userScore+= question.ask();
        }

        JOptionPane.showMessageDialog(null, "You reached "+userScore+ " of "+maxScore+" possible points!")
    }
}
票数 5
EN

Code Review用户

发布于 2018-01-25 17:31:05

我的观点(这肯定与蒂莫西的解释重叠):

抽象

我真的不喜欢抽象,我会对待它--就像德语里说的那样--“继母”。您有一个抽象类型Question,一个抽象类型MultipleOptionQuestion,它扩展了Question并覆盖了ask()showClue(),一个类ArithmeticQuestion扩展了Question并提供了其他方法。以及其他几种覆盖和扩展行为的Question类型。这是,温和地说,有点混乱。除了混乱之外,您不能在没有实际实现的情况下测试抽象类型的逻辑,也不能在没有抽象的情况下测试您的实现,除非您非常严格地分离抽象和实现。这也可能违反Liskov的替代原则,稍后会有更多的内容:

问题与图形用户界面

的耦合

您的问题类型与swing类有直接的依赖关系。如果你想改变gui,或者使用另一种技术,可能是应用程序,或者网页,你就必须改变业务逻辑。GUI通常是多层体系结构的顶层,并且调用它下面的层,下面的层一定不知道GUI。

Liksov代换原理

现在,如果您实现上面的“耦合”部分,这一点特别重要。它的原理是:你有一个例程A,A用‘超级型’执行一些东西,你有一个X和Y类,它们是S的一个子类型。如果你必须改变常规A,当你引入一个新的S子类型时,你就违反了Liskov的替代原则。例如,在您的示例中,它是ArithmeticQuestion类型。被编程用于与您的类型Question一起工作的类型不能使用AirthmeticQuestion,因为您需要例如getFirstOperand才能正确工作。因此,接下来您必须做的是,例如,对instanceof类型的Question进行转换。有一些方法可以以"oo“的方式来解决这个问题,但我认为在使用子类型时,这是非常重要的。

小事物

  • 在构造函数中计算它们的操作数的一些问题。这些都应该通过。仅仅是因为单元测试的原因:您无法验证正确的操作,如果您不知道操作数实际上是什么。
  • 不确定它是否是标准/约定,但我倾向于将抽象类型命名为抽象类型(Question -> AbstractQuestion)。这常常有帮助。
  • maximumPossibleScore应该与getPrefix一起使用。另外,您可能需要考虑只计算一次。
  • mockExam应该以大写M开头。

爱因斯坦是令人敬畏的

考虑到任何问题的解决办法,爱因斯坦说:尽可能容易,但不容易。老实说,这就是你的解决方案最缺乏的地方。在这里,对于如何更容易地实现它(当然也是有争议的),有一个普遍的想法:

代码语言:javascript
复制
public class Question {
    private String question;
    private String answer;
    private String[] answers;

    private Question(String question, String answer) {
        this(question);
        this.answer = answer;
    }

    private Question(String question, String[] answers) {
        this(question);
        this.answers = answers;
    }

    private Question(String question) {
        this.question = question;
    }

    public boolean isMultipleChoiceQuestions() {
        return answers != null;
    }

    // ...

    public static Question createQuestion(String question, String answer) {
        return new Question(question, answer);
    }

}

public class ExamGenerator {

    public static Exam createEasyExam() {
        // create questions ...

        return new Exam();
    }

    private static Question createMultiplicationQuestion(int factor1, int factor2, int product) {
        return Question.createQuestion("What is blablabla", "put correct answer here");
    }
}

希望这会有所帮助:)

慢慢来

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/185892

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档