"Diga, no pregunte" sobre varios objetos de dominio

Pregunta

How do I adhere to the "Dile, no preguntes" principle when performing a function involving multiple objects.

Example - Generating a Report

I have the following objects (illustrative purposes only):

Car, Horse, Rabbit

There is no relationship between these objects, but I do want to generate 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 has to "Pull" data from each object, which violates the "Tell, Don't Ask" rule. I would rather keep the insides of each object hidden, and have them generate a report for me:

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

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

Finally, whilst generating the report I may want to switch on multiple items:

if (car.getWheels() == 4 || horse.getLegs() == 4)
    // do something

preguntado el 09 de marzo de 12 a las 15:03

+1 & fav for link and question. -

4 Respuestas

The report should maintain the ability to create its self.

In this case, each IReportable object should Implement void UpdateReport(Report aReport).

Cuando te Report.CreateReport(List<Reportable> aList) is invoked, it iterates through the List and each object in its own implementation of UpdateReport invoca:

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

Al final de los CreateReport, the report object should produce its own result.

respondido 09 mar '12, 16:03

Visitor & double dispatch rule! - Yves Reynout

para ser claro, Report debe tener AddCar y AddHorse implemented? i assume these method names are taken just for example, but they are very misleading and. actually, i wasted like 10 minutes just to understand that these methods have nothing to do with Car and Horse types themselves o_O - jungla_mole

The goal of "Tell don't ask" rule is to help you identify situations where the responsibility that should lie with the given object ends up being implemented outside of it (bad thing).
What responsibilities can we see in your case? What I see is:

1) knowing how to format the report (in xml, ascii, html, etc)
2) knowing what goes on which report

First one obviously does not belong with the domain object (Car, Horse etc.). Where should the 2) go? One could suggest the domain object but if there are multiple different reports in your system you end up burdening your objects with knowledge about differnt report details which would look and smell bad. Not to mention that it would violate the Single Responsibility Principle: being a Rabbit is one thing but knowing which parts of Rabbit information should go on report X vs. report Y is quite another. Thus I would design classes which encapsulate data contents that go on a specific type of report (and possibly perform necessary calculations). I would not worry about them reading the data members of Rabbit, Horse or Car. The responsibility this class implements is 'gathering the data for a specific type of a report' which you've consciously decided should lie outside of the domain object.

respondido 10 mar '12, 03:03

Eso es exactamente lo que Patrón de visitante es para.

respondido 10 mar '12, 03:03

I don't know exactly this pattern's 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 {
    ...
}

This way you can have multiple representations of the same domain object, not violating "Tell don't ask" principle.

Respondido el 27 de enero de 13 a las 19:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.