【问题标题】:Best practice for saving data in abstract classes在抽象类中保存数据的最佳实践
【发布时间】:2013-10-23 12:30:06
【问题描述】:

因此,举个例子,假设我们有一个名为Questionabstract class,该问题包含很多字符串,一个用于问题本身,一个用于答案,两个响应发布给用户,如果他的问题是对的/错的。

public abstract class Question {

    private final String question;
    private final String answer;
    private final String answerCorrect;
    private final String answerWrong;

}

我的问题基本上是,初始化所有字符串的常用方法是什么?到目前为止,我已经编写了 2 个关于如何做到这一点的版本,它们各有优缺点,我想知道是否有某种“最佳编码实践”。


A 版
初始化构造函数中的所有内容。

public abstract class Question {

    //...

    public Question(String question, String answer, String answerCorrect, String answerWrong) {

        this.question = question;
        this.answer = answer;
        this.answerCorrect = answerCorrect;
        this.answerWrong = answerWrong;

    }
}

这似乎很方便,我唯一的问题是用户无法确定字符串的顺序。

public class ExampleClass extends Question {

    public ExampleClass() {
        super("I think, that's the answer", "and that's the question", "answer wrong?", "answer right?");
    }

}

版本 B
不要立即初始化并等待用户执行。

public abstract class Question {

    //...

    public Question() {

        this.question = "";
        this.answer = "";
        this.answerCorrect = "";
        this.answerWrong = "";

    }

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

    //...
}

这使得初始化变量更容易,但是字符串不能再是final,并且不能保证用户会初始化所有的变量。


我也想过让子类实现在Question 的构造函数中调用的抽象方法来初始化所有字符串并保留它们final,但那个版本对我来说似乎有点太奇怪了.

还有其他/更好的方法吗?我应该更喜欢哪个版本?
提前感谢您的支持。

【问题讨论】:

  • 如果你的属性是final并且你放了setter,你的代码将不会编译
  • @nachokk 我提到了它:“(...)字符串不能再是最终的(...)”
  • the only problem I have with this is that users will not be sure, in which order the strings have to be。用户不会调用构造函数,开发人员会这样做。为了确保正确的顺序,您应该写评论。这不应该妨碍您选择版本 A。
  • 考虑到大多数 IDE 都有弹出窗口,可以为您提供构造函数的参数,第一个缺点是 N/A(另请注意,这个“缺点”适用于几乎所有编写过的方法和构造函数) .
  • 如果你有一个额外的non-final成员“points”,那么使用CTOR给它一个合理的“undefined”值。然后为它添加一个setter。在 getter 的文档中,该“未定义”值是什么。就像“-1 如果还没有设置点”。

标签: java initialization abstract-class


【解决方案1】:

不要将属性(例如question)视为变量,而是要考虑对它们的值的限制,这些限制必须遵守才能使类正确运行。它们可以为空吗?它们可以是空的吗?现在设计您的方法构造函数,这样就不可能打破这些限制。您可能会发现 only 可以做到这一点的方法是在构造函数(您的版本 A)中设置初始值。您可能必须在构造函数和 setter 方法中添加前置条件检查,如果传递给它们的值会导致限制被破坏,它们会检查给定抛出适当异常(NullPointerExceptionIllegalArgumentException)的值。

另外,请考虑在构造对象后更改属性的值是否真的有意义。如果不是,那么该属性不应该是一个设置器,使您的版本 B 不可能。

【讨论】:

  • 如果一个变量不能为null(并且是final),我应该立即抛出一个NullPointerException吗?
  • @felixfritz 是的;我已经修改了我的答案以澄清这一点。
  • 这种方法要么使属性不可能具有无效值,要么导致尝试具有无效值的代码快速失败:stackoverflow.com/questions/2807241/…
【解决方案2】:

这可能有点矫枉过正,但我​​相信你可以在这里使用构建器......

public class Question
{
    private final String question;
    private final String answer;
    private final String answerCorrect;
    private final String answerWrong;

    Question(QuestionBuilder builder) {
        this.question = builder.question;
        this.answer = builder.answer;
        this.answerCorrect = builder.answerCorrect;
        this.answerWrong = builder.answerWrong;
    }

