DI w bibliotece .dll

Odpowiedz Nowy wątek
2019-08-30 19:14
0

Hej, piszę dla siebie małą bibliotekę w .Net.

W zasadzie to nigdy tego wcześniej nie robiłem. Chcę mieć po prostu kawałek kodu który będę sobie wykorzystywał co jakiś czas w różnych projektach.

Biblioteka ta mi trochę już urosła. Korzysta z różnych domen, i siłą rzeczy chciałem je porozdzielać po różnych klasach i komunikować się między nimi przez interfejsy, co by mieć je ogarnięte oraz móc sobie też przetestować lub mockować łatwiej.

I jakoś niespecjalnie wydaje się to praktyczne, bo później jest zabawa z IoC projektu który z biblioteki korzysta. Zastanawiam się czy:

  • biblioteki się testuje?
  • korzysta się w nich z IoC oraz DI? tzn wewnątrz dll-a.
  • czy zamyka się kilka domen w jednej bibliotece? np. operacje na stringu + IO + WebBrowser.

Pozdrawiam,
b.

edytowany 1x, ostatnio: bakunet, 2019-08-31 02:40

Pozostało 580 znaków

2019-08-30 21:26
1
bakunet napisał(a):
  • czy zamyka się kilka domen w jednej bibliotece? np. operacje na stringu + IO + WebBrowser.

Nie wiem o jakim języku piszesz.

Ale na pewno "okienka" (w adekwatnym środowisku GUI) oddzielnie od "algorytmów" *)
IO - zależy. Jeśli to nadbudowa nad biblioteką standardową to może razem. Jeśli coś specyficznego (mikrokontrolery, specyficzne urządzenia) to oddzielnie

*) zarówno w świecie Javy, jak i dotnet są sytuacje, że cały projekt jest odrzucany, bo ISTNIEJE coś zakazanego. Np Java / Google App Engine ma na blackliscie całe GUI, łącznie z zupełnie niewinną klasą Color.
.NET web odrzuca(ł ?) referencje do WinForms

Pozostało 580 znaków

2019-08-31 06:22
0
AnyKtokolwiek napisał(a):

Nie wiem o jakim języku piszesz.

Ale na pewno "okienka" (w adekwatnym środowisku GUI) oddzielnie od "algorytmów" *)
IO - zależy. Jeśli to nadbudowa nad biblioteką standardową to może razem. Jeśli coś specyficznego (mikrokontrolery, specyficzne urządzenia) to oddzielnie

*) zarówno w świecie Javy, jak i dotnet są sytuacje, że cały projekt jest odrzucany, bo ISTNIEJE coś zakazanego. Np Java / Google App Engine ma na blackliscie całe GUI, łącznie z zupełnie niewinną klasą Color.
.NET web odrzuca(ł ?) referencje do WinForms

Chodzi mi o C#. Właściwie to chcę żeby to była jak najbardziej autonomiczna biblioteka, więc chyba będzie z IO, właśnie wyczytałem, że lepiej nie wołać UI z biblioteki, w sumie to ma sens. Myślę też wrzucić jednak wszystko do jednego worka i nie tworzyć interfejsów/serwisów, za dużo później zabawy z IoC w aplikacji, coś wymyślę z testowaniem. Dzięki za komentarz.

Pozostało 580 znaków

2019-08-31 13:40
0

UPDATE

Ok, uparłem się, trochę pogrzebałem, i udało mi się znaleźć jak to zrobić. W zasadzie to nie znalazłem bezpośredniego rozwiązania, ale zainspirował mnie ten artykuł oraz ta odpowiedź na SO.

Generalnie rzecz biorąc to chodzi tu o to, żeby ukryć zależności biblioteki przed projektem aplikacji. W przeciwnym wypadku projekt aplikacji będzie płakał wyjątkami, że zależności biblioteki nie zostały zarejestrowane w kontenerze aplikacji. A tak naprawdę zostały już zarejestrowane w kontenerze biblioteki, choć są wstrzyknięte do konstruktora biblioteki dostępnego publicznie, przez to widzi je też aplikacja.

Jak ktoś poradził na SO, z pomocą tu przychodzi wzorzec fasady. Należy w nim schować zależności i rozwiązać Resolve fasadę w klasie biblioteki dostępnej z projektu aplikacji. Prosty przykład poniżej, w którym warstwę serwisową zarejestrowałem jako singletony:

public class Booter
    {
        public static IContainer BootStrap()
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<FilesService>()
              .As<IFilesService>().SingleInstance();

            builder.RegisterType<ControlsService>()
              .As<IControlsService>().SingleInstance();

            builder.RegisterType<Facade>()
              .AsSelf().SingleInstance();

            return builder.Build();
        }
    }

Do fasady wstrzykujemy wszystkie zależności biblioteki:

