Przełączanie vs polimorfizm w przypadku modelu i widoku

12

Nie mogę znaleźć lepszego rozwiązania mojego problemu. Mam kontroler widoku, który przedstawia listę elementów. Te elementy to modele, które mogą być instancjami B, C, D itp. I dziedziczyć po A. W tym kontrolerze widoku każdy element powinien przejść do innego ekranu aplikacji i przekazać niektóre dane, gdy użytkownik wybierze jeden z nich . Dwie alternatywy, które przychodzą mi do głowy to (zignoruj ​​składnię, to nie jest konkretny język)

1) przełącznik (wiem, że jest do bani)

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);

    switch(a.type) {
         case b:
             B b = (B)a;
             go to screen X;
             x.v1 = b.v1; // fill X with b data
             x.v2 = b.v2; 
         case c:
             go to screen Y;
         etc...
    }
}

2) polimorfizm

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);
    Screen s = new (a.getDestinationScreen()); //ignore the syntax
    s.v1 = a.v1;   // fill s with information about A
    s.v2 = a.v2;
    show(s);
}

//inside B
Class getDestinationScreen(void) {
    return Class(X);
}

//inside C
Class getDestinationScreen(void) {
    return Class(Y);
}

Mój problem z rozwiązaniem 2 polega na tym, że ponieważ B, C, D itp. Są modelami, nie powinni wiedzieć o rzeczach związanych z wyświetlaniem. A może powinni w takim przypadku?

Raphael Oliveira
źródło

Odpowiedzi:

6

Myślę, że może przydałaby się tu implementacja wzorca odwiedzających . Klasy B, C i D musiałyby zostać „odwiedzone”, aby określić typ widoku, ale nie musiałyby wiedzieć nic o widokach. ViewFactory (poniżej) odwiedziłby przedmiot i użył polimorfizmu, aby określić poprawny widok do zbudowania. Brak instrukcji zamiany. Nie pytaj o wewnętrzne elementy modelu, aby zdecydować, co zbudować. Interfejs użytkownika korzysta z polimorfizmu, aby wybrać właściwy ustawiacz dla widoku. Seter może przekazać element do konstruktora określonego typu widoku (X, Y lub Z), a następnie ten widok może wypełnić pola z tego elementu.

   //inside the view controller
   void onClickItem(int index) {
      ViewFactoryVisitable a = items.get(index);
      ViewFactory aViewFactory = new ViewFactory(
      s = aViewFactory.getViewFor(a);
      show(s);
   }

--------

//Element interface
public interface ViewFactoryVisitable
{
    public void accept(ViewFactory theViewFactory);
}

---------

public interface ViewFactoryVisitor
{
   // one for each concrete type, polymorphism will choose correct setter
   public set setViewFor(B b);
   public set setViewFor(C c);
   public set setViewFor(D d);
}

--------

// B, C, D must implement this visitable interface
class B implements ViewFactoryVisitable
{ 
   ...

   //accept the ViewFactory as a visitor
   public void accept(ViewFactoryVisitor theViewFactoryVisitor)
   {
      theViewFactoryVisitor. setViewFor(this);
   }

   ...
} 

--------

class ViewFactory implements ViewFactoryVisitor
{
   ViewFactory(ViewFactoryVisitable theItem) {
      theItem.accept(this);
   }

   private View mView = null;
   ...

   public void setViewFor(B b) {
      // construct a view x and populate with data from b
      mView = new ViewX(b); 
   }

   public void setViewFor(C c) {
      mView = new ViewY(c); 
   }

   public void setViewFor(D d) {
      mView = new ViewZ(d); 
   }

   View getView() {
      return mView;
   }

} 
Chuck Krutsinger
źródło
1
Jeśli implementacja accept nie powinna być „theViewFactoryVisitor.setViewFor (this);” Przepraszam, jeśli jestem głupi!
Ryan,
@Ryan Good catch. Ten błąd istnieje od 3 lat!
Chuck Krutsinger
1

Bardziej komentarz niż odpowiedź, ale myślę, że to los. Albo Zobacz musi wiedzieć wszystko o modelu więc może wybrać ekran (przełącznik) lub model musi wiedzieć wszystko o widok więc to może wybrać ekran (polimorfizm). Myślę, że musisz wybrać to, co według ciebie będzie najprostsze w czasie; nie ma właściwej odpowiedzi na pytanie. (Mam nadzieję, że ktoś może udowodnić, że się mylę). Ja sam skłaniam się ku polimorfizmowi.

Wpadłem trochę na ten problem. Najbardziej irytującym przypadkiem była klasa Wędrowca, której przypadki wędrowały po mapie. Aby go narysować, albo wyświetlacz musiał wiedzieć o Wędrowcu, albo Wędrowiec musiał wiedzieć o wyświetlaczu. Problem polegał na tym, że pojawiły się dwa wyświetlacze. Ponieważ liczba różnych podklas Wędrowca była duża i stale rosła, umieściłem kod rysunkowy w podklasach Wędrowca. Oznaczało to, że każda duża klasa miała dokładnie jedną metodę, która musiała wiedzieć o Graphics2D i dokładnie jedną metodę, która musiała wiedzieć o Java3D. Brzydki.

Ostatecznie podzieliłem klasę, dając mi dwie równoległe struktury klas. Klasa Wanderer została uwolniona od wiedzy o grafice, ale klasa DrawWanderer nadal musiała wiedzieć więcej o Wanderer niż była przyzwoita i musiała wiedzieć o dwóch (i może więcej) zupełnie różnych środowiskach graficznych (Widoki). (Przypuszczam, że ten pomysł podziału klasy może być rodzajem odpowiedzi, ale tak naprawdę tylko zawiera problem.)

Myślę, że jest to bardzo ogólny i fundamentalny problem projektowania obiektowego.

RalphChapin
źródło
0

Myślę, że przejście z przełącznikiem jest lepszą opcją niż przejście z polimorfizmem w tym przypadku.

Jest to dość prosta rzecz, więc nie sądzę, że musi to być nadmiernie skomplikowane przez zastosowanie polimorfizmu.

Chciałbym monetyzować w tym poście na blogu . Instrukcje przełączania niekoniecznie są brzydkie, o ile prawidłowo je używasz. A w twoim przypadku modele abstrakcyjne, takie jak te do użycia w kontrolerze, mogą być nadmierne i mogą przynosić niepożądane rezultaty. Jak naruszanie SRP.

Maru
źródło
Rozumiem co masz na myśli. Cóż, nie sądzę, że polimorfizm jest nadmiernie skomplikowany. A klasa A w moim przypadku nie jest abstrakcyjna, w rzeczywistości jest używana. Dziękuję za twoje przemyślenia, choć wciąż czekam na lepsze rozwiązanie i bardziej skłonny do podejścia polimorficznego.
Raphael Oliveira
1
nie martw się, po prostu daję 2 centy w tej sprawie. Chociaż, aby rozwiązać problem z koniecznością umieszczenia logiki widoku w swoich modelach, zawsze możesz po prostu owinąć je dekoratorami, aby modele nie były logiczne. Następnie można użyć polimorfizmu w klasach dekoratorów zamiast w modelu.
Maru,
0

Mój problem z rozwiązaniem 2 polega na tym, że ponieważ B, C, D itp. Są modelami, nie powinni wiedzieć o rzeczach związanych z wyświetlaniem.

Zgadzam się z tą troską. Niepokoi mnie również to, że obiekty, które znajdują się w comboboxie, będą się zachowywać. Nie jestem pewien, czy to „zła rzecz”, która nigdy tego nie zrobiła, wydaje mi się to po prostu nienaturalnym wyborem.

Poza tym nie wydaje się, Aa jego podklasy są typem, z którym masz interesujący polimorfizm. Ciekawy typ jest w rzeczywistości Screen. W tym przykładzie Ajest to tylko klasa, która przechowuje informacje w celu informowania Screenstworzenia.

Jeśli sprawisz, że combobox zawiera listę a.typezwrotów, wówczas instrukcja switch wydaje się bardziej naturalna. Jednak zamiast umieszczać go bezpośrednio w module obsługi zdarzeń kliknięcia, umieściłbym go w ScreenFactory. Następnie masz:

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);

    s = _screenFactory.GetScreen(a);
    show(s);
    }
}

//inside a ScreenFactory implementation
internal Screen GetScreen(A typeIndicator)
{
switch(a.type) {
     case b:
         return new ScreenX();
     case c:
         return new ScreenY();
     etc...        
}

Pozwala to przetestować zachowanie budowania ekranu i ładnie wyciąga niektóre funkcje z interfejsu użytkownika. Utrzymuje nienaruszone nakładanie warstw View. Być może upraszcza to twój projekt, jeśli to oznacza, Aa podklasy można zwinąć w typeflagę, którą zawierają.

wzrost
źródło
Dziękuję za wysoką odpowiedź. Niestety modele zawierają wiele informacji, nie tylko ich typ lub kontroler widoku docelowego. Ponadto, chociaż nie wspomniałem, ScreenX, ScreenY itp. Muszą otrzymywać informacje o B, C, D na budowie, więc nie mogę korzystać z fabryki tylko tego typu, muszę przekazać sam model.
Raphael Oliveira