    // public getters omitted to shorten answer

    @Override
    public String toString(){
        return String.format("question: '%s', answer: '%s', answerCorrect: '%s', answerWrong: '%s'", question, answer, answerCorrect, answerWrong);
    }

    public static void main(String[] args) {
        QuestionBuilder qb = new QuestionBuilder();
        qb = qb.question("This is the question").answer("This is the answer").answerCorrect("Correct answer").answerWrong("Wrong Answer");
        Question question = new Question(qb);
        System.out.println(question);
    }


    public static class QuestionBuilder{
        private String question;
        private String answer;
        private String answerCorrect;
        private String answerWrong;

        public QuestionBuilder question(String question) {
            this.question = question;
            return this;
        }

        public QuestionBuilder answer(String answer) {
            this.answer = answer;
            return this;
        }

        public QuestionBuilder answerCorrect(String answerCorrect) {
            this.answerCorrect = answerCorrect;
            return this;
        }

        public QuestionBuilder answerWrong(String answerWrong) {
            this.answerWrong = answerWrong;
            return this;
        }
    }
}

给出输出

question: 'This is the question', answer: 'This is the answer', answerCorrect: 'Correct answer', answerWrong: 'Wrong Answer'

注意:我意识到最初的问题是关于抽象类的。我使用了一个具体的类,所以我可以给出一个工作示例,尽管该解决方案可以适用于抽象类。

【讨论】:

  • @Dukeling 同意,Builder 不会强制用户设置每个值,但是 OP 的“版本 B”解决方案也是如此。与“版本 B”解决方案不同,我相信 Builder 提供了 OP 所需的不变性。
  • 我以前从未见过这样的东西,这很酷!而且我认为,如果开发人员决定不初始化所有内容,那与在版本 A 中将字符串设置为 null 一样,对吧?我喜欢这个主意。
  • @felixfritz 没错。例如,如果您只调用了.answer(...) 方法,则剩余元素将是null,结果输出将是question: 'This is the question', answer: 'null', answerCorrect: 'null', answerWrong: 'null' 我提供的示例应该编译并运行,这样您就可以在调试器中轻松使用它看看它在各种不同的情况下是如何工作的。
【解决方案3】:

版本 A 是要走的路。但是,您是对的,如果您不告诉您的用户(我假设的其他开发人员)哪个参数是哪个,他们就无法知道在哪里输入什么。

这就是Javadoc 派上用场的地方。

这是一个例子:

/**
 * Create a new instance of Question given the following parameters:
 * 
 * @param  question This is the question
 * @param  answer This is the answer
 * @param  answerCorrect Whenever someone guesses correct, print this
 * @param  answerWrong Whenever someone guesses wrong, print this
 */
public Question(String question, String answer, String answerCorrect, String answerWrong) {

    this.question = question;
    this.answer = answer;
    this.answerCorrect = answerCorrect;
    this.answerWrong = answerWrong;

}

【讨论】:

  • 这就是“正确命名参数”派上用场的地方 ;)
  • 只是说自己的经验:说明如果其中一个参数为空会发生什么 - 或者一般来说,你期望它们是什么。如果参数可以有一个,则始终添加单位。 (比如“int time”——它是什么?秒?天?可以是负数吗?)
  • @Fildor:我想使用与felix fritz 在他的问题中所做的相同的名称。该评论是否旨在补充我的答案?如果是这样:我同意。我个人更喜欢listCarBrandsbtnCreateNewEntry 之类的名称来描述汽车品牌列表和用于分别创建新条目的按钮。通过这种方式,我可以快速了解所有按钮的概览,例如,如果我在首选 IDE 中键入 btn 并按 Ctrl+Space(自动完成建议)。
  • 是的,这是一个补充:)
猜你喜欢
  • 1970-01-01
  • 2010-12-28
  • 2017-01-26
  • 2013-09-22
  • 2015-12-02
  • 1970-01-01
  • 2015-03-12
  • 2017-12-03
  • 1970-01-01
相关资源
最近更新 更多