public class Facade
    {
        IFilesService _fileService;
        IControlsService _controlsService;
        public Facade(IFilesService filesService, IControlsService controlsService)
        {
            _fileService = filesService;
            _controlsService = controlsService;

            FilesService = _fileService;
            ControlsService = _controlsService;
        }

        public IFilesService FilesService { get; }

        public IControlsService ControlsService { get; }
    }

Następnie w naszej bibliotece najpierw wołamy metodę Initialize, w której rozwiązujemy fasadę, a następnie do interfejsów serwisów przypisuję własności fasady wstrzykniętych już serwisów:

public class MainService : IMainService
    {
        IFilesService _fileService;
        IControlsService _controlsService;
        IContainer _container;
        public MainService()
        {
            Initialize();
        }

        public void Initialize()
        {
            _container = Booter.BootStrap();

            var aggregator = _container.Resolve<Facade>();

            _fileService = aggregator.FilesService;
            _controlsService = aggregator.ControlsService;
        }

        public UiControlsModel SwitchOff()
        {
            return _controlsService.SwitchOff();
        }

        public UiControlsModel SwitchOn()
        {
            return _controlsService.SwitchOn();
        }
    }

Mam nadzieję że nie jest to żaden antywzorzec.

Pozostało 580 znaków

2019-08-31 23:29
2

Szczerze, to w ogóle nie rozumiem, czemu biblioteka w ogóle ma potrzebę używania wewnętrznie kontenera. Jak dla mnie biblioteka to zestaw klas, których dopiero używa się w swojej właściwej aplikacji, i tam trzeba tworzyć ich obiekty, zgodnie z regułami przyjętymi w aplikacji. Chyba w życiu nie widziałem biblioteki, która by wewnętrznie używała kontenera.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
Pokaż pozostałe 2 komentarze
Dzięki Wam się dowiedziałem jaka jest różnica pomiędzy frameworkiem a biblioteką. W takim razie bym się skłaniał ku temu, że stworzyłem framework. Zakładając, że framework korzysta z IoC, mój korzysta z wbudowanych bibliotek odpowiedzialnych za: zapisywanie danych, konfigurację, operacje na danych. Trzy różne biblioteki działające w obrębie jednego pliku .dll, który chcę podpiąć do aplikacji za pomocą jednego interfejsu. Swój wniosek opieram na tym artykule - bakunet 2019-09-03 12:35
Ale co konkretnie robi ten Twój framework? - somekind 2019-09-03 12:35
@somekind: to jest logger który wykorzystuję do debugowania: hhttps://github.com/przemyslawbak/Params_Logger - bakunet 2019-09-03 12:40
No jak dla mnie to jest biblioteka w takim razie - ja muszę wywoływać jej kod, a nie ona mój. - somekind 2019-09-03 17:21
@somekind: Ok, przyznaję rację. Jedyne co mi pozostaje to przeanalizować podobne projekty opensource i zobaczyć jak sobie z w nich radzą lepsi z izolacją i testami. - bakunet 2019-09-04 06:04

Pozostało 580 znaków

2019-09-04 06:18
1

Po co w ogóle używasz kontenera w tej bibliotece? Co on Ci daje? Czy rozumiesz jak robić dependency injection bez kontenera?

Kontener mam po to, by rejestrować interfejsy które chcę wstrzyknąć do innej klasy . Kiedyś robiłem proste przykłady mockowania bez DI za pomocą fabryk i własnych namiastek/makiet. W podobnych bibliotekach widzę, że też korzystają po prostu z fabryki. Polecisz jakieś inne podejście? Chodzi mi tu głównie o makiety integracji - bakunet 2019-09-04 06:24
@bakunet: pisz proszę w postach. - somekind 2019-09-04 10:43
Nie chciałem zaśmiecać forum, ale ok, już będę. - bakunet 2019-09-04 10:44
Pisanie na temat w komentarzach to jest właśnie robienie bałaganu. Posty są od tematu, komentarze od off-topu. - somekind 2019-09-04 10:46
Do tej pory byłem przekonany, że komentarze są do postu :) - bakunet 2019-09-04 10:47
Tak, właśnie. Komentarze są do postu. A posty do tematu. - somekind 2019-09-04 12:42

Pozostało 580 znaków

2019-09-04 06:33
2

Kontener mam po to, by rejestrować interfejsy które chcę wstrzyknąć do innej klasy . Kiedyś robiłem proste przykłady mockowania bez DI za pomocą fabryk i własnych namiastek/makiet.

Stop, ja jeszcze o żadnym mockowaniu nie mówię. Po co Ci kontener? Ponownie pytam, czy rozumiesz jak robić dependency injection bez kontenera?

Na oko obstawiam, że nie rozumiesz czym jest DI i po co właściwie używa się kontenera. DI != kontener DI. Może się mylę, jeżeli tak, to wyjaśnij, co ten kontener daje Ci w tym przypadku.

W kontenerze rejestruję interfejsy razem z klasami oraz ewentualnymi parametrami klasy które są rozwiązywane wraz z instancją kontenera. Następnie, dzięki zarejestrowanemu interfejsowi mogę wstrzyknąć interfejs do swojej klasy, co jest tzw luźnym połączeniem (loose coupling) i w razie potrzeby zamiast klasy sparowanej z interfejsem w kontenerze mogę wykorzystać makietę, np do testów jednostkowych. Kontener jest swego rodzaju service lokatorem, jednak jest on blisko punktu wejścia aplikacji i nie jest traktowany jako anty-wzorzec. Nie wiem jeszcze jak zrobić DI bez kontenera - bakunet 2019-09-04 06:38

Pozostało 580 znaków

2019-09-04 06:40
0

W kontenerze rejestruję interfejsy razem z klasami oraz ewentualnymi parametrami klasy które są rozwiązywane wraz z instancją kontenera. Następnie, dzięki zarejestrowanemu interfejsowi mogę wstrzyknąć interfejs do swojej klasy, co jest tzw luźnym połączeniem (loose coupling) i w razie potrzeby zamiast klasy sparowanej z interfejsem w kontenerze mogę wykorzystać makietę, np do testów jednostkowych.

Klawo. Której z tych rzeczy nie możesz zrobić bez kontenera?

Nie umiem zarejestrować interfejsu, tzn sparować z klasą. Kiedyś widziałem jak ktoś wykorzystał słownik (Dictionary) w service-lokatorze, ale do końca nie wiem jeszcze jak go później wykorzystać. Dopiero zacząłem research po Twoim pytaniu :) Chodzi mi o loose coupling przy użyciu interfejsu oczywiście, a nie wstrzykiwanie klasy samej w sobie. Bo wstrzykiwać w konstruktor można cokolwiek. - bakunet 2019-09-04 06:45

Pozostało 580 znaków

2019-09-04 06:47
1

Jak dla mnie potwierdziłeś, że nie rozumiesz DI. Dodatkowo Twój kod wyżej pokazuje, jak tworzysz kontener tylko po to, żeby wyciągnąć z niego fasadę, jeżeli takie coś chcesz robić w bibliotece, to kontener jest moim zdaniem całkowicie zbędny i tylko komplikuje sprawę.

Ma nawet sens to co prawisz. W końcu do głównej klasy trafia sama fasada. Więc bez sensu tworzyć dodatkowo kontener. Pokombinuję w tym kierunku. - bakunet 2019-09-04 06:56

Pozostało 580 znaków

2019-09-04 09:33
0

@bakunet:

  1. nie pisz odpowiedzi na posty w komentarzach!
  2. Wrzucanie ciężkiej zależności w stylu kontenera do biblioteki to zły pomysł. Bo tego się potem nie da używać.

Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...

Pozostało 580 znaków

2019-09-04 09:48
0
Shalom napisał(a):

@bakunet:

  1. Wrzucanie ciężkiej zależności w stylu kontenera do biblioteki to zły pomysł. Bo tego się potem nie da używać.

Ale ja właśnie nie chcę żeby kontener i zależności były widoczne na zewnątrz. Stąd pomysł na fasadę. I jak zauważył @Afish, bez sensu jest tu zastosowanie fasady z kontenerem. Teraz próbuję przetestować nowy kod, choć jeszcze nie wiem jak to zrobić bez wstrzykiwania zależności:

public class Facade
    {
        public Facade()
        {
            FilesService = new FilesService();
            ControlsService = new ControlsService();
        }

        public IFilesService FilesService { get; }
        public IControlsService ControlsService { get; }
    }
public class MainService : IMainService
    {
        IFilesService _fileService;
        IControlsService _controlsService;

        public UiControlsModel Controls { get; set; }

        public MainService()
        {
            Initialize();
        }

        public void Initialize()
        {
            Facade facade = new Facade(); //dodać interfejs i mockować póżniej?

            _fileService = facade.FilesService;
            _controlsService = facade.ControlsService;
        }

        public UiControlsModel SwitchOff()
        {
            Controls = _controlsService.SwitchOff();
            return Controls;
        }
    }
public class MainServiceTests
    {
        private Mock<IControlsService> _controlsServiceMock; //mockować to?
        private MainService _vm;

        private UiControlsModel _controlsModelOff = new UiControlsModel() { Dwa = true, Jeden = true, Trzy = true };

        public MainServiceTests()
        {
            _controlsServiceMock = new Mock<IControlsService>();

            _vm = new MainService();
        }

        [Fact]
        public void SwitchOff_Called_ReturnsModel()
        {
            _controlsServiceMock.Setup(c => c.SwitchOff())
                .Returns(_controlsModelOff);
            _vm.Controls = new UiControlsModel() { Jeden = false, Dwa = false, Trzy = false };

            _vm.SwitchOff();

            Assert.True(_vm.Controls.Trzy); //test nie przechodzi
        }
    }
edytowany 1x, ostatnio: bakunet, 2019-09-04 09:50

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0