单一职责原则 vs 开闭原则

Single responsability principle vs open close principle

我正在编写一个程序来向用户显示一系列问题,收集他的回答并打印出来。

我有不同类型的问题,具体取决于他们需要的回答类型:整数、布尔值或文本。

我开始写这段代码:

abstract class Question
{
    string text;
}

class IntegerQuestion : Question
{
    int response;
}

class TextQuestion : Question
{
    string response;
}

class BooleanQuestion : Question
{
    bool response;
}

好了,现在我们要打印问题和答案了。

我的第一个方法是在问题class中定义一个新的抽象打印函数来强制子classes定义打印方法,然后是一个打印机class:

abstract class Question
{
    string text;
    abstract string Print();
}

class Printer
{
    string PrintQuestions(List<Question> questions)
    {
        string result = "";

        foreach(var question in Questions)
            result += question.Print() + "\r\n";

        return result;
    }
}

我想到的另一种方法是忽略抽象方法并像这样创建打印机 class:

class Printer
{
    string PrintQuestions(List<Question> questions)
    {
        string result = "";

        foreach(var question in Questions)
        {
            if(question is IntegerQuestion)
            {
                var integerQuestion = (IntegerQuestion)question;
                result += integerQuestion.text + integerQuestion.response;
            }
            if(question is TextQuestion)
            {
                ...
            }
            ...
        }

        return result;
    }
}

显然,第二种方法不遵循打印机的 OCP class,而是首先遵循。

但是,SRP 呢?

如果那我需要在HTML中写问题和回复:

abstract class Question
{
    string text;
    abstract string Print();
    abstract string PrintHTML();
}

class HTMLPrinter { ... }

不质疑 subclass 是否违反了 SRP,因为他们知道如何以纯文本格式打印它们 html?

Aren't question subclasses violating SRP because they know how to print them in plain text and html

你完全正确。

首先,关于您的命名约定和设计,如果我理解您的演示,为什么答案扩展 Question ?继承是对象之间的"Is a"关系。

我们应该说 Answer 是 Question 吗?您的业​​务中似乎有两种不同的概念:

  • 问题中的问题
  • 答案包含用户对问题的回答

我可能会做类似的事情:(抱歉语法,它是某种伪代码)

interface IAnswer{
    string toString();
}
class IntegerAnswer implements IAnswer{
    int answer;
    string toString(){
        return (string)this.answer;
    }
}
....
class Question{
    string text;
    IAnswer answer; //or List<IAnswer> answers if you can old more than one answer by Question
    string toString(){
        return this.text;
    }
}

然后,您可以定义打印机:

interface IQuestionPrinter{
    string print(List<Question> questions);
}
class Printer implements IQuestionPrinter{
     string print(List<Question> questions){
          string res = '';
          foreach(question in questions){
              res+=question.toString() + " : " + question.answer.toString();
          }
          return res;
     }
}
class HTMLPrinter implements IQuestionPrinter{
    string print(List<Question> questions){
          string res = "<ul>";
          foreach(question in questions){
              res+="<li>";
              res+= "<span>" + question.toString() + "</span>";
              res+="<span>" + question.answer.toString()+"</span>;
              res+="</li>";
          }
          return res+"</ul>";
     }
}

或类似的东西。

那么你所有的问题和答案都知道它们必须扩展一个 toString() 方法,我们将打印工作委托给专用的 IQuestionPrinter。

制作 Answer 界面很好,因为 Printer 不必知道 Answer 是 Integer、Boolean 还是 String 或其他。如果你有其他 "types" 的问题,你应该定义一个接口 IQuestion :

interface IQuestion{
    IAnswer answer; // or List<IAnswer> answers
    string toString();
}

然后 IQuestionPrinter 应该考虑它:

interface IQuestionPrinter{
    string print(List<IQuestion> questions);
}