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 
AlexNek патриот12.10.24 17:15
AlexNek
NEW 12.10.24 17:15 
в ответ alex445 12.10.24 08:19
Меня уже больше интересует, зачем делать интерфейсы для целей лишь тестирования

почему именно для тестирования? И не обязательно всё мОкать.

вот у меня например, есть имплементация в памяти для теста и на диске для работы.


#21 
Программист коренной житель13.10.24 15:24
NEW 13.10.24 15:24 
в ответ alex445 12.10.24 08:19
если мок-фреймворки и так вытащат все данные рефлексией или парсингом исходного кода.

мок-фреймворки этим не занимаются :)

#22 
MrSanders коренной житель13.10.24 16:36
NEW 13.10.24 16:36 
в ответ Программист 13.10.24 15:24

смотря что он под "вытаскивать данные" имеет в виду... Так-то рефлекшен в полный рост используют.

#23 
Программист коренной житель14.10.24 11:17
NEW 14.10.24 11:17 
в ответ MrSanders 13.10.24 16:36

Я не очень понимаю, зачем мок-фреймфоркам использовать рефлекшены :)


Впрочем, есть и такие фреймворки, которые умеют подменять буквально все (в том числе и статики)... но вот только их мало кто использует :D

#24 
MrSanders коренной житель14.10.24 11:43
NEW 14.10.24 11:43 
в ответ Программист 14.10.24 11:17
Я не очень понимаю, зачем мок-фреймфоркам использовать рефлекшены :)

С примерами не получится, я почти всё что о шарпе знал уже благополучно забыл. Затем, чтобы можно было работать с неидеальным кодом.

В яве самое, пожалуй, частое применение: подменить значение приватного поля.

Сеттера нет, поле инициализируется или конструктором, или каким-нибудь DI фреймворком. А геттер сделали финальным (не виртуальным, в подклассе не перепишешь).


Впрочем, есть и такие фреймворки, которые умеют подменять буквально все (в том числе и статики)... но вот только их мало кто использует :D

Ну. насчёт мало. Пытаются постоянно. У меня уже рука устала линейкой по пальцам бить. Но иногда прям действительно приходится. Как подсунут тебе какую-то библиотеку, так чтобы замочить в ней доступ к какому-нибудь сервису или к БД приходится или конструктор или какой-то приватный метод подменять.

Но в яве это уже байткод менять на лету надо. Столько весёлых побочек приносит, особенно если при тестах оптимизацию не отключать.

#25 
Программист коренной житель14.10.24 12:33
NEW 14.10.24 12:33 
в ответ MrSanders 14.10.24 11:43
Затем, чтобы можно было работать с неидеальным кодом.

Все самые популярные мок-фремворки не работают с кодом. На вход ты даешь интерфейс или какой-нибудь класс и преймворк "пишет" свои классы наследники. Идеальный там код или нет вообще никого не интересует.


В яве самое, пожалуй, частое применение: подменить значение приватного поля.

Наиболее популярные мок-фремворки этим не занимаются.


#26 
alex445 патриот14.10.24 12:34
NEW 14.10.24 12:34 
в ответ MrSanders 14.10.24 11:43
всё что о шарпе знал уже благополучно забыл
В яве

Щас Котлин у всех на слуху.

#27 
7495 коренной житель14.10.24 18:09
7495
NEW 14.10.24 18:09 
в ответ alex445 14.10.24 12:34
Щас Котлин у всех на слуху.


Ну какой ещё котлин, ява или тем более сишарп, это всё для древних стариков, молодёжь выбирает лучшее, Я сказал учить всем Solidity!


вот тут пример простейшего вендора, и интерфейс токена, заливаю холокост токен, но дергал смартконтракт не через функцию, а фаллбак.


в результате ханурик, вклинился, захватил мой смартконтракт, вывел бабосики! 999.999 холохосттокенов ещё остались висеть на балансе:


Вопросы и Ответы - Программируем калькулятор пособий для беженцев вместе.
#28 
MrSanders коренной житель15.10.24 08:26
NEW 15.10.24 08:26 
в ответ Программист 14.10.24 12:33
Все самые популярные мок-фремворки не работают с кодом.

Да ниможитбыть! Кого-то засосала трясина ИИ. Представил себя большой языковой моделью.

Ладно, раскрою мысль: "чтобы программист, мог с помощью этих фреймворков писать тесты для неидеального кода".

Наиболее популярные мок-фремворки этим не занимаются.

Ну, или у нас разные популярные мок фреймворки, или кто-то в мире ява как я в шарпе. Мокито популярный? Что делает аннотация @InjectMocks в его расширении для JUnit-а?

А уж что делает EasyMock с рефлексией, чтобы работать с bridge - методами...

#29 
Программист коренной житель15.10.24 11:19
NEW 15.10.24 11:19 
в ответ MrSanders 15.10.24 08:26
Ладно, раскрою мысль: "чтобы программист, мог с помощью этих фреймворков писать тесты для неидеального кода".

