在我的课堂上,我们要创建一个包含多个Main类和共享类的项目。在一个名为UserApp
的特定主类中,我创建了一个类UserInterface
的对象,它将直接与一个名为Log.txt的文件进行交易。
我在DataStorage
内创建了一个类UserApp
的对象,我用它来调用一个将String值返回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方式。我给她发了电子邮件,问她是否正确使用。她说要在ui.displayThis
中的listById()
内拨打DataStorage
...这意味着我需要在UserInterface
课程中创建一个DataStorage
对象,或将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
会更好吗?
答案 0 :(得分:3)
你应该知道的第一件事是两种方式都可以“工作”,这意味着你完成了你想要做的事情,即从数据存储中检索某些内容并将其显示在UI中。 / p>
然而,获得“工作”的东西并不总是最好的。在实际应用中,像我这样的工程师关注可维护性。简单地说,可维护性意味着在未来的某个时刻,我可能不得不回到这段代码并添加功能,或改变某些方式的工作方式(与您编写代码的学校项目相比,提交它,然后永远不会看到它或再次使用它)。如果我有一个编写良好的组件,只有在必要的地方依赖于其他组件,那么我可以轻松修改和测试所述组件,并确信我不会改变其他组件的行为。
回到你的问题 - 你提出的第一个方法有一个DataStorage类,它有一个方法queryById,它接受一个参数并返回一个值。它与显示组件没有任何依赖关系。在我看来,这是构建代码的正确方法。依赖性越少越好 - 它更容易维护,并且更容易编写测试。如果您不相信我,尝试使用JUnit或其他测试框架编写单元测试,以实现queryById方法的两种方式 - 您会发现您的方法更容易测试,因为您不必模拟或者注入或创建UI组件的实例。
答案 1 :(得分:3)
根据您提供的信息,第一种方式似乎更灵活。专业人士很少会编写存储层直接与UI层对话的系统。
您的第一个示例似乎遵循MVC模式。第二个例子似乎更像是GoF Command模式。
要么可行,这只是一个可维护性的问题。
然而,面向对象设计的目的是将整个计划的关注点分成较小的内聚单元。
答案 2 :(得分:0)
如果是我,我可以将listById
作为一个无效函数进行显示(而不是返回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
的东西应该放在一个地方而不是分散在几个类上的本能是一个非常好的本能。恭喜。 (P.S.我并不是说DisplayMethod
是一个好名字。命名事物是我的弱点之一。)
答案 3 :(得分:0)
从数据存储中查询并写入文件(显然是应用程序用户界面)是两回事,而queryById
方法不应同时执行这两项操作。在该方法中创建UserInterface
对象的事实使其更糟糕,但可以通过在构造函数中传递并将其存储在(final
)字段中来解决。
你的老师提出这个问题的原因,可能是因为她赞成 Tell,Do not Ask 原则。请参阅Martin Fowler的Bliki对这一原则的一个很好的解释:http://martinfowler.com/bliki/TellDontAsk.html他也解释了为什么他不使用它。正确观察到:您的queryById
方法不会返回值。实际上,根据定义它不再是一个查询方法,但查询方法并不总是坏的。
您的DataStore
课程代表模型,您的UserInterface
课程代表视图。从第一个解决方案开始,添加另一个类来表示控制器,它调用DataStore
和UserInterface
,这两个类都由{{1}在构造函数中传递方法,否则为空。由于逻辑现在位于控制器而不是main
方法,因此它是可测试的(main
和DataStore
可能需要Test Doubles。