3

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

DataStorage我在 内部创建了一个类的对象,UserApp我用它来调用一个方法,该方法将字符串值返回给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 的做事方式。我给她发了电子邮件,问她这是否正确。她说在inui.displayThis内部调用... 这意味着我需要在类中创建一个对象,或者将对象作为参数传递给. 如果我像她说的那样做,该方法不会返回字符串,而是无效的。例如:listById()DataStorageUserInterfaceDataStorageDataStoragelistById()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是更好的方法吗?

4

4 回答 4

3

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

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

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

于 2013-09-23T21:44:22.193 回答
3

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

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

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

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

于 2013-09-23T21:46:00.360 回答
0

如果是我,我会很好地拥有listById一个 void 函数来显示(而不是返回 a 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成为一个 void 函数(如果要显示多个字符串,这很有用),但您不需要硬连线有关如何在 中显示的信息DataStorage,因为它确实不属于那里。你的直觉认为要处理的东西UserInterface应该放在一个地方,而不是分散在几个班级,这是一个非常好的直觉。恭喜。(PS 我并不是说这DisplayMethod是一个好名字。命名事物是我的弱点之一。)

于 2013-09-23T22:01:19.277 回答
0

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

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

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

我的建议

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

于 2013-09-23T23:08:46.173 回答