DI w bibliotece .dll

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
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
AK
  • Rejestracja:około 7 lat
  • Ostatnio:7 miesięcy
  • Postów:3561
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


Bo C to najlepszy język, każdy uczeń ci to powie
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
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.

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
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:

Kopiuj
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:

Kopiuj
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:

Kopiuj
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.

somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
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.

Zobacz pozostałe 2 komentarze
bakunet
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
somekind
Ale co konkretnie robi ten Twój framework?
bakunet
@somekind: to jest logger który wykorzystuję do debugowania: hhttps://github.com/przemyslawbak/Params_Logger
somekind
No jak dla mnie to jest biblioteka w takim razie - ja muszę wywoływać jej kod, a nie ona mój.
bakunet
@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.
AF
  • Rejestracja:ponad 18 lat
  • Ostatnio:3 miesiące
1

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

Zobacz pozostały 1 komentarz
somekind
@bakunet: pisz proszę w postach.
bakunet
Nie chciałem zaśmiecać forum, ale ok, już będę.
somekind
Pisanie na temat w komentarzach to jest właśnie robienie bałaganu. Posty są od tematu, komentarze od off-topu.
bakunet
Do tej pory byłem przekonany, że komentarze są do postu :)
somekind
Tak, właśnie. Komentarze są do postu. A posty do tematu.
AF
  • Rejestracja:ponad 18 lat
  • Ostatnio:3 miesiące
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.

bakunet
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
AF
  • Rejestracja:ponad 18 lat
  • Ostatnio:3 miesiące
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?

bakunet
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.
AF
  • Rejestracja:ponad 18 lat
  • Ostatnio:3 miesiące
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ę.

bakunet
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.
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:ponad 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
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ć.

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
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:

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

        public IFilesService FilesService { get; }
        public IControlsService ControlsService { get; }
    }
Kopiuj
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;
        }
    }
Kopiuj
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
Shalom
  • Rejestracja:ponad 21 lat
  • Ostatnio:ponad 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Nie wiem jak to jest w C# ale w Javie widziałem kiedyś jakąś libkę która używała kontenera IoC i po prostu nie dało się jej użyć, bo gryzła się z aplikacją. Co jak ktoś w aplikacji ma już inne IoC albo to samo, ale w innej wersji? :)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
Shalom napisał(a):

Nie wiem jak to jest w C# ale w Javie widziałem kiedyś jakąś libkę która używała kontenera IoC i po prostu nie dało się jej użyć, bo gryzła się z aplikacją. Co jak ktoś w aplikacji ma już inne IoC albo to samo, ale w innej wersji? :)

Dobre pytanie, faktycznie to może być problem.

AreQrm
  • Rejestracja:ponad 11 lat
  • Ostatnio:7 miesięcy
  • Lokalizacja:Londyn
  • Postów:873
1

Ręcznie. Czy to poprzez fabryki, czy w jakiejś głównej metodzie IX something = new X(); IY somethingelse = new Y(something); .... i tak sobie lecisz. Oczywiście nie jest to super wygodne, ale ma zaletę. Od razu Cię boli jak klasa ma multum zależności.
Jeśli to co masz to biblioteka, to użyj buildera, i zamknij to w nim, żeby użytkownik mógł łatwo sobie zbudować to co potrzebuje. Albo Fabrykę, w zależności co jest odpowiednie w Twoim przypadku.


bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
AreQrm napisał(a):

Ręcznie. Czy to poprzez fabryki, czy w jakiejś głównej metodzie IX something = new X(); IY somethingelse = new Y(something); .... i tak sobie lecisz. Oczywiście nie jest to super wygodne, ale ma zaletę. Od razu Cię boli jak klasa ma multum zależności.
Jeśli to co masz to biblioteka, to użyj buildera, i zamknij to w nim, żeby użytkownik mógł łatwo sobie zbudować to co potrzebuje. Albo Fabrykę, w zależności co jest odpowiednie w Twoim przypadku.

Sorry, ale zdążyłem usunąć pytanie, bo miałem olśnienie ;p

Ale udało się. Najpierw tworzę fasadę która przyjmuje w konstruktor interfejsy:

Kopiuj
public class Facade
    {
        public Facade(IFilesService fileServ, IControlsService contrServ)
        {
            FilesService = fileServ;
            ControlsService = contrServ;
        }

        public virtual IFilesService FilesService { get; set; }
        public virtual IControlsService ControlsService { get; set; }
    }

W SUT w konstruktor fasady wrzucam nowe obiekty:

