【问题标题】:OOP Design, Calling Certain Methods Outside of a Main ClassOOP 设计,在主类之外调用某些方法
【发布时间】:2013-09-28 22:27:21
【问题描述】:

在我的班级中,我们将创建一个包含多个主类和共享类的项目。在一个名为UserApp 的特定主类中,我创建了UserInterface 类的一个对象,它直接 处理一个名为Log.txt 的文件。

我在UserApp 内部创建了一个类的对象DataStorage,我用它来调用一个将字符串值返回给UserApp 的方法。然后我获取该字符串值并将其传递给UserInterface 中的一个方法,该方法将写入文件Log.txt。例如:

public class UserApp {
    public static void main(String[] args) {
        UserInterface ui = new UserInterface();

        String[] commands = ui.readCommandLine();

        while(!ui.isFileEnd()){

            switch(command[0]){
            case "LI":  ui.displayThis(dataStorage.listById());
            break;
            case "QI":  ui.displayThis(dataStorage.queryById(command[0]));
            }
        }
    }
}

public class DataStorage {
    public String queryById(String id) {
        // Stuff the method does goes here

        return stringToReturn;
    }
}

对我来说,这似乎是最 OOP 的做事方式。我给她发了电子邮件,问她这是否正确。她说要在DataStorage 中的listById() 内调用ui.displayThis... 这意味着我需要在DataStorage 类中创建一个UserInterface 对象,或者将DataStorage 对象作为listById() 的参数。如果我像她说的那样做,listById() 方法不会返回字符串,而是无效的。例如:

public class UserApp {
    public static void main(String[] args) {

        String[] commands = ui.readCommandLine();

        while(!ui.isFileEnd()){

            switch(command[0]){
            case "LI":  dataStorage.listById(); // Here is the difference
            break;
            case "QI":  dataStorage.queryById(command[0]); // And here
            }
        }
    }
}

public class DataStorage {
    public void queryById(String id) {
        UserInterface ui = new UserInterface();
        // Stuff the method does goes here

        ui.displayThis(stringToDisplay);
    }
}

还有更多的 switch 语句和方法,但我觉得没有必要为这个问题展示它们。我对此进行了一些研究,根据我收集到的信息,我不确定这是一种风格偏好,或者一种方式是否比另一种更好。她希望我这样做的方式对 OOP 语言来说并不合适。哪种方法实际上对于 OOP 设计是正确的?

编辑:第二部分实际上是传入一个 UserInterface 对象作为参数。这似乎比每次都创建对象更有意义。 this 会是更好的方法吗?

【问题讨论】:

  • 旁注:switch 语句中的情况会失败,所以按照您编写的方式,"LI" 命令将执行"LI""QI" 操作。如果这不是您想要的,请使用break;
  • 如果我们遵循Model-View-Controller (MVC)-原则,其中DataStorage中的ModelUserInterfaceView,和UserAppController,那么在任何情况下都不应该DataStorage 调用UserInterface,甚至知道它的存在。 MVC 是一个古老的原则,我希望即使是老师也知道它。但我猜不是……
  • 你导师的解决方案非常可笑。暴露所有的 GUI 内部,让它们成为数据层和 UI 层之间的 API 是非常罕见的。
  • @ajb 啊,我的错。我将添加编辑。谢谢。
  • 一方面,正确地执行第二种方式需要一组稳定的 GUI 操作来处理数据。与您的方式相反,API 是一组查询操作可用数据的可能方式。根据经验,后者往往是一组更可预测(主要是样板文件)和稳定的功能集 - 你的模型变化比你的 UI 少。

标签: java class oop parameter-passing


【解决方案1】:

如果是我,我可以让 listById 成为显示的 void 函数(而不是返回 String)... 但是我会这样做它是这样的:

public interface DisplayMethod {
    public void display (String s);
}

public class DataStorage {
    public void queryById(String id, DisplayMethod displayer) {
        // UserInterface ui = new UserInterface();  DELETE THIS LINE
        // Stuff the method does goes here

        displayer.display(stringToDisplay);
    }
}

UserApp:

final UserInterface ui = new UserInterface();

...

DisplayMethod displayer = new DisplayMethod () {
    public void display (String s) 
        { ui.displayThis (s); }
};

...

case "QI":  dataStorage.queryById(command[0], displayer); 

或类似的东西。 (您必须将final 添加到ui 的声明中。)这里的效果是您仍然可以使queryById 成为一个空函数(如果要显示多个字符串,这很有用),但是您不会将有关如何显示的信息硬连接到DataStorage,因为它确实不属于那里。你的直觉认为处理UserInterface 的东西应该放在一个地方,而不是分散在几个班级中,这是一个非常好的直觉。恭喜。 (P.S. 我并不是说DisplayMethod 是个好名字。命名是我的弱点之一。)

