2016-12-21 10 views
0

Я читал о Принципе замещения Лискова (LSP), и я немного смущен тем, как вы придерживаетесь его правильно. Особенно, когда используются интерфейсы и подклассы.C# LSP Конструктор Параметры и защитные оговорки

Например, если у меня есть базовый класс:

public abstract class AccountBase 
{ 
    private string primaryAccountHolder; 

    public string PrimaryAccountHolder 
    { 
     get { return this.primaryAccountHolder; } 
     set 
     { 
      if (value == null) throw ArgumentNullException("value"); 
      this.primaryAccountHolder = value; 
     } 
    } 

    public string SecondaryAccountHolder { get; set; } 

    protected AccountBase(string primary) 
    { 
     if (primary == null) throw new ArgumentNullException("primary"); 
     this.primaryAccountHolder = primary; 
    } 
} 

Теперь, скажем, у меня есть два счета, которые наследуют от базового класса. Тот, который ТРЕБУЕТ SecondaryAccountHolder. Добавление нулевой защиты к подклассу является нарушением LSP, правильно? Итак, как я мог бы создавать свои классы таким образом, чтобы они не нарушали LSP, но для одного из моих подклассов требуется дополнительный владелец учетной записи, а другой нет?

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

И у меня тот же вопрос с интерфейсами. Если у меня есть интерфейс:

public interface IPrintsSomething 
{ 
    void PrintSomething(string text); 
} 

это не будет нарушением LSP добавить нулевой пункт охраны для текста любого класса, который реализует IPrintsSomething? Как вы защищаете свои инварианты? Правильно ли это слово? : p

ответ

0

Так как же я буду создавать свои классы таким образом, чтобы они не нарушали LSP, но один из моих подклассов требует дополнительного владельца учетной записи, а другой нет?

Выход из этой проблемы заключается в устранении этой изменчивости на контракт базового класса. Это может выглядеть следующим образом (ненужные детали реализации опущены):

public abstract class AccountBase 
{ 
    public string PrimaryAccountHolder 
    { 
     get { … } 
     set { … } 
    } 

    public string SecondaryAccountHolder 
    { 
     get { … } 
     set 
     { 
      … 
      if (RequiresSecondaryAccountHolder && value == null) throw …; 
      … 
     } 
    } 

    public abstract bool RequiresSecondaryAccountHolder { get; } 
} 

Тогда вы не нарушая LSP, так как пользователь AccountBase может определить, есть ли они или не иметь, чтобы обеспечить значение SecondaryAcccountHolder.

И у меня тот же вопрос с интерфейсами. ... Разве это не было бы нарушением LSP для добавления предложения null guard для текста в любом классе, который реализует IPrintsSomething?

Сделать валидацию очевидной частью контракта с интерфейсом. Как? Документ о том, что исполнитель должен забрать значение text за null.

+0

Для примера AccountBase, как это работает с конструктором? Это нарушает LSP, чтобы иметь конструктор в подклассе, который требует SecondaryAccountHolder, когда конструктор базового класса этого не делает? –

+0

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

+0

Но если вы прямо назначаете свойства, то вы не гарантируете, что ваш объект находится в допустимом состоянии. Например, если я просто поместил свойство «RequiresSecondaryAccountHolder» в базовый класс, то это только гарантирует, что свойство не может быть установлено в null, что не гарантирует, что свойство действительно установлено. Таким образом, не требуется ли использование конструктора производного класса для сохранения объекта в допустимом состоянии? Нарушает ли LSP разные конструкторы на производных классах? –

1

Вы должны исследовать сказать-не-спросить, и разделение команды/запрос, вы могли бы начать здесь: https://pragprog.com/articles/tell-dont-ask

Вы должны стараться, чтобы сказать, что все объекты, которые вы хотите, чтобы они делали; не задавайте им вопросы об их состоянии, принимайте решение, а затем рассказывайте им, что делать.

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

Вместо того, чтобы просить его и принимать решения, как это:

string holders = account.PrimaryAccountHolder; 
if (accountHolder.SecondaryAccountHolder != null) 
{ 
    holders += " " + accountHolder.SecondaryAccountHolder; 
} 

рассказывайте:

string holders = account.ListAllHoldersAsAString(); 

В идеале, вы на самом деле сказать ему, что вы на самом деле хотите сделать с этой строки:

account.MailMergeAllAccountHoldersNames(letterDocument); 

Теперь логика для работы с двумя держателями счетов находится в подклассе. Может быть один, два или n владельцы учетных записей, код вызова не заботится и не должен знать.

Что касается LSP, хорошо, если есть формально (или неофициально) документированный контракт, в котором говорится, что клиенты должны проверить на null на втором держателе с самого начала, тогда это нормально. Это нехорошо, но любые исключения с нулевым указателем будут ошибкой клиента за неправильное использование класса. (Обратите внимание, что неверно, что добавление логического свойства улучшается, это может быть немного более удобочитаемо, т. Е. Кто-нибудь проверяет IList.IsReadOnly, прежде чем писать ему ?!).

Однако, если вы начали с двойным счетом держателя, а затем добавил, что условие, что второй владелец счета может быть null позже для одной учетной записи, то вы изменили контракт и экземпляр сингла может нарушить существующее код. Если вы полностью контролируете все места, где используются учетные записи, тогда вам разрешено это делать, если это общедоступный api , который вы меняете, это другое дело.

Но tell-don't-ask избегает всей проблемы в этом случае.

 Смежные вопросы

  • Нет связанных вопросов^_^