Напомню, что Open-closed принцип призывает программистов проектировать классы таким образом, чтобы они были открыты для расширения, но при этом закрыты для модификации. Модифицировать класс разрешается только в том случае, если в классе обнаружена ошибка. Если же требуется добавить функциональность, то принцип призывает создать новый класс и воспользоваться либо наследованием, либо реализацией того же самого интерфейса.
Например, существует система управления посылками. Допустим, пришла задача добавить возможность создавать помимо простых посылок, еще и срочные.
Имеем класс Parcel, который описывает работу обычной посылки:
public interface IParcel { string Barcode {get; set;} } public class Parcel: IParcel { public string Barcode {get; set;} }
Очень соблазнительно просто добавить поле и в старый класс Parcel, и в интерфейс IParcel:
public interface IParcel { string Barcode {get; set;} bool IsUrgent {get; set;} } public class Parcel: IParcel { public string Barcode {get; set;} public bool IsUrgent {get; set;} }
Однако, такой код не должен проходить CodeReview! Строгий и опытный «проверяльщик» кода должен вернуть его с замечанием: «такая реализация нарушает Open-closed принцип.»
Гораздо лучше создать новый класс UrgentParcel, и не нужно будет менять ни интерфейс, ни класс Parcel. Файлы класса и интерфейса останутся нетронутыми:
public class UrgentParcel: IParcel { public string Barcode {get; set;} }
Это будет соблюдением Open-closed принципа, и такой код не получит замечания при CodeReview.
Теперь давайте вернемся к DRY и к тому, каким образом он мешает реализовать Open-closed принцип.
Представим, что в классе Parcel у нас есть поле «статус посылки» и некая логика изменения этого статуса:
public class Parcel: IParcel { public string Barcode {get; set;} // Статус посылки (в пути, или уже доставлена и т.п.) public ParcelStatuses Status {get; } // метод, который меняет статус посылки на "доставлена" public void ArrivedToRecipient(){ this.Status = ParcelStatuses.Arrived; } }
Нужно ли эту логику копировать в класс UrgentParcel? Принцип DRY говорит, что ни в коем случае. Гораздо лучше, чтобы класс UrgentParcel просто наследовался от класса Parcel, что решит задачу и не придется Copy/Paste’ть тело метода ArrivedToRecipient в класс UrgentParcel.
Однако, если не копировать код, а наследовать его, то изменения метода ArrivedToRecipient в классе Parcel сразу же приведут к изменению поведения класса UrgentParcel, что и будет являться нарушением Open-closed принципа. Это действительно нарушение, потому что задача со срочными посылками (реализация класса UrgentParcel) уже была сдана, протестирована и, как следствие, работает «в бою». Значит, эта логика, реализованная в методе UrgentParcel.ArrivedToRecipient и примененная к срочным посылкам — всех устраивает и она НЕ должна меняться при изменении работы других видов посылок. Так вот Open-closed принцип как раз и предназначен защищать систему от подобных действий неопытных junior-программистов, которые, решая задачу, как обычно «в лоб», не осознают еще пока всех зависимостей, и их изменения в одном месте затрагивают многие другие функциональные области.
Обычно, одним из главных аргументов в пользу DRY является тот факт, что, если найдена ошибка в методе ArrivedToRecipient, то ее надо исправлять везде, куда ее скопировали. Так вот этого, как раз, делать не нужно. Если найдена ошибка в методе ArrivedToRecipient при работе с обычными посылками, то и исправлять надо именно работу обычных посылок. А на работу срочных посылок никто не жаловался и, вероятно, всех устраивает, как срочные посылки работают.
Для перфекционистов, к коим я себя тоже причисляю, я бы предложил оставить комментарий, который позволил бы не забыть про все те места, куда копировался метод, и помочь поднять вопрос: а правильно ли у нас работает этот метод для срочных посылок.
Вот пример такого комментария:
public class Parcel: IParcel{ ... /// <summary> /// Проставляет посылке статус "доставлена" /// </summary> /// <remarks>NOTE: код метода копировался в классы: <see cref="UrgentParcel"/></remarks> public void ArrivedToRecipient(){ ... } } public class UrgentParcel: IParcel{ ... /// <summary> /// Проставляет посылке статус "доставлена" /// </summary> /// <remarks>NOTE: код метода был скопирован из класса: <see cref="Parcel"/></remarks> public void ArrivedToRecipient(){ ... } }
Очень интересно мнение сообщества по этому вопросу. Заранее благодарен за ваши комментарии.
ссылка на оригинал статьи https://habr.com/post/423515/
Добавить комментарий