Próbuję zmusić Unity do zarządzania tworzeniem moich obiektów i chcę mieć pewne parametry inicjalizacji, które nie są znane do czasu wykonania:
W tej chwili jedynym sposobem, w jaki mogłem wymyślić, jak to zrobić, jest posiadanie metody Init w interfejsie.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Następnie, aby go użyć (w Unity), zrobiłbym to:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
W tym scenariuszu runTimeParam
parametr jest określany w czasie wykonywania na podstawie danych wejściowych użytkownika. Ten trywialny przypadek po prostu zwraca wartość, runTimeParam
ale w rzeczywistości parametr będzie czymś w rodzaju nazwy pliku, a metoda initialize zrobi coś z plikiem.
Stwarza to szereg problemów, a mianowicie to, że Initialize
metoda jest dostępna w interfejsie i można ją wywołać wiele razy. Ustawienie flagi w implementacji i wyrzucenie wyjątku przy powtarzającym się wywołaniu Initialize
wydaje się być niezgrabne.
W momencie, w którym rozwiązuję mój interfejs, nie chcę nic wiedzieć o implementacji IMyIntf
. Chcę jednak wiedzieć, że ten interfejs wymaga pewnych jednorazowych parametrów inicjalizacyjnych. Czy istnieje sposób, aby w jakiś sposób dodać adnotacje (atrybuty?) Do interfejsu tymi informacjami i przekazać je do frameworka podczas tworzenia obiektu?
Edycja: nieco bardziej opisałem interfejs.
źródło
runTimeParam
jest zależnością określaną w czasie wykonywania na podstawie danych wejściowych użytkownika. Czy alternatywą powinno być podzielenie go na dwa interfejsy - jeden do inicjalizacji, a drugi do przechowywania wartości?Odpowiedzi:
W każdym miejscu, w którym do skonstruowania określonej zależności potrzebna jest wartość czasu wykonywania, rozwiązaniem jest Abstract Factory .
Posiadanie metod inicjalizacji na interfejsach pachnie nieszczelną abstrakcją .
W twoim przypadku powiedziałbym, że powinieneś modelować
IMyIntf
interfejs na podstawie tego, jak chcesz go używać, a nie jak zamierzasz tworzyć jego implementacje. To szczegół implementacji.Dlatego interfejs powinien wyglądać po prostu:
Teraz zdefiniuj fabrykę abstrakcyjną:
Możesz teraz utworzyć konkretną implementację,
IMyIntfFactory
która tworzy konkretne wystąpienia,IMyIntf
takie jak ta:Zwróć uwagę, jak to pozwala nam chronić niezmienniki klasy za pomocą
readonly
słowa kluczowego. Żadne śmierdzące metody inicjalizacji nie są konieczne.IMyIntfFactory
Realizacja może być tak proste, jak to:W przypadku wszystkich konsumentów, w których potrzebujesz
IMyIntf
instancji, po prostu pobierasz zależnośćIMyIntfFactory
, żądając jej za pośrednictwem Constructor Injection .Każdy kontener DI Container wart swojej soli będzie mógł automatycznie połączyć
IMyIntfFactory
instancję, jeśli zarejestrujesz ją poprawnie.źródło
MyIntf
implementacji przez fabrykę wymaga więcej niżrunTimeParam
(czytaj: inne usługi, które można by rozwiązać za pomocą IoC), nadal masz do czynienia z rozwiązaniem tych zależności w fabryce. Podoba mi się odpowiedź @PhilSandler polegająca na przekazaniu tych zależności do konstruktora fabryki w celu rozwiązania tego problemu - czy to również Ty podchodzisz do tego?Zwykle, gdy napotkasz taką sytuację, musisz ponownie przyjrzeć się projektowi i określić, czy mieszasz obiekty stanowe / dane z czystymi usługami. W większości (nie we wszystkich) przypadkach będziesz chciał zachować te dwa typy obiektów oddzielnie.
Jeśli potrzebujesz parametru specyficznego dla kontekstu przekazanego w konstruktorze, jedną z opcji jest utworzenie fabryki, która rozwiązuje zależności usług za pośrednictwem konstruktora i przyjmuje parametr czasu wykonywania jako parametr metody Create () (lub Generate ( ), Build () lub jakkolwiek nazwiesz metody fabryczne).
Posiadanie seterów lub metody Initialize () jest ogólnie uważane za zły projekt, ponieważ musisz "pamiętać", aby je wywołać i upewnić się, że nie otwierają zbyt wiele stanu twojej implementacji (tj. Co ma powstrzymać kogoś przed ponownym -calling initialize czy setter?).
źródło
Z taką sytuacją spotkałem się również kilka razy w środowiskach, w których dynamicznie tworzę obiekty ViewModel w oparciu o obiekty Model (bardzo dobrze opisane przez ten inny poście Stackoverflow ).
Podobało mi się jak rozszerzenie Ninject pozwalające na dynamiczne tworzenie fabryk w oparciu o interfejsy:
Bind<IMyFactory>().ToFactory();
Nie mogłem znaleźć podobnej funkcjonalności bezpośrednio w Unity ; więc napisałem własne rozszerzenie do IUnityContainer, które pozwala rejestrować fabryki, które będą tworzyć nowe obiekty na podstawie danych z istniejących obiektów zasadniczo mapujących z jednej hierarchii typów do innej hierarchii typów: UnityMappingFactory @ GitHub
Mając na celu prostotę i czytelność, otrzymałem rozszerzenie, które umożliwia bezpośrednie określanie mapowań bez deklarowania poszczególnych klas fabrycznych lub interfejsów (oszczędność czasu rzeczywistego). Po prostu dodajesz mapowania dokładnie tam, gdzie rejestrujesz klasy podczas normalnego procesu ładowania ...
Następnie wystarczy zadeklarować interfejs fabryki mapowania w konstruktorze dla CI i użyć jego metody Create () ...
Jako dodatkowy bonus, wszelkie dodatkowe zależności w konstruktorze mapowanych klas również zostaną rozwiązane podczas tworzenia obiektu.
Oczywiście to nie rozwiąże każdego problemu, ale jak dotąd bardzo mi się to służy, więc pomyślałem, że powinienem się nim podzielić. Więcej dokumentacji znajduje się na stronie projektu w GitHub.
źródło
Nie mogę odpowiedzieć konkretną terminologią Unity, ale wygląda na to, że dopiero uczysz się o wstrzykiwaniu zależności. Jeśli tak, zachęcam do przeczytania krótkiej, jasnej i pełnej informacji instrukcji obsługi Ninject .
To przeprowadzi Cię przez różne opcje, które masz podczas korzystania z DI, i jak uwzględnić konkretne problemy, które napotkasz po drodze. W twoim przypadku najprawdopodobniej będziesz chciał użyć kontenera DI do utworzenia wystąpienia obiektów i sprawić, by ten obiekt uzyskał odwołanie do każdej z jego zależności za pośrednictwem konstruktora.
Przewodnik zawiera również szczegółowe informacje na temat opisywania metod, właściwości, a nawet parametrów przy użyciu atrybutów do rozróżniania ich w czasie wykonywania.
Nawet jeśli nie używasz Ninject, przewodnik zawiera koncepcje i terminologię funkcjonalności, która pasuje do twojego celu, i powinieneś być w stanie zmapować tę wiedzę na Unity lub inne frameworki DI (lub przekonać cię, aby spróbować Ninject) .
źródło
Myślę, że rozwiązałem to i wydaje się raczej zdrowe, więc musi być w połowie poprawne :))
Podzieliłem się
IMyIntf
na interfejsy „getter” i „setter”. Więc:Następnie realizacja:
IMyIntfSetter.Initialize()
nadal można go wywołać wiele razy, ale używając fragmentów paradygmatu lokalizatora usług możemy to całkiem ładnie opakować, tak żeIMyIntfSetter
jest to prawie wewnętrzny interfejs, który różni się odIMyIntf
.źródło