Kopiuj
public class MainService : IMainService
    {
        private IFilesService _fileService;
        private IControlsService _controlsService;

        public UiControlsModel Controls { get; set; }
        public Facade Facade { get; set; }

        public MainService()
        {
            Initialize();
        }

        public void Initialize()
        {
            Facade = new Facade(new FilesService(), new ControlsService());
        }
...
}

A w konstruktorze testu obiekty podmieniam makietami.

Kopiuj
public class MainServiceTests
    {
        private readonly Mock<IControlsService> _controlsServiceMock;
        private readonly Mock<IFilesService> _filesServiceMock;
        private readonly MainService _vm;

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

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

            _controlsServiceMock.Setup(c => c.SwitchOff())
                .Returns(_controlsModelOff);

            _vm = new MainService();
            _vm.Facade = new Facade(_filesServiceMock.Object, _controlsServiceMock.Object);
        }

        [Fact]
        public void SwitchOff_Called_ReturnsModel()
        {
            _vm.Controls = new UiControlsModel() { Jeden = false, Dwa = false, Trzy = false };

            _vm.SwitchOff();

            Assert.True(_vm.Controls.Trzy); //test przechodzi pozytywnie
        }
    }

Trochę mnie kłuje w oczy fakt, że w SUT muszę korzystać z własności publicznej Facade której przypisuję instancję, ale chyba inaczej się nie da... ? Czy publiczna public Facade Facade { get; set; } i nadpisanie w teście _vm.Facade to zła praktyka?

edytowany 3x, ostatnio: bakunet
mad_penguin
mad_penguin
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 4 lata
  • Lokalizacja:Rzeszów
0

Zrób internal konstruktor i zrób publiczną fabrykę która będzie konstruować ten obiekt. Potem w testach możesz użyć InternalsVisibleTo i użyć internalowego konstruktora.

somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
4

Żeby dało się tego sensownie używać, to powinieneś mieć po prostu klasę, która wczytuje konfigurację z pliku, i zwraca typ IParamsLogger. Coś w rodzaju:

Kopiuj
var logger = LoggerManager.GetLogger()

I to wszystko, potem użytkownik może wołać sobie metody tego loggera według potrzeb.

Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (które wypadałoby jakoś sensowniej nazwać, np. LogEntryFormatter). I ta klasa nie potrzebuje do niczego żadnego interfejsu. Podobnie jak nie potrzebny jest IFileService.
Powinieneś mieć dwa zestawy testów:

  • jednostkowe (tylko przetwarzanie LogModel w string realizowane obiecnie przez StringService, które nie wymaga żadnych mocków, kontenerów IoC ani nawet DI w ogóle.
  • integracyjne (czyli LoggerManager wczytuje konfigurację, tworzy ParamsLogger, ten zapisuje coś faktycznie do pliku).

Jedyny obecnie sensowny interfejs to IParamsLogger, bo użytkownik biblioteki (jeśli bardzo chce, bo to i tak nie ma sensu) może sobie w testach zastąpić pustym mockiem, który nie będzie śmiecił po dysku.
Jeśli chodzi o abstrakcje, których tu faktycznie brakuje, to:
ITarget - bo logger powinien móc pisać do róznych miejsc, nie tylko do plików na dysku.
IFormatter, bo może fajnie byłoby móc generować JSONa albo XMLa, a nie tylko stringa oddzielonego |.

edytowany 1x, ostatnio: somekind
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
somekind napisał(a):

(...) Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (...)

A co z testowaniem ParamsLogger? Jakbym potworzył makiety dla serwisów, to mógłbym publiczną metodę przetestować.

Co do ITarget oraz IFormatter, to w przyszłości może rozwinę bibliotekę, jak najdzie mnie taka potrzeba.

P.S. klasa wczytuje konfigurację z pliku, choć rozwiązałem to na trochę innej zasadzie, ale Twój pomysł ma chyba większy potencjał. Jak pamiętam NLog i log4net chyba podobnie tworzą instancję.

edytowany 2x, ostatnio: bakunet
somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
1
bakunet napisał(a):
somekind napisał(a):

(...) Jeśli chodzi o testowanie, to główne co można u Ciebie testować jednostkowo jest StringService (...)

A co z testowaniem ParamsLogger? Jakbym potworzył makiety dla serwisów, to mógłbym publiczną metodę przetestować.

Ja tam widzę większy problem - jedna klasa ParamsLogger ma w sobie odpowiedzialność odczytu konfiguracji, skonfigurowania samej siebie, dodawania kolejnych wpisów loga, jak i połowę logiki związanej z zapisywaniem do pliku. Na Twoim miejscu bym to poprawił, do wszystkiego powinny być oddzielne klasy, a ParamsLogger nie powinien mieć zwłaszcza logiki specyficznej do operacji dyskowych.

Generalnie prosta architektura ułatwia testowanie. No i trzeba się zastanowić - co takiego konkretnie chcesz w ParamsLogger testować, czego obecnie nie pokryją testy integracyjne?

edytowany 2x, ostatnio: somekind
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
somekind napisał(a):

Ja tam widzę większy problem - jedna klasa ParamsLogger ma w sobie odpowiedzialność odczytu konfiguracji, skonfigurowania samej siebie, dodawania kolejnych wpisów loga, jak i połowę logiki związanej z zapisywaniem do pliku. (...)

Przecież ParamsLogger korzysta z serwisów:

  • _fileService - zapisuje string
  • _stringService - zwraca string do zapisania
  • _configService - zwraca konfigurację

Co tak naprawdę ParamsLogger robi, to:

  • inicjalizacja
  • tworzy obiekt MethodBase ze StackTrace
  • obsługuje nieobsłużone wyjątki
    Reszta jest przyjmowana z serwisów.
somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
1

No właśnie o to mi chodzi, że ParamsLogger robi zbyt wiele. https://en.wikipedia.org/wiki/Single_responsibility_principle

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
somekind napisał(a):

No właśnie o to mi chodzi, że ParamsLogger robi zbyt wiele. https://en.wikipedia.org/wiki/Single_responsibility_principle

Ok, już rozumiem. Wydawało mi się, że jeśli klasa robi to jedynie przez interfejsy serwisów, to tak naprawdę one biorą na siebie ciężar pracy. Ale wnioskuję, że tak naprawdę one powinny wykonać całą pracę? Z zapisem nie ma problemu. Ale zastanawiam się jak skonfigurować obiekt loggera jeśli nie będzie on przyjmował konfiguracji...?

somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
0
bakunet napisał(a):

Ok, już rozumiem. Wydawało mi się, że jeśli klasa robi to jedynie przez interfejsy serwisów, to tak naprawdę one biorą na siebie ciężar pracy.

No jest to możliwe, ale raczej nie w Twoim przypadku. Ale może się mylę, więc wyjaśnij - czemu masz Timer w tym ParamsLogger?

Ale zastanawiam się jak skonfigurować obiekt loggera jeśli nie będzie on przyjmował konfiguracji...?

No w tym rzecz, że powinien przyjmować konfigurację. A obecnie nie przyjmuje - konstruktor jest przeciez bezparametrowy.

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
somekind napisał(a):

(...)
No w tym rzecz, że powinien przyjmować konfigurację. A obecnie nie przyjmuje - konstruktor jest przeciez bezparametrowy.

Próbuję zrozumieć co dokładnie masz na myśli. Choć wydaje mi się, że powoli zaczynam kumać. Wcześniej ponownie wpadłem w swoją pułapkę, i idąc na łatwiznę skorzystałem z IoC rejestrując mój logger jako singleton. Teraz w każdej klasie próbuję utworzyć instancję na zasadzie:

Kopiuj
private static readonly IParamsLogger _log = new ParamsLogger();

I tu zaczynają się schody. Wydaje mi się, że albo ponownie będę musiał gdzieś utworzyć singleton, albo utworzyć kolekcję loggerów, gdzie każdy kolejny będzie dodawany. Jeszcze nie wiem jak to ogarnąć, ale coś wymyślę. Inaczej skończę z n ilością instancji.

Jak chodzi o konfigurację samego loggera, to wydaje mi się, że będzie mógł być tworzony przy wykorzystaniu info ze stosu, więc nie muszę przyjmować nic w konstruktorze. Jak wspominałem wcześniej, pozostaje mi jedynie kwestia utworzenia jednego obiektu zamiast tuzina takich samych. A singleton lub kolekcja będą zasysać info z pliku konfiguracyjnego.

O to Ci chodziło, jestem blisko?

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0
somekind napisał(a):

No właśnie o to mi chodzi, że ParamsLogger robi zbyt wiele. https://en.wikipedia.org/wiki/Single_responsibility_principle

W dzisiejszym odcinku Ulicy Sezamkowej się nauczyłem jak utworzyć singleton oraz wzorca fabryki. W fabryce ładuję konfigurację i przekazuję ją do właściwej klasy loggera. Nie wiem czy nie przekombinowałem trochę z tą własnością. Ale działa, lockuje się na wypadek kilku wątków, więc powinno być spoko. Też później będę chciał jeszcze spojrzeć świeżym okiem na połączenie fabryki + fasady.

Będę wdzięczny za wszelkie uwagi.

somekind
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:dzień
  • Lokalizacja:Wrocław
0
bakunet napisał(a):

I tu zaczynają się schody. Wydaje mi się, że albo ponownie będę musiał gdzieś utworzyć singleton, albo utworzyć kolekcję loggerów, gdzie każdy kolejny będzie dodawany. Jeszcze nie wiem jak to ogarnąć, ale coś wymyślę. Inaczej skończę z n ilością instancji.

Ale co w tym złego? Czemu Ci zależy na singletonie?

Jak chodzi o konfigurację samego loggera, to wydaje mi się, że będzie mógł być tworzony przy wykorzystaniu info ze stosu, więc nie muszę przyjmować nic w konstruktorze. Jak wspominałem wcześniej, pozostaje mi jedynie kwestia utworzenia jednego obiektu zamiast tuzina takich samych. A singleton lub kolekcja będą zasysać info z pliku konfiguracyjnego.

Jeśli masz obiekt z normalnym konsturktorem, w którym wstrzykujesz zależności, np. ParamsLogger(IFileService, IStringService), to w testach możesz utworzyć mocki, a w normalnym kodzie możesz mieć jakąś fabryczkę, która wczytuje konfiguracje z pliku, tworzy odpowiednie FileService oraz StringService, a na końcu faktyczny ParamsLogger.
Wszystko jest proste, eleganckie, testowalne, rozszerzalne i nie ma niespodzianek typu logger, który po utworzeniu wczytuje sobie sam swoją konfigurację z jakiegoś pliku.

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0

@somekind:

Chcę mieć singleton, żeby mieć kontrolę nad zapisem logów do jednego pliku.

Co do fabryki to wydaje mi się że faktycznie może ona istnieć bez fasady. Popróbuję zaraz na prostych przykładach żeby ją lepiej zrozumieć i umieć wykorzystać w tym projekcie.

bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0

@somekind:

Poniżej mam uproszczony kod. Na razie udało mi się zrobić coś na zasadzie jak niżej. Chcę uniknąć wstrzykiwania zależności w konstruktor ObjectService, w przeciwnym wypadku IoC kontener aplikacji będzie chciał rozwiązać zależności. **A nie chcę żeby projekt aplikacji wiedział o zależnościach biblioteki. **Zastanawiam się jak rozwiązać problem podmieniania fabryki przy testowaniu. Więc stworzyłem własność Factory, która w teście jest podmieniana makietą:
```csharp
public class ObjectService : IObjectService
{
public ObjectService()
{
Factory = new ObjectFactory();
}

        public IObjectFactory Factory { get; set; }

        public IObjectModel GetObject(string firstName)
        {
            return Factory.GetObject(firstName);
        }
    }
Kopiuj
Więc w teście jednostkowym wykorzystuję `_factoryMock`
```csharp
public class ObjectServiceTests
        {
            private readonly ObjectService _serv;
            private readonly Mock<IObjectFactory> _factoryMock;
            private readonly Mock<ISomeService> _someServMock;
            private readonly string _someFirstName = "SomeFirstName";
    
            public ObjectServiceTests()
            {
                _someServMock = new Mock<ISomeService>();
                _someServMock.SetupGet(s => s.SecondName).Returns("Cos");
    
                _factoryMock = new Mock<IObjectFactory>();
                _factoryMock.Setup(f => f.GetObject(_someFirstName))
                    .Returns(new ObjectModel(_someFirstName, _someServMock.Object));
    
                _serv = new ObjectService();
            }
    
            [Fact]
            public void GetObject_Called_ReturnsObject()
            {
                _serv.Factory = _factoryMock.Object;
    
                IObjectModel newObj = _serv.GetObject(_someFirstName);
    
                Assert.Equal(_someFirstName + ", Cos", newObj.FullName);
            }
        }

Zastanawiam się czy wykorzystanie public IObjectFactory Factory { get; set; } jest poprawne? Na razie nie mam lepszego pomysłu.

EDIT właśnie wyczytałem, że to jest forma property DI. Chyba przy niej zostanę, jako że zaspokaja moje oczekiwania.

edytowany 3x, ostatnio: bakunet
bakunet
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
  • Postów:1666
0

Ok, chyba dopiąłem temat:

Choć w fabryce mogłem interfejsy z tej metody wczytywać do Loggera inaczej, ale to już zostawiam na przyszłość TODO

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.