【讨论】:

  • 我认为您想删除queryById 中调用UserInterface 构造函数的行。
  • 感谢您发现这一点。固定。
【解决方案2】:

我不喜欢你老师的解决方案

从数据存储中查询某些内容和写入文件(显然是应用程序用户界面)是两件不同的事情,您的 queryById 方法不应该同时做这两个事情。您在该方法中创建了 UserInterface 对象这一事实使情况变得更糟,但这可以通过将其传递给构造函数并将其存储在 (final) 字段中来解决。

你的老师建议这样做的原因可能是因为她赞成告诉,不要问原则。请参阅 Martin Fowler 的 Bliki 以获得对该原理的一个很好的解释:http://martinfowler.com/bliki/TellDontAsk.html 他还解释了为什么他不使用它。正如您正确观察到的那样:您的 queryById 方法不会返回值。实际上,根据定义,它不再是查询方法,但查询方法并不总是坏的。

我的建议

您的DataStore 类代表模型,您的UserInterface 类代表视图。从您的第一个解决方案开始,添加另一个类来表示 控制器,它调用 DataStoreUserInterface,这两者都通过您的 main 方法在构造函数中传递,否则空的。由于逻辑现在在控制器中而不是 main 方法中,因此它是可测试的(DataStoreUserInterface 可能都需要 Test Doubles)。

【讨论】:

  • “告诉不要问”在这种情况下可以正常工作,无需耦合,如果我们通过接口对象告诉 DataStorage 要做什么。要么这种情况的老师不明白如何正确使用原理,要么这实际上是一个大多数学生都远远落后于我们的OP的课程,如果他们不得不使用回调之类的复杂东西,他们的脑袋就会爆炸。跨度>
  • 这是一个中级本科CS课程,所以也许吧。大声笑
  • @ajb 我仍然认为将结果写入数据存储中的查询方法更简单更好:毕竟您不是在询问课程的任何私人细节,而是在询问无论如何,它将通过回调共享的完全相同的信息。实际上,告诉某人告诉你某事和询问并没有太大区别。
【解决方案3】:

根据您提供的信息,似乎第一种方式设计得更灵活。很少有专业人士编写存储层直接与 UI 层对话的系统。

您的第一个示例似乎遵循 MVC 模式。第二个例子似乎更像是 GoF 命令模式。

两者都行,只是可维护性的问题。

但是,OO 设计就是将整个程序的关注点分离成更小的内聚单元。

【讨论】:

  • 我将不得不更多地研究 MVC 和 GoF。谢谢。
【解决方案4】:

您应该知道的第一件事是,这两种方式都会“起作用”,这意味着您将完成您打算做的事情,即从数据存储中检索某些内容并将其显示在 UI 中。

但是,仅仅“工作”并不总是最好的。在现实世界的应用程序中,像我这样的工程师关心可维护性。简而言之,可维护性意味着在未来的某个时候,我可能不得不回到这段代码并添加功能,或者改变某些工作的方式(与您编写代码的学校项目相比,提交它,然后再也看不到它或再次使用它)。如果我有一个编写良好的组件,它仅在必要的地方依赖于其他组件,那么我可以轻松地修改和测试所述组件,并确信我不会改变其他组件的行为。

回到你的问题——你提出的第一个方法有一个 DataStorage 类,它有一个方法 queryById,它接受一个参数并返回一个值。它对显示组件没有任何依赖关系。在我看来,这是构建代码的正确方法。依赖项越少越好——更容易维护,也更容易编写测试。如果您不相信我,请尝试使用 JUnit 或其他测试框架编写单元测试,以实现 queryById 方法的两种方式——您会发现您的方法更易于测试,因为您不必模拟或注入或创建 UI 组件的实例。

【讨论】:

  • 好吧,我也是这么想的。谢谢,信息量很大。
  • 我不同意:你不是为方法编写测试,而是为应用程序的特性编写测试。将事物移至您的主要方法会使它们更难测试。
  • @herman 您可能将单元测试与验收/集成测试混淆了。线索就在名称中:单元测试单独测试基本的units代码。这些单元之间的耦合越少,这就越容易。
  • 1.不,我不是。与流行的看法相反,在 TDD 中,单元测试不是针对特定方法/类的测试。这是一个独立于其他测试运行的测试(即对其他测试没有副作用)。集成测试是跨端口的测试(例如数据库访问、Web 服务调用等)。Ian Cooper 在 Vimeo 上有一个很好的演讲,他解释了大多数人如何解释/做 TDD 错误:vimeo.com/68375232 2. 你会如何“单元测试”(根据您的定义)移动到主方法的逻辑?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多