2009-07-28 22 views
5

Piszę aplikację internetową .NET, w której administratorzy mogą dostosowywać różne formularze wprowadzania danych do swoich użytkowników. Istnieje około pół tuzina różnych typów pól, które administratorzy mogą tworzyć i dostosowywać (np. Tekst, numer, rozwijanie, przesyłanie plików). Wszystkie pola mają zestaw podstawowych atrybutów/zachowań (czy wymagane jest pole? Czy będzie mieć domyślną wartość pola?). Istnieje również szereg specyficznych dla danego pola atrybutów/zachowań (np. Lista rozwijana ma atrybut źródła danych, ale pole tekstowe nie). W celu uproszczenia pozostawiam wiele innych cech domeny problemowej.Metoda przeciążania i polimorfizm

Hierarchia klas jest prosta: abstrakcyjna nadklasa, która zawiera w sobie typowe zachowania/atrybuty oraz około pół tuzina konkretnych podklas zajmujących się konkretnymi tematami.

Każdy typ pola jest renderowany (tzn. Mapowany) jako określony typ formantu serwera .NET, który wywodzi się z System.Web.UI.Control.

stworzyłem następujący kod do mapowania wartości między obiektów domeny pola i odpowiadających im kontroli UI:

public static void Bind(Control control, IList<DocumentFieldBase> fieldBaseList) 

    foreach (DocumentFieldBase fieldBase in fields){ 

      if (typeof (DocumentFieldText).IsInstanceOfType(fieldBase)){ 
       TextBox textbox = (TextBox) control; 
       textbox.Text = (fieldBase as DocumentFieldText).GetValue(); 
      } 

      if (typeof (DocumentFieldDropDown).IsInstanceOfType(fieldBase)){ 
       DropDown dropDown= (DropDown) control; 
       dropDown.Text = (fieldBase as DocumentFieldSelectOne).GetValue().Text; 
       dropDown.DataSource= (fieldBase as DocumentFieldSelectOne).DataSource; 
       dropDown.Id= (fieldBase as DocumentFieldSelectOne).GetValue().Id; 
      } 

      //more if statements left out for brevity 
     } 
} 

Chcę porzucić tych bezbożnych if które wykonują typ kontroli. Podejście, do którego dążyłem, polegało na stworzeniu przeciążenia metody dla każdej kombinacji pola/kontroli za pomocą typowania podklas. Na przykład:

public static void Bind(TextBox control, DocumentFieldText fieldText){ 
//some implementation code 
} 
public static void Bind(DropDown control, DocumentFieldDropDown fieldDropDown){ 
//some implementation code 
} 

Miałem nadzieję, że mogę liczyć na .NET następnie wywołać odpowiednią przeciążenie w wykonawczego pomocą określonej podklasy używany: Na przykład:

foreach (DocumentFieldBase field in fields){ 
    Control control = FindControl(field.Identifier); 
    Bind(control, field) 
} 

Niestety, dławiki kompilatora, gdy próbuję tego: Argument "1": nie można przekonwertować z "System.Web.UI.Control" na "TextBox".

Jeśli muszę rzucić pierwszy argument do TextBox, wracam do wykonywania sprawdzania typu siebie i pokonuję cały cel tego ćwiczenia.

Czy staram się osiągnąć a) możliwe i b) dobry pomysł?

Odpowiedz

5

Znacznik "wysyłka" na to pytanie jest całkiem odpowiedni: to, co chcesz, nazywa się "wielokrotną wysyłką" .C# (jak większość popularnych języków) obsługuje tylko "pojedynczą wysyłkę", gdzie metoda do wykonania jest wybierana wyłącznie w typie (uruchomionym) obiektu, który wywołuje się, a nie w typie (uruchomionym) jego argumentów.

Wzór gościa może być często używany do obejścia tego problemu. Chodzi o to, że dajesz DocumentFieldBase metodę (którą przesłonisz w konkretnych podklasach), która wywołuje metodę na Control (również przesłoniętą w konkretnych podklasach), która wykonuje rzeczywistą pracę.

Niestety, kod źródłowy klasy Control prawdopodobnie nie jest pod Twoją kontrolą * ... więc będziesz musiał uciekać się do jeszcze bardziej hackowskiego rozwiązania. Przyjęta odpowiedź na this question zapewnia taką, która wykorzystuje odbicie.

* Metody rozszerzeń są po prostu syntaktycznym cukrem dla metod statycznych, a zatem są rozwiązywane w czasie kompilacji i bezużyteczne w tym scenariuszu.

+0

Odpowiedź przyznana, ponieważ nadałeś mojemu bólowi nazwę :) Często czytałem o podwójnej wysyłce i wzorze Odwiedzającego i miałem przeczucie, z którym miałem do czynienia (stąd wymieniany tag "wysyłkowy"). Udało Ci się również wyodrębnić istotne różnice między przesyłką pojedynczą a wysyłką wielokrotną, czego wiele artykułów na ten temat nie mogłem zrobić. –

6

Przed C# 4, wszystkie przeciążenia są wykonywane podczas kompilacji. Musisz użyć podwójnej wysyłki lub wzoru gościa, aby efektywnie przeciążać w czasie wykonywania, a to szybko staje się kłopotliwe.

W języku C# 4, można zadeklarować zmienną jako dynamiczny i niech to wszystko się załatwić w czasie wykonywania:

foreach (DocumentFieldBase field in fields){ 
    dynamic control = FindControl(field.Identifier); 
    Bind(control, field) 
} 

oczywiście, że nie jest zbyt pomocne w momencie, choć (chyba, że ​​używasz VS2010b1) .

Jedną z opcji jest użycie mapy od Type do Action<object>, ale wtedy pojawią się problemy z dziedziczeniem ... (potencjalnie trzeba będzie kontynuować pracę nad hierarchią typów od typu konkretnego do obiektu aż do znalezienia wpisu w Mapa). Nadal będziesz musiał rzucić na właściwy typ w akcji :(

0

Nie możesz mieć abstrakcyjnej, niestatycznej metody Bind() w DocumentFieldBase, a następnie wykonać downcasting w ramach każdej implementacji klasy konkretnej klasy? Każda klasa DocumentFieldBase wie, jakiego rodzaju jest to Control, prawda?

+0

Carl, Nie rozważałem tego podejścia. Nie jestem strasznie zainteresowany wprowadzeniem zależności od System.Web.UI w mojej warstwie domeny, ale mogę napisać szybki POC tak samo. Dzięki! –