Deutsch
Germany.ruФорумы → Архив Досок→ Программирование

Пустой виртуальный метод вместо интерфейса

1779  1 2 все
alex445 патриот08.10.24 12:28
08.10.24 12:28 

Иногда встречаю такую конструкцию, что в обычном, не абстрактном базовом классе есть пустой виртуальный метод, который явно нужен, чтобы его перегрузил потомковый класс. А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным? Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят. Если делать просто пустой метод, то его могут не перегрузить - ничего же не обязывает по синтаксису языка. Если же это такая задумка, что метод необязательно перегружать, то значит, что он не во всех потомках и нужен. Тогда смысл деражть такой метод в базовом классе? Проще каждому потомку свой метод иметь?


Вопрос - может, всё же есть какой-то смысл именно в пустом базовом методе, не абстрактном, и без интерфейса?

#1 
Программист коренной житель08.10.24 13:28
NEW 08.10.24 13:28 
в ответ alex445 08.10.24 12:28, Последний раз изменено 08.10.24 17:37 (Программист)
Иногда встречаю такую конструкцию, что в обычном, не абстрактном базовом классе есть пустой виртуальный метод, который явно нужен, чтобы его перегрузил потомковый класс.

Да, бывает.


А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным?

Потому что не "нужно, чтобы перегрузил", а "нужно дать возможность перегрузить". Если бы было "нужно, чтобы перегрузил", то класс был бы абстратнным и метод тоже был бы абстрактным.


Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят.

Не получаешь :)


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

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'а
    }
}


#2 
alex445 патриот08.10.24 17:06
NEW 08.10.24 17:06 
в ответ Программист 08.10.24 13:28
Сделать в виде интерфейса, и тогда получаешь гарантию, что его перегрузят.
Не получаешь :)

Я имел ввиду имплементируют.


А раз нужно, чтобы перегрузил, то зачем делать этот метод обычным?

Потому что не "нужно, чтобы перегрузил", а "нужно дать возможность перегрузить".

Ну да, тут я не учёл.


Если бы было "нужно, чтобы перегрузил", то класс был бы абстратнным и метод тоже был бы абстрактным.

Тоже забыл, что абстрактные методы не имеют имплементации. А чем тогда они отличаются от методов интерфейса?

#3 
Программист коренной житель08.10.24 17:35
NEW 08.10.24 17:35 
в ответ alex445 08.10.24 17:06
Я имел ввиду имплементируют.

Да, но ведь может быть так, что какая-то функция не является частью интерфейса :) (поправил свой пример)


Тоже забыл, что абстрактные методы не имеют имплементации. А чем тогда они отличаются от методов интерфейса?

Тем, что они могут быть скрытыми от пользователя (см. мой поправленный пример)

Т.е. можно наложить на потомков требование имплементировать какую-то функцию и при этом функция эта не будет частью интерфейса.... да что там частью интерфейска, ее можно сделать непубличной.

#4 
alex445 патриот08.10.24 19:27
NEW 08.10.24 19:27 
в ответ Программист 08.10.24 17:35

Немного другой вопрос. Заметил, что раз в интерфейсе все члены должны быть публичными, то это требование публичности распространяется и на их имплементацию в классах. А если мне надо имплементацию сделать непубличной? Пока только такой хак хашёл


public interface IPresenter
{
    public string DisplayName { get; }
}

public class Presenter : IPresenter
{
    public string DisplayName { get; protected set; }


Т.е. в интерфейсе не объявляем геттер, тогда в имплементации можно его сделать с каким угодно уровнем доступа. Проблема лишь в том, что если передать объект Presenter как IPresenter, то свойство DisplayName нельзя будет установить.


Что за дурацкие ограничения? А если я хочу и сеттер ещё вдобавок сделать непубличным?


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

#5 
Срыв покровов патриот08.10.24 19:44
NEW 08.10.24 19:44 
в ответ alex445 08.10.24 19:27

кто-то не понял смысл интерфейса.

#6 
alex445 патриот08.10.24 22:47
NEW 08.10.24 22:47 
в ответ Срыв покровов 08.10.24 19:44

У вас кандибобер съехал - поправьте.

#7 
alex445 патриот08.10.24 22:49
NEW 08.10.24 22:49 
в ответ alex445 08.10.24 22:47

Короче, всё это "хочется странного" возникло из-за запутанной иерархии моих классов. Разобрался, переделал, и уже не нужно, чтобы интерфейсы стали закрытими или защищёнными. Вообще убрал его нафиг. )))


Чё там по тестам, у нас мок-объекты любят делать через интерфейсы? А если их нет, а есть лишь абстрактные классы - пойдёт для создания мок-объекта? Если они не непубличные, конечно.

#8 
Программист коренной житель09.10.24 09:14
NEW 09.10.24 09:14 
в ответ alex445 08.10.24 19:27
Т.е. в интерфейсе не объявляем геттер, тогда в имплементации можно его сделать с каким угодно уровнем доступа.

Именно. А также не стоит забывать и про 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; }
    }


но если я хочу ограничить действия некоторых из них в пределах одной иерархии классов?

Сформулируй проблему поточнее.

#9 
Программист коренной житель09.10.24 09:25
NEW 09.10.24 09:25 
в ответ alex445 08.10.24 22:49
А если их нет, а есть лишь абстрактные классы - пойдёт для создания мок-объекта?

Для мок-объекта подойдет все, где можно (нужно) перегружать фукнкции или проперти. Т.е. абстрактые и проперти также можно использовать в мок-объектах.

Если у тебя есть такой класс:

public class abstract Foo
{
    public void DoSomething () {}
    public abstract void DoSomethingElse () {}
    public virtual void Sleep () {}
}

то DoSomethingElse и Sleep ты сможешь заменить, а вот с DoSomething ты ничего не сделаешь.



Если они не непубличные, конечно.

Если они internal, то не проблема :)

#10 
alex445 патриот09.10.24 09:57
NEW 09.10.24 09:57 
в ответ Программист 09.10.24 09:14, Последний раз изменено 09.10.24 10:05 (alex445)
Есть правда еще 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 нельзя. Если уж дошли до несовместимости сверху вниз, то не пора бы взяться за удаление устаревших вещей, чтобы язык и фреймворк не замусоривался?


#11 
alex445 патриот09.10.24 10:02
NEW 09.10.24 10:02 
в ответ Программист 09.10.24 09:14
но если я хочу ограничить действия некоторых из них в пределах одной иерархии классов?

Сформулируй проблему поточнее.

Защищённые и приватные ограничители создают ограничения в пределах одной иерархии классов. Но тогда интерфейсы использовать нельзя, т.к. они могут действовать на разные иерархии. Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.


Для мок-объекта подойдет все, где можно (нужно) перегружать фукнкции или проперти. Т.е. абстрактые и проперти также можно использовать в мок-объектах.
...
DoSomethingElse и Sleep ты сможешь заменить, а вот с DoSomething ты ничего не сделаешь.

Вот я и говорю - ограничения и проблемы. В интерфейсе можно заменить всё.

#12 
Программист коренной житель09.10.24 11:42
NEW 09.10.24 11:42 
в ответ alex445 09.10.24 09:57
Не работает (9 версия языка)

Это я не заметил :) Надо убрать protected:

public string DisplayName { get; set; }
#13 
Программист коренной житель09.10.24 11:52
NEW 09.10.24 11:52 
в ответ alex445 09.10.24 10:02, Последний раз изменено 09.10.24 11:55 (Программист)
Защищённые и приватные ограничители создают ограничения в пределах одной иерархии классов. Но тогда интерфейсы использовать нельзя, т.к. они могут действовать на разные иерархии.

Я же просил тебя сформулировать задачу понятнее :) Из этого текста я так и не понял чего ты хочешь добиться.


Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.

Я не знаю какие проблемы у тебя там возникают :) Подозреваю, что ты разрядил полную обойму себе в колено :)


Вот я и говорю - ограничения и проблемы. В интерфейсе можно заменить всё.

Нет там никаких ограничений и проблем. Если говорить в терминологии C++, то интерфейс - это pure abstract class, т.е. интерфейс - это синтаксический сахар. По сути интерфейс - это абстрактный класс, у которого все методы и проперти - абстрактны :)


Надо просто понимать, как работают mock-фреймворки. Они не делают ничего большего, чем просто создают новый класс-наследник от указанного класса и перегружают все виртуальные функции. Никакой магии там нет :)

#14 
alex445 патриот09.10.24 13:05
NEW 09.10.24 13:05 
в ответ Программист 09.10.24 11:52

Я же просил тебя сформулировать задачу понятнее :) Из этого текста я так и не понял чего ты хочешь добиться.


Остаются лишь абстрактные классы, как возможность ЗАСТАВИТЬ имплементировать или перегрузить какой-то метод. А с таким ограничением возникают проблемы с моками для тестов.

Я не знаю какие проблемы у тебя там возникают :) Подозреваю, что ты разрядил полную обойму себе в колено :)

Надо заставить наследник перегрузить пустой (без реализации, абстрактный) непубличный метод или пропертю. Т.е. подходит только абстрактный класс? А как его непубличную часть моки создают? "Сканят" через рефлексию? Тогда смысл в обязательных интерфейсах для тестов, если можно любой класс и любые его члены просканить и создать мок-имплементацию?


Тут ещё вопрос, что мы тестируем. Если лишь публичную часть - контракт, то и моки надо лишь на публичную часть делать. Если вообще весь класс, то только рефлексией (ну или там парсить код самим), но тогда требование интерфейса для создания моков не нужно.

#15 
Программист коренной житель09.10.24 13:26
NEW 09.10.24 13:26 
в ответ alex445 09.10.24 13:05
Т.е. подходит только абстрактный класс?

Да, заставить можно только абстрактным методом/проперти.


А как его непубличную часть моки создают?

Все непубличное геморрой, но решаемый - 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


#16 
Murr патриот11.10.24 22:08
Murr
NEW 11.10.24 22:08 
в ответ alex445 08.10.24 19:27

А если я хочу и сеттер ещё вдобавок сделать непубличным?

-----

А зачем? внутри класса тебе достаточно просто переменной


не объявляем геттер, тогда в имплементации можно его сделать

-----

По мне так ошибка.

Но вопрос будет ли он в рантайме доступен через интерфейс.

#17 
Murr патриот11.10.24 22:17
Murr
NEW 11.10.24 22:17 
в ответ alex445 09.10.24 13:05

А как его непубличную часть моки создают?

-----

А зачем?

#18 
alex445 патриот12.10.24 08:19
NEW 12.10.24 08:19 
в ответ Murr 11.10.24 22:08, Последний раз изменено 12.10.24 08:25 (alex445)
А если я хочу и сеттер ещё вдобавок сделать непубличным?
-----
А зачем? внутри класса тебе достаточно просто переменной

А в иерархии классов? Вроде, считается плохим тоном делать защищённые переменные.


не объявляем геттер, тогда в имплементации можно его сделать

-----
По мне так ошибка.
Но вопрос будет ли он в рантайме доступен через интерфейс.

Не будет. Поэтому я тоже не хочу такого.


Меня уже больше интересует, зачем делать интерфейсы для целей лишь тестирования, если мок-фреймворки и так вытащат все данные рефлексией или парсингом исходного кода. Зачем мне загромождать свой код лишними конструкциями, которые в моей бизнес-логике не нужны?

#19 
alex445 патриот12.10.24 08:21
NEW 12.10.24 08:21 
в ответ Murr 11.10.24 22:17, Последний раз изменено 12.10.24 08:24 (alex445)
А как его непубличную часть моки создают?
-----
А зачем?

Точно, для моков не надо.

#20 
1 2 все