Нужно ли виртуальные методы объявлять как protected?
1,00
р.
р.
Коллеги, я не вполне понимаю одну из рекомендаций в .NET design guidelines. В ней говорится: DO prefer protected accessibility over public accessibility for virtual members. Public members should provide extensibility (if required) by calling into a protected virtual member. The public members of a class should provide the right set of functionality for direct consumers of that class. Virtual members are designed to be overridden in subclasses, and protected accessibility is a great way to scope all virtual extensibility points to where they can be used. то есть Предпочитайте делать виртуальные члены (например, методы) защищёнными, а не открытыми. Открытые члены класса должны обеспечивать расширяемость (если нужно) путём вызова защищённого виртуального члена. Открытые члены класса должны давать нужную, правильную функциональность для клиентского кода. Виртуальные члены разрабатываются чтобы быть переопределёными в классах-потомках, и защищённый доступ — хороший метод для того, чтобы ограничить видимость мест для расширения только теми, кто будет её использовать. Прочитав этот текст, я всё же не понимаю, какая проблема может быть в том, если семейство виртуальных функций будет объявлено открытым. Например, вот в таком коде: class Human : IDisposable { IDisposable property = new Property() public virtual Dispose() { property.Dispose() } } class Spy : Human { IDisposable spyGadgets = new SpyGadgets() public virtual Dispose() { base.Dispose() spyGadgets.Dispose() } } Какие могут быть проблемы с таким кодом? О чём меня пытается предупредить документация? Если с этим случаем всё хорошо, то в каком случае возможны проблемы? Приведите, если можно, осмысленный пример с кодом (не с классами Foo и Bar).
Огромное спасибо за ответы! Мне было нелегко выбрать, какой из них отметить галочкой, потому что все ответы очень хороши, и проливают свет на проблему с разных сторон.
Ответ Это же рекомендации для разработчиков фреймворков. Очевидно, что разработчики фреймворков будут выпускать новые версии своих фреймворков. Также понятно, что одной из важнейших задач для них - сохранение обратной совместимости, по мере возможностей. А значит, что клиентов, что используют их код, следует максимально ограничить. То есть, это мы (ну или только я такой рукожоп) привыкли писать классы наследуемыми - но если мы пишем фреймворк, то нужна достаточно веская причина сделать класс наследуемым. Также нужна веская причина сделать метод виртуальным. Но вот ты сделал публичный метод виртуальным, и теперь клиенты могут наследоваться, перегрузить метод и запускать сами написанный ими же код, используя наш фреймвок - и мы уже не можем это контролировать. Я к тому, что делая публичный метод виртуальным, мы даем право клиенту решать, что будет делать наш АПИ и мы уже никак не можем ничего изменить, не сломав обратную совместимость. Однако, сделав защищенный метод виртуальным, мы не гарантируем клиенту, что этот метод не перестанет использоваться в будущем, если наш публичный АПИ будет изменен. Таким образом клиенты, что перегрузили защищенный метод, сохраняют обратную совместимость, даже если логика публичных методов была изменена. Походу надо добавить пример, хотя я и не мастер примеров :) Допустим есть следующие классы: public class CsvWriter1 { protected void WriteHeader(TextWriter stream) protected void WriteBody(IEnumerable obj, TextWriter stream) protected virtual void WriteInternal(IEnumerable obj, TextWriter stream) { WriteHeader(stream) WriteBody(obj, stream) } public void Write(IEnumerable obj, TextWriter stream) { WriteInternal(obj, stream) } } public class CsvWriter2 { protected void WriteHeader(TextWriter stream) protected void WriteBody(IEnumerable obj, TextWriter stream) public virtual void Write(IEnumerable obj, TextWriter stream) { WriteHeader(stream) WriteBody(obj, stream) } } Теперь представим, что в следующей версии нашего чудесного фреймворка нам надо писать объекты в CSV без заголовка. То есть, заголовок больше не нужен. И переопределять это больше нельзя. Что делать? Для класса CsvWriter1 все просто public class CsvWriter1 { protected void WriteHeader(TextWriter stream) protected void WriteBody(IEnumerable obj, TextWriter stream) protected virtual void WriteInternal(IEnumerable obj, TextWriter stream) { WriteHeader(stream) WriteBody(obj, stream) } protected void WriteInternalNew(IEnumerable obj, TextWriter stream) { WriteBody(obj, stream) } public void Write(IEnumerable obj, TextWriter stream) { WriteInternalNew(obj, stream) } } В случае с классом CsvWriter2 мы в луже. Так как указанное изменение для него будет обрано несовместимым, и конечно сломает логику клиентских классов.