Przełącz lub Słownik podczas przypisywania do nowego obiektu

12

Ostatnio wolę mapować relacje 1-1 używając Dictionarieszamiast Switchinstrukcji. Uważam, że jest to trochę szybsze pisanie i łatwiejsze do mentalnego przetworzenia. Niestety podczas mapowania na nowe wystąpienie obiektu nie chcę go definiować w następujący sposób:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

Biorąc pod uwagę ten konstrukt, zmarnowałem cykle procesora i pamięć, tworząc 3 obiekty, robiąc wszystko, co mogą zawierać ich konstruktory, i ostatecznie wykorzystałem tylko jeden z nich. Wierzę również, że odwzorowanie innych obiektów fooDict[0]w tym przypadku spowoduje, że będą odwoływać się do tej samej rzeczy, zamiast tworzyć nową instancję Foozgodnie z przeznaczeniem. Rozwiązaniem byłoby użycie zamiast tego lambda:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

Czy dochodzi do tego, że jest to zbyt mylące? Na ()końcu łatwo tego przeoczyć . A może mapowanie do funkcji / wyrażenia jest dość powszechną praktyką? Alternatywą byłoby użycie przełącznika:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

Które wywołanie jest bardziej akceptowalne?

  • Słownik, w celu szybszego wyszukiwania i mniejszej liczby słów kluczowych (wielkość liter i podział)
  • Przełącznik: częściej spotykany w kodzie, nie wymaga użycia obiektu Func <> do celów pośrednich.
KChaloux
źródło
2
bez lambda będziesz zwracał tę samą instancję za każdym razem, gdy przeprowadzasz wyszukiwanie z tym samym kluczem (jak w fooDict[0] is fooDict[0]). zarówno w przypadku lambda, jak i przełącznika tak nie jest
maniak zapadkowy
@ratchetfreak Tak, faktycznie zdałem sobie z tego sprawę, kiedy pisałem przykład. Myślę, że gdzieś to zanotowałem.
KChaloux
1
Wydaje mi się, że jawne umieszczenie go w stałej oznacza, że ​​potrzebny obiekt musi być modyfikowalny. Ale jeśli pewnego dnia sprawisz, że będą niezmienne, najlepszym rozwiązaniem będzie zwrot obiektu bezpośrednio. Dict można umieścić w polu const i ponieść koszty tworzenia tylko raz w całej aplikacji.
Laurent Bourgault-Roy

Odpowiedzi:

7

To interesujące podejście do wzoru fabrycznego . Lubię kombinację słownika i wyrażenia lambda; sprawiło, że spojrzałem na ten pojemnik w nowy sposób.

Ignoruję obawy w twoim pytaniu o cykle procesora, jak wspomniałeś w komentarzach, że podejście inne niż lambda nie zapewnia tego, czego potrzebujesz.

Myślę, że każde podejście (zmiana vs. słownik + lambda) będzie w porządku. Jedynym ograniczeniem jest to, że korzystając ze Słownika, ograniczasz rodzaje danych wejściowych, które możesz otrzymać w celu wygenerowania zwróconej klasy.

Użycie instrukcji switch zapewni większą elastyczność parametrów wejściowych. Jeśli jednak to musi być problem, możesz owinąć słownik wewnątrz metody i uzyskać ten sam efekt końcowy.

Jeśli jest nowy dla Twojego zespołu, skomentuj kod i wyjaśnij, co się dzieje. Zadzwoń po recenzję kodu zespołu, poprowadź ich przez to, co zostało zrobione i poinformuj ich o tym. Poza tym wygląda dobrze.


źródło
Niestety, mniej więcej miesiąc temu, mój zespół składa się wyłącznie ze mnie (odejście na prowadzenie). Nie myślałem o jego znaczeniu dla wzoru fabrycznego. Właściwie to fajna obserwacja.
KChaloux
1
@KChalhal: Oczywiście, jeśli używasz tylko wzorca metody fabrycznej, twoja case 0: quux = new Foo(); break;staje się, case 0: return new Foo();co jest szczerze mówiąc, równie łatwa do napisania i znacznie łatwiejsza do odczytania niż{ 0, () => new Foo() }
pdr
@pdr To już pokazuje kilka miejsc w kodzie. Prawdopodobnie istnieje dobry powód, aby stworzyć fabryczną metodę na obiekcie, która zainspirowała to pytanie, ale uznałem, że było wystarczająco interesujące, aby zadać to samo.
KChaloux
1
@KChalhal: Przyznaję, że nie przepadam za ostatnią obsesją związaną z zastąpieniem przełącznika / skrzynki słownikiem. Nie widziałem jeszcze przypadku, w którym upraszczanie i izolowanie przełącznika według własnej metody nie byłoby bardziej skuteczne.
pdr
@pdr Obsession to mocne słowo do użycia tutaj. Więcej uwagi przy podejmowaniu decyzji, jak radzić sobie z jednorazowymi mapowaniami między wartościami. Zgadzam się, że w przypadkach, w których jest powtarzany, najlepiej jest wyodrębnić metodę tworzenia.
KChaloux
7

C # 4.0 daje ci Lazy<T>klasę podobną do twojego drugiego rozwiązania, ale wyraźniej krzyczy „Leniwa inicjalizacja”.

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}
Avner Shahar-Kashtan
źródło
Ooh, nie wiedziałem tego.
KChaloux,
Och, to jest to miłe!
Laurent Bourgault-Roy
2
Jednak po wywołaniu Lazy.Value używa tego samego wystąpienia przez cały okres jego istnienia. Zobacz Leniwa inicjalizacja
Dan Lyons,
Oczywiście inaczej nie byłaby to leniwa inicjalizacja, wystarczy ponowna inicjalizacja za każdym razem.
Avner Shahar-Kashtan
OP twierdzi, że potrzebuje go za każdym razem do tworzenia nowych instancji. Zarówno drugie rozwiązanie z lambdas, jak i trzecie rozwiązanie z przełącznikiem, robią to, podczas gdy pierwsze rozwiązanie i implementacja Lazy <T> nie.
Dan Lyons,
2

Stylistycznie myślę, że czytelność między nimi jest równa. Łatwiej jest zrobić wstrzyknięcie zależności za pomocą Dictionary.

Nie zapominaj, że musisz sprawdzić, czy klucz istnieje podczas korzystania z Dictionary, i musisz zapewnić rezerwę, jeśli nie istnieje.

Wolałbym switchinstrukcję dla statycznych ścieżek kodu, a Dictionarydla dynamicznych ścieżek kodu (gdzie można dodawać lub usuwać wpisy). Kompilator może być w stanie przeprowadzić statyczną optymalizację z tym switch, czego nie może zrobić z Dictionary.

Co ciekawe, ten Dictionarywzorzec jest czasem tym, co ludzie robią w Pythonie, ponieważ w Pythonie brakuje switchinstrukcji. W przeciwnym razie używają łańcuchów if-else.

M. Dudley
źródło
1

Ogólnie wolałbym nie.

Cokolwiek to zużywa, powinno działać z Func<int, IBigObject>. Zatem źródłem mapowania może być słownik lub metoda, która ma instrukcję switch, wywołanie usługi sieci Web lub wyszukiwanie plików ... cokolwiek.

Jeśli chodzi o implementację, wolę słownik, ponieważ łatwiej jest go refaktoryzować ze „słownika kodu twardego, klucza wyszukiwania, zwrotu wyniku” do „załadowania słownika z pliku, klucza wyszukiwania, wyniku wyszukiwania”.

Telastyn
źródło