Как изменить код так, чтобы он соответствовал закону Деметры

public class BigPerformance  
{  
    public decimal Value { get; set; }
}  

public class Performance  
{  
    public BigPerformance BigPerf { get; set; }
}  

public class Category    
{  
    public Performance Perf { get; set; }     
}

Если я позвоню:

Category cat = new Category();  
cat.Perf.BigPerf.Value = 1.0;  

Я предполагаю, что это нарушает Закон Деметры / Принцип Наименьшего Знания?
Если так, как я могу исправить это, если у меня есть большое количество свойств внутреннего класса?

5 ответов

Одна из моих любимых цитат от Мартина Фаулера:

Я бы предпочел, чтобы его называли "Случайно полезное предложение Деметры".

http://haacked.com/archive/2009/07/14/law-of-demeter-dot-counting.aspx

Если вы говорите о Законе Деметры как в "не называйте соседей соседями", вы можете делегировать его другим методам, которые делают то, что вы хотите.

Из вашего примера, я думаю, вы хотите сбросить значение производительности или что-то в этом роде. Вы можете изменить пример кода так, чтобы он был по своей сути вместо этого:

Category cat = new Category();

cat.resetPerf();

Код был бы чем-то похожим на это:

public class BigPerformance 
{
    //constructors 'n stuff

    public static decimal DEFAULT;

    public decimal Value {get; private set;}

    public void reset() {
        Value = BigPerformance.DEFAULT;
    }
}

public class Performance
{
    //constructors 'n stuff

    private BigPerformance BigPerf {get; set};

    public reset() {
        BigPerf.reset();
    }
}

public class Category
{
    // constructors 'n stuff

    public Performance Perf {get; private set;}

    public resetPerformance() {
        Perf.reset();
    }
}

Таким образом, Category Класс не должен знать, как сбросить значение в случае, если значение по умолчанию является чем-то другим или его тип будет изменен в будущем.

Лично, если риск для изменений низок, я бы вместо этого пошел на ответ Юхарра.

Category cat = new Category();  
cat.Perf.BigPerf.Value = 1.0;  

является

Category cat = new Category();  
cat.GetPerf().GetBigPerf().SetValue(1.0);  

So it is breaking the rules if the wikipedia definition is correct:

..[M]ethod M of an object O may only invoke the methods of the following kinds of objects:

  • О себе
  • Параметры М
  • любые объекты, созданные / созданные в M
  • Прямые составляющие объекты О
  • глобальная переменная, доступная O, в области M

In particular, an object should avoid invoking methods of a member object returned by another method

If you are worried about the 3 being tightly coupled, then remove the public accessors and add a method on Category to set the value. Then refactor Performance and BigPerformance to be private members.

Если вы всегда помните о своих классах и используете IoC, вы заметите, что вам больше не нужно беспокоиться о LoD.

Рассмотрим этот вариант

Как я собираюсь проверить Category ? Я не хочу, чтобы это автоматически создавало Performance это использует медленную файловую систему. Давайте пройдем IPerformance Через Category и заменить фактическую реализацию на пустышку Performance пример.

Как я собираюсь проверить Performance ? Я не хочу, чтобы это автоматически создавало BigPerformance сделать подключение к базе данных. Давайте пройдем IBigPerformance Через Performance и заменить фактическую реализацию на пустышку BigPerformance пример.
...
Вы, очевидно, заметили закономерность

Ваш код будет в строке

BigPerformance BigPerf = new BigPerformance();
BigPerf.Value := 1.0;
Performance Perf = new Performance(BigPerformance);
Category cat = new Category(Performance);

(This would be retrieved from a factory.)

Похоже (а в краткосрочной перспективе это так), похоже, гораздо больше работы, но преимущества в конечном итоге окупятся, если вы сможете тестировать свои классы изолированно.

Загляните в блог Миско Хевери, чтобы узнать больше об этой и других темах.

Это не нарушает закон Деметры, потому что вы используете публичный контракт классов.

Другие вопросы по тегам