Скажите, не спрашивайте по нескольким объектам домена

Вопрос

Как придерживаться принципа "Говори, не спрашивай" при выполнении функции, включающей несколько объектов.

Пример — создание отчета

У меня есть следующие объекты (только для иллюстративных целей):

Автомобиль, Лошадь, Кролик

Между этими объектами нет связи, но я хочу создать отчет на основе этих объектов:

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 частичных отчета. Кроме того, я не думаю, что Кролик должен знать, как генерировать каждый отчет, который мне нужен (HTML, JMS, XML, JSON....).

Наконец, при создании отчета я могу включить несколько элементов:

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

person djcredo    schedule 09.03.2012    source источник
comment
+1 и избранное за ссылку и вопрос.   -  person knownasilya    schedule 07.12.2012


Ответы (4)


Отчет должен поддерживать возможность создания самого себя.

В этом случае каждый объект IReportable должен реализовывать void UpdateReport(Report aReport).

Когда вызывается Report.CreateReport(List<Reportable> aList), он выполняет итерацию по списку, и каждый объект в своей собственной реализации UpdateReport вызывает:

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

В конце CreateReport объект отчета должен выдать свой собственный результат.

person lostinplace    schedule 09.03.2012
comment
Посетитель и правило двойной отправки! - person Yves Reynhout; 15.03.2012
comment
чтобы было ясно, Report должны быть реализованы AddCar и AddHorse? я предполагаю, что эти имена методов взяты только для примера, но они очень вводят в заблуждение и. на самом деле, я потратил минут 10 только на то, чтобы понять, что эти методы не имеют ничего общего с самими типами Car и Horse o_O - person jungle_mole; 18.05.2015

Цель правила "Говорить, а не спрашивать" – помочь вам определить ситуации, когда ответственность, которая должна лежать на данном объекте, в конечном итоге реализуется за его пределами (плохая вещь).
Какие обязанности мы можем увидеть в вашем случае ? Я вижу следующее:

1) знание того, как форматировать отчет (в xml, ascii, html и т. д.)
2) знание того, что происходит в каком отчете

Первый, очевидно, знает не принадлежат объекту домена (автомобиль, лошадь и т.д.). Куда следует 2) идти? Можно было бы предложить объект домена, но если в вашей системе есть несколько разных отчетов, вы в конечном итоге обременяете свои объекты знаниями о разных деталях отчета, которые будут выглядеть и пахнуть плохо. Не говоря уже о том, что это нарушило бы принцип единой ответственности: быть Кроликом — это одно, а знать, какие части информации о Кролике должны быть в отчете X, а какие — в отчете Y, — это совсем другое. Таким образом, я бы разработал классы, которые инкапсулируют содержимое данных, которые используются в отчете определенного типа (и, возможно, выполняют необходимые вычисления). Я бы не беспокоился о том, что они читают элементы данных Rabbit, Horse или Car. Ответственность, которую реализует этот класс, заключается в «сборе данных для определенного типа отчета», который, как вы сознательно решили, должен лежать вне объекта предметной области.

person ppietrus    schedule 10.03.2012

Именно для этого предназначен шаблон посетителя.

person Terry Wilcox    schedule 10.03.2012

Я не знаю точного имени этого шаблона (Посетитель, Строитель,...):

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

Таким образом, вы можете иметь несколько представлений одного и того же доменного объекта, не нарушая принцип «Говори, не спрашивай».

person user2014029    schedule 27.01.2013