Всегда можно нашлепать нетестируемый код и потом говорить, что фреймворки плохи :)

Неидеальный код можно приближать к идеалу рефакторингом.


Ну, или у нас разные популярные мок фреймворки, или кто-то в мире ява как я в шарпе.

Я про мир явы вообще ничего не знаю. Знаю только, что Java существует улыб


Мокито популярный?

Судя по всему, это что-то для специальное для явы. Ну и судя по тому, что они умеют подменять статики, рефлекшенами они таки пользуются.


А уж что делает EasyMock с рефлексией, чтобы работать с bridge - методами...

EasyMock тоже что-то исключительно из мира явы. Кто такие bridge - методы я не в курсе.

#30 
MrSanders коренной житель15.10.24 14:36
NEW 15.10.24 14:36 
в ответ Программист 15.10.24 11:19
Всегда можно нашлепать нетестируемый код и потом говорить, что фреймворки плохи :)

Подмена понятий. Мы не о "хороший-плохой" а о "зачем мок-фреймворку использовать reflection".

Неидеальный код можно приближать к идеалу рефакторингом.

Не всегда. Ну и пока не приблизили, что, не тестируем?

#31 
Программист коренной житель15.10.24 17:44
NEW 15.10.24 17:44 
в ответ MrSanders 15.10.24 14:36
Мы не о "хороший-плохой" а о "зачем мок-фреймворку использовать reflection".

Задача мок-фремворка - подменить внешние зависимости у unit under test.

Т.е. для того, чтобы код был тестируемым, нужно фактически 2 вещи: 1) unit under test должен зависить от абстракций и 2) нужно иметь возможность инжектить эти абстракции (самое простое - через конструктор).


Если эти 2 условия соблюдаются, то никакие рефлекшены мок-фреймвокру не нужны :)


Ну и пока не приблизили, что, не тестируем?

Пока не приблизили, это все легаси, которое и так работает. Покрывать тестами легаси можно, но вот насколько это эффективно - большой вопрос.

Если же у вас новый код нетестируемый, то либо вы сами себе злобные Буратины, либо тесты вам нахрен не нужны.

#32 
alex445 патриот15.10.24 19:18
NEW 15.10.24 19:18 
в ответ Программист 15.10.24 17:44
тесты вам нахрен не нужны.

Разве так можно? Это же тяжкий грех!

#33 
MrSanders коренной житель15.10.24 20:41
NEW 15.10.24 20:41 
в ответ Программист 15.10.24 17:44, Последний раз изменено 15.10.24 21:00 (MrSanders)
Если эти 2 условия соблюдаются, то никакие рефлекшены мок-фреймвокру не нужны :)

Угу. А рядом порхают единороги и попукивают радугой феи. Вот только есть ещё одна загвоздка - юнит у нас не класс. Юнит у нас - метод. И для тестирования метода приходится мочить другие методы того же самого класса. Или суперкласса. А вот они могут быть из другого фреймворка. И с ними и случаются засады.

// Customer - наш класс. А Entity - класс фреймворка, которому 5 лет, от другой фирмы, от которого у нас нет исходников.
// Да даже если б и были, модифицировать его мы не можем. 
public class Customer extends Entity {
  private final String firstName;
  ....
  private String plz;
  public boolean isSame(Person other) {   
    // Тут логика, которую мы хотим протестировать
    if(other == null) return false;
    // а вот тут порылась собака. Метод getPrimaryKey() определён в Entity. Из чужого фреймворка. И он финальный.
    // т.е. его переписать нельзя. Он просто возвращает primary key с которым этот объект сохранён в БД. Или -1 если
    // ещё не сохранён. А сеттера к нему нет. Или скрыт. Бывает, да? Ели это тот же объект из БД то он "тот же самый".
    if(getPrimaryKey() == other.getPrimaryKey()) return true;
    // и дальше код, который надо тестировать.
    boolean result = getName().equals(other.getName() && ....;
    result &= //ещё что-нибудь.  
    return result;
  } 
}

Ну и как же мне сделать этот код тестируемым по феншую? Написать свою public long getPkFromEntity() { return getPrimaryKey();} ?

Ну, т.е. расширить продуктивный код исключительно в интересах юнит теста, сделает его менее читаемым и добавить возможность подклассу поломать ПК, переписав мой метод.

А может лучше воспользоваться рефлексией и подставить в приватное поле other.primaryKey (унаследованное от суперкласса Entity) значение 123?


Пока не приблизили, это все легаси, которое и так работает.

Эту реплику из зала мы, товарищи, сейчас отметём с негодованием. Как неорганизованную. (ц)

Не верно. Это не наше легаси. И не его мы тестируем. Мы его должны использовать. Иначе ORM работать не будет. А тестировать мы должны свой код. Который от легаси зависит.


И таких примеров не один и не 10. Иногда бывают сильносвязанные классы. Ну вот надо чтобы А знало Б, а Б знало А. Цикл. И уже никто не может передать А или Б в конструктор. Делаем завод, который пару А и Б сгенерирует, конструкторы и сеттеры прячем чтобы только завод их вызвать мог, геттеры финальные (не переписываемые) опять же всё по феншую. А вот красота с "инжектить абстракции" в тесте поломалась.


А бывают DI фреймворки, которые ТРЕБУЮТ чтобы конструктор был пустым. А все зависимости засовывают напрямую в аннотированные поля. Приватные. И наш красивый код ДОЛЖЕН использовать этот фреймворк. Потому что экосистема. И именно потому что экосистема к коду архитектурное требование - сеттеров быть не должно (мнэ, на вашем языке - проперть ридонли :)). Потому что значения установит DI. А геттеры или финальные (не переписываемые) или их нет, чтобы не давать доступа к полю даже наследникам.

И сидишь ты такой и чешешь тыковку - поднимать DI для тестов со своей конфигурацией (а это тоже иногда не сложно, а невозможно из-за конфликтов реализаций) или тупо воспользоваться фишкой мок-фреймворка и подменить значения приватных полей. И угадай с трёх раз, что же обычно выбирают?


Когда у тебя ну просто всё с чистого листа, и пишешь ты свой новенький REST сервис, например, то счастье возможно. Но авторы мок-фреймворков к счастью понимают, что не только лишь все могут таким наслаждаться, мало кто может это делать.


P.S. Кстати, примерно такой и была первая версия EasyMock-а. Только интерфейсы мочить умела. Но автор быстро понял что этого для реальной жизни не хватает.

#34 
alex445 патриот15.10.24 23:26
NEW 15.10.24 23:26 
в ответ MrSanders 15.10.24 20:41, Последний раз изменено 15.10.24 23:29 (alex445)
А бывают DI фреймворки, которые ТРЕБУЮТ чтобы конструктор был пустым. А все зависимости засовывают напрямую в аннотированные поля. Приватные. И наш красивый код ДОЛЖЕН использовать этот фреймворк. Потому что экосистема. И именно потому что экосистема к коду архитектурное требование - сеттеров быть не должно (мнэ, на вашем языке - проперть ридонли :)). Потому что значения установит DI. А геттеры или финальные (не переписываемые) или их нет, чтобы не давать доступа к полю даже наследникам.

Сначала призывают к соревнованиям по бегу, затем заранее простреливают обе ноги, затем дают пару модных костылей, которые немного облегчат участь.

#35 
Программист коренной житель16.10.24 08:40
NEW 16.10.24 08:40 
в ответ MrSanders 15.10.24 20:41
Вот только есть ещё одна загвоздка - юнит у нас не класс. Юнит у нас - метод.

Да хоть кого выбери юнитом. Юнит в любом случае должен работать с абстракциями.


Ну и как же мне сделать этот код тестируемым по феншую?

Очень просто - использовать абстракции. Если их нет, то ввести :) Использовать "тонкие" DTO,

Можно также использовать прокси-объекты.

Было бы желание сделать по феншую :)


Ну, т.е. расширить продуктивный код исключительно в интересах юнит теста, сделает его менее читаемым и добавить возможность подклассу поломать ПК, переписав мой метод.

Ну это зависит от того, как расширять :) Совсем необязательно, что будет менее читаемым.


А может лучше воспользоваться рефлексией и подставить в приватное поле other.primaryKey (унаследованное от суперкласса Entity) значение 123?

Угу, а еще тебе понадобится реальная БД, т.к. где-нибудь в этом Entity погребено подключение к БД. Надо будет держать эту БД в валидном для теста состоянии. А еще не дай бог кому-то придет в голову поменять логику Entity и приватное в лучшем случае удалят, а в худшем - будут использовать как-то иначе. Или натрафят обфускатор :)


А бывают DI фреймворки, которые ТРЕБУЮТ чтобы конструктор был пустым.

Ну во времена, когда один класс может имень много конструкторов это вообще самая маленькая проблема в мире :)


И именно потому что экосистема к коду архитектурное требование - сеттеров быть не должно (мнэ, на вашем языке - проперть ридонли :)) Потому что значения установит DI. А геттеры или финальные (не переписываемые) или их нет, чтобы не давать доступа к полю даже наследникам.

Да пофиг :) Переходи на уровень абстракций, т.е. интерфейсов. На этом уровне совершенно наплевать на всякие там финальные или приватные методы/проперти... их просто нет, т.к. интерфейс по определению публичный :)


И угадай с трёх раз, что же обычно выбирают?

Стреляющие себе в ногу выбирают рефлекшены :) Это ж очевидно :D :D :D

#36 
MrSanders коренной житель16.10.24 20:50
NEW 16.10.24 20:50 
в ответ Программист 16.10.24 08:40
Да пофиг :) Переходи на уровень абстракций, т.е. интерфейсов.

Феерично. Мышки, станьте ёжиками. Какой, блин, интерфейс, если у нас требование наследовать от Enttity?

Ладно, не буду отрывать от радужных феечек, пока сам не увидишь - не поймёшь.

#37 
1 2 все