Say Don’t Ask for Multiple Domain Objects

Question

How can I adhere to the principle of "Report, do not ask" when executing a function with multiple objects.

Example - Reporting

I have the following objects (for illustration only):

Car, horse, rabbit

There is no connection between these objects, but I want to create a report based on these objects:

createHtmlReport(Car car, Horse horse, Rabbit rabbit){ Report report = new Report() report.setSomeField(car.getSerialNumber()) report.setAnotherField(horse.getNumberOfLegs()) // ...etc } 

The problem with this method is that it must "pull" data from each object, which violates the "Report, Do Not Ask" rule. I would rather keep the insides of each object hidden and make them generate a report for me:

 car.createHtmlReport() horse.createHtmlReport() rabbit.createHtmlReport() 

... but then I get 3 partial reports. Also, I don’t think that Rabbit should know how to generate every single report that I need (HTML, JMS, XML, JSON ....).

Finally, when creating a report, I can include several elements:

 if (car.getWheels() == 4 || horse.getLegs() == 4) // do something 
+7
source share
4 answers

The report must support the ability to create self.

In this case, each IReportable must complete void UpdateReport(Report aReport) .

When Report.CreateReport(List<Reportable> aList) , it Report.CreateReport(List<Reportable> aList) through the List, and each object in its UpdateReport implementation calls:

 aReport.AddCar(serialNumber) aReport.AddHorse(horseName) 

At the end of CreateReport the report object must create its own result.

+8
source

The purpose of the Tell not ask rule is to help you identify situations where the responsibility that should lie with a given object falls outside of it (a bad thing).
What responsibilities can we see in your case? I see:

1) knowing how to format the report (in xml, ascii, html, etc.)
2) knowing what is happening with this report

The first, obviously, does not belong to the domain object (Car, Horse, etc.). Where 2) to go? You could suggest a domain object, but if your system has several different reports, you end up burdening your objects with knowledge of the different report details that will look and smell bad. Not to mention that this violates the principle of a single responsibility: to be a rabbit is one thing, but to know what parts of the information about a rabbit should come in report X, and report Y is completely different. Thus, I would develop classes that encapsulate the contents of the data that go to a certain type of report (and possibly perform the necessary calculations). I would not worry that they read the data of the members of the Rabbit, Horse or Car. The responsibility of this class is that the “data collection for a certain type of report” that you consciously decided should lie outside the domain object.

+6
source

This is exactly what the visitor template is for.

+3
source

I definitely don’t know this template name (Visitor, Builder, ...):

 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 { ... } 

Thus, you can have multiple representations of the same domain object, and not violate the principle of "Tell do not ask".

+1
source

All Articles