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.

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.


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
}

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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.