Хорошо известное эвристическое правило, называемое законом Деметры[25], гласит, что модуль не должен знать внутреннее устройство тех объектов, с которыми он работает. Как мы видели в предыдущем разделе, объекты скрывают свои данные и предоставляют операции для работы с ними. Это означает, что объект не должен раскрывать свою внутреннюю структуру через методы доступа, потому что внутреннюю структуру следует скрывать.
В более точной формулировке закон Деметры гласит, что метод f класса C должен ограничиваться вызовом методов следующих объектов:
• C;
• объекты, созданные f;
• объекты, переданные f в качестве аргумента;
• объекты, хранящиеся в переменной экземпляра C.
Метод не должен вызывать методы объектов, возвращаемых любыми из разрешенных функций. Другими словами, разговаривать можно с друзьями, но не с чужаками.
Следующий код нарушает закон Деметры (среди прочего), потому что он вызывает функцию getScratchDir() для возвращаемого значения getOptions(), а затем вызывает getAbsolutePath() для возвращаемого значения getScratchDir().
>final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Подобная структура кода часто называется «крушением поезда», потому что цепочки вызовов напоминают сцепленные вагоны поезда. Такие конструкции считаются проявлением небрежного стиля программирования и их следует избегать [G36]. Обычно цепочки лучше разделить в следующем виде:
>Options opts = ctxt.getOptions();
>File scratchDir = opts.getScratchDir();
>final String outputDir = scratchDir.getAbsolutePath();
Нарушают ли эти два фрагмента закон Деметры? Несомненно, вмещающий модуль знает, что объект контекста ctxt содержит значения параметров, в число которых входит и временный каталог, обладающий абсолютным путем. Это довольно большой объем информации для одной функции. Вызывающая функция должна знать, как перемещаться между множеством разных объектов.