Пустой виртуальный метод вместо интерфейса
Иногда встречаю такую конструкцию, что в обычном, не абстрактном базовом классе есть пустой виртуальный метод, который явно нужен, чтобы его перегрузил потомковый класс. А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным? Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят. Если делать просто пустой метод, то его могут не перегрузить - ничего же не обязывает по синтаксису языка. Если же это такая задумка, что метод необязательно перегружать, то значит, что он не во всех потомках и нужен. Тогда смысл деражть такой метод в базовом классе? Проще каждому потомку свой метод иметь?
Вопрос - может, всё же есть какой-то смысл именно в пустом базовом методе, не абстрактном, и без интерфейса?
Иногда встречаю такую конструкцию, что в обычном, не абстрактном базовом классе есть пустой виртуальный метод, который явно нужен, чтобы его перегрузил потомковый класс.
Да, бывает.
А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным?
Потому что не "нужно, чтобы перегрузил", а "нужно дать возможность перегрузить". Если бы было "нужно, чтобы перегрузил", то класс был бы абстратнным и метод тоже был бы абстрактным.
Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят.
Не получаешь :)
Если делать просто пустой метод, то его могут не перегрузить - ничего же не обязывает по синтаксису языка. Если же это такая задумка, что метод необязательно перегружать, то значит, что он не во всех потомках и нужен. Тогда смысл деражть такой метод в базовом классе? Проще каждому потомку свой метод иметь?
1) метод может быть частью интерфейса при этом имплементирующий этот интерфейс класс не является абстрактным.
2) метод может использоваться базовым классом
public interface IFoo { void Initialize (); void Start (); void Stop (); } public class DummyRunner : IFoo { public void Initialize () { 100500 различных инициализаций } public void Start () { сложный старт } public void Stop () { сложная остановка } protected virtual void TraceState () { } public override string ToString () { TraceState (); } } public class WorkerRunner : DummyRunner { protected override void TraceState () { base.TraceState (); // на случай, если база когда-нибудь захочет что-то записать в лог Пишем в лог 100500 параметров WorkerRunner'а } }
Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят.Не получаешь :)
Я имел ввиду имплементируют.
А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным?Потому что не "нужно, чтобы перегрузил", а "нужно дать возможность перегрузить".
Ну да, тут я не учёл.
Если бы было "нужно, чтобы перегрузил", то класс был бы абстратнным и метод тоже был бы абстрактным.
Тоже забыл, что абстрактные методы не имеют имплементации. А чем тогда они отличаются от методов интерфейса?
Я имел ввиду имплементируют.
Да, но ведь может быть так, что какая-то функция не является частью интерфейса :) (поправил свой пример)
Тоже забыл, что абстрактные методы не имеют имплементации. А чем тогда они отличаются от методов интерфейса?
Тем, что они могут быть скрытыми от пользователя (см. мой поправленный пример)
Т.е. можно наложить на потомков требование имплементировать какую-то функцию и при этом функция эта не будет частью интерфейса.... да что там частью интерфейска, ее можно сделать непубличной.
Немного другой вопрос. Заметил, что раз в интерфейсе все члены должны быть публичными, то это требование публичности распространяется и на их имплементацию в классах. А если мне надо имплементацию сделать непубличной? Пока только такой хак хашёл
public interface IPresenter { public string DisplayName { get; } } public class Presenter : IPresenter { public string DisplayName { get; protected set; }
Т.е. в интерфейсе не объявляем геттер, тогда в имплементации можно его сделать с каким угодно уровнем доступа. Проблема лишь в том, что если передать объект Presenter как IPresenter, то свойство DisplayName нельзя будет установить.
Что за дурацкие ограничения? А если я хочу и сеттер ещё вдобавок сделать непубличным?
Понятно, что интерфейсы они типа по природе должны быть публичными, но если я хочу ограничить действия некоторых из них в пределах одной иерархии классов?
Короче, всё это "хочется странного" возникло из-за запутанной иерархии моих классов. Разобрался, переделал, и уже не нужно, чтобы интерфейсы стали закрытими или защищёнными. Вообще убрал его нафиг. )))
Чё там по тестам, у нас мок-объекты любят делать через интерфейсы? А если их нет, а есть лишь абстрактные классы - пойдёт для создания мок-объекта? Если они не непубличные, конечно.
Т.е. в интерфейсе не объявляем геттер, тогда в имплементации можно его сделать с каким угодно уровнем доступа.
Именно. А также не стоит забывать и про internal. Это тоже очень полезная область видимости :) Есть правда еще protected internal и private protected, но эти ограничения я в реальной жизни еще не встречал :)
Проблема лишь в том, что если передать объект Presenter как IPresenter, то свойство DisplayName нельзя будет установить.
Ну это можно решить двумя интерфейсами:
public interface IPresenter { public string DisplayName { get; } } public interface IPresenterExt { public string DisplayName { get; set; } } public class Presenter : IPresenter, IPresenterExt { public string DisplayName { get; protected set; } }
или так:
public interface IPresenter { public string DisplayName { get; } } public interface IPresenterExt : IPresenter { public string DisplayName { set; } } public class Presenter : IPresenterExt { public string DisplayName { get; set; } }
но если я хочу ограничить действия некоторых из них в пределах одной иерархии классов?
Сформулируй проблему поточнее.
А если их нет, а есть лишь абстрактные классы - пойдёт для создания мок-объекта?
Для мок-объекта подойдет все, где можно (нужно) перегружать фукнкции или проперти. Т.е. абстрактые и проперти также можно использовать в мок-объектах.
Если у тебя есть такой класс:
public class abstract Foo { public void DoSomething () {} public abstract void DoSomethingElse () {} public virtual void Sleep () {} }
то DoSomethingElse и Sleep ты сможешь заменить, а вот с DoSomething ты ничего не сделаешь.
Если они не непубличные, конечно.
Если они internal, то не проблема :)
Есть правда еще protected internal и private protected, но эти ограничения я в реальной жизни еще не встречал :)
Надо бы кандидатов на собесах погонять. Чтобы не расслаблялись. ))
Проблема лишь в том, что если передать объект Presenter как IPresenter, то свойство DisplayName нельзя будет установить.
Ну это можно решить двумя интерфейсами:
public interface IPresenter { public string DisplayName { get; } } public interface IPresenterExt { public string DisplayName { get; set; } } public class Presenter : IPresenter, IPresenterExt { public string DisplayName { get; protected set; } }
Не работает (9 версия языка)
И так тоже не работает, с той же ошибкой
А в таком варианте вообще странность - мало того, что позволили сделать сеттер защищённым в интерфейсе, так ещё и несовместимость ввели - в 10 версии можно, в 9 нельзя. Если уж дошли до несовместимости сверху вниз, то не пора бы взяться за удаление устаревших вещей, чтобы язык и фреймворк не замусоривался?
но если я хочу ограничить действия некоторых из них в пределах одной иерархии классов?Сформулируй проблему поточнее.
Защищённые и приватные ограничители создают ограничения в пределах одной иерархии классов. Но тогда интерфейсы использовать нельзя, т.к. они могут действовать на разные иерархии. Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.
Для мок-объекта подойдет все, где можно (нужно) перегружать фукнкции или проперти. Т.е. абстрактые и проперти также можно использовать в мок-объектах.
...
DoSomethingElse и Sleep ты сможешь заменить, а вот с DoSomething ты ничего не сделаешь.
Вот я и говорю - ограничения и проблемы. В интерфейсе можно заменить всё.
Защищённые и приватные ограничители создают ограничения в пределах одной иерархии классов. Но тогда интерфейсы использовать нельзя, т.к. они могут действовать на разные иерархии.
Я же просил тебя сформулировать задачу понятнее :) Из этого текста я так и не понял чего ты хочешь добиться.
Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.
Я не знаю какие проблемы у тебя там возникают :) Подозреваю, что ты разрядил полную обойму себе в колено :)
Вот я и говорю - ограничения и проблемы. В интерфейсе можно заменить всё.
Нет там никаких ограничений и проблем. Если говорить в терминологии C++, то интерфейс - это pure abstract class, т.е. интерфейс - это синтаксический сахар. По сути интерфейс - это абстрактный класс, у которого все методы и проперти - абстрактны :)
Надо просто понимать, как работают mock-фреймворки. Они не делают ничего большего, чем просто создают новый класс-наследник от указанного класса и перегружают все виртуальные функции. Никакой магии там нет :)
Я же просил тебя сформулировать задачу понятнее :) Из этого текста я так и не понял чего ты хочешь добиться.
Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.Я не знаю какие проблемы у тебя там возникают :) Подозреваю, что ты разрядил полную обойму себе в колено :)
Надо заставить наследник перегрузить пустой (без реализации, абстрактный) непубличный метод или пропертю. Т.е. подходит только абстрактный класс? А как его непубличную часть моки создают? "Сканят" через рефлексию? Тогда смысл в обязательных интерфейсах для тестов, если можно любой класс и любые его члены просканить и создать мок-имплементацию?
Тут ещё вопрос, что мы тестируем. Если лишь публичную часть - контракт, то и моки надо лишь на публичную часть делать. Если вообще весь класс, то только рефлексией (ну или там парсить код самим), но тогда требование интерфейса для создания моков не нужно.
Т.е. подходит только абстрактный класс?
Да, заставить можно только абстрактным методом/проперти.
А как его непубличную часть моки создают?
Все непубличное геморрой, но решаемый - Mock abstract protected method
public interface IPresenter { public string DisplayName { get; } } public interface IPresenterExt { public void SetDisplayName(string name); } public abstract class Presenter : IPresenter, IPresenterExt { public string DisplayName { get => whatever; } public abstract void SetDisplayName(string name); }
Тут ещё вопрос, что мы тестируем.
:) Это основной вопрос :D
А если я хочу и сеттер ещё вдобавок сделать непубличным?
-----
А зачем? внутри класса тебе достаточно просто переменной
не объявляем геттер, тогда в имплементации можно его сделать
-----
По мне так ошибка.
Но вопрос будет ли он в рантайме доступен через интерфейс.
А если я хочу и сеттер ещё вдобавок сделать непубличным?
-----
А зачем? внутри класса тебе достаточно просто переменной
А в иерархии классов? Вроде, считается плохим тоном делать защищённые переменные.
не объявляем геттер, тогда в имплементации можно его сделать
-----
По мне так ошибка.
Но вопрос будет ли он в рантайме доступен через интерфейс.
Не будет. Поэтому я тоже не хочу такого.
Меня уже больше интересует, зачем делать интерфейсы для целей лишь тестирования, если мок-фреймворки и так вытащат все данные рефлексией или парсингом исходного кода. Зачем мне загромождать свой код лишними конструкциями, которые в моей бизнес-логике не нужны?