问题
在执行涉及多个对象的函数时,如何遵循"Tell, Don't Ask"原则。
示例 - 生成报告
我有以下对象(仅供说明之用):
汽车,马,兔
这些对象之间没有关系,但我确实希望根据这些对象生成报告:
createHtmlReport(Car car, Horse horse, Rabbit rabbit){
Report report = new Report()
report.setSomeField(car.getSerialNumber())
report.setAnotherField(horse.getNumberOfLegs())
// ...etc
}
此方法的问题在于它必须从每个对象“拉”数据,这违反了“告诉,不要问”规则。我宁愿隐藏每个对象的内部,让它们为我生成一个报告:
car.createHtmlReport()
horse.createHtmlReport()
rabbit.createHtmlReport()
...但随后我收到3份部分报告。此外,我认为Rabbit不应该知道如何生成我需要的每个报告(HTML,JMS,XML,JSON ....)。
最后,在生成报告时,我可能想要打开多个项目:
if (car.getWheels() == 4 || horse.getLegs() == 4)
// do something
答案 0 :(得分:8)
报告应该保持创造自我的能力。
在这种情况下,每个IReportable
对象都应实现void UpdateReport(Report aReport)
。
当调用Report.CreateReport(List<Reportable> aList)
时,它会遍历List并且它自己的UpdateReport
实现中的每个对象都会调用:
aReport.AddCar(serialNumber)
aReport.AddHorse(horseName)
在CreateReport
结束时,报表对象应该生成自己的结果。
答案 1 :(得分:6)
“告诉不要问”规则的目标是帮助您确定应该对给定对象承担责任的情况最终在其外部实施(坏事)。
在您的案例中我们可以看到什么责任?我看到的是:
1)知道如何格式化报告(以xml,ascii,html等)
2)了解报告的内容
第一个显然不属于域对象(Car,Horse等)。 2)应该去哪里?可以建议域对象,但如果您的系统中有多个不同的报告,您最终会使用不同报告详细信息的知识来增加对象的负担,这些信息看起来和味道都很糟糕。更不用说它会违反单一责任原则:作为一个兔子是一回事,但知道哪些部分的兔子信息应该在报告X上与报告Y相对应是另一回事。
因此,我将设计封装数据内容的类,这些数据内容包含在特定类型的报告上(并可能执行必要的计算)。我不担心他们阅读兔子,马或汽车的数据成员。这个类实现的职责是“收集特定类型报告的数据”,你有意识地决定这些数据应该位于域对象之外。
答案 2 :(得分:3)
这正是Visitor Pattern的用途。
答案 3 :(得分:1)
我不确切知道这个模式的名称(访客,构建器......):
public interface HorseView {
void showNumberOfLegs(int number);
}
public interface CarView {
void showNumberOfWheels(int number);
void showSerialNumber(String serialNumber);
}
public class Horse {
void show(HorseView view) {
view.showNumberOfLegs(this.numberOfLegs);
}
}
public class Car {
void show(CarView view) {
view.showNumberOfWheels(this.numberOfWheels);
view.showSerialNumber(this.serialNumber);
}
}
public class HtmlReport implements HorseView, CarView {
public void showNumberOfLegs(int number) {
...
}
public void showNumberOfWheels(int number) {
...
}
public void showSerialNumber(String serialNumber) {
...
}
}
public XmlModel implements HorseView, CarView {
...
}
public JsonModel implements HorseView, CarView {
...
}
通过这种方式,您可以对同一个域对象进行多次表示,而不是违反“Tell do not ask”原则。