2010-09-15 8 views
7

Rozważmy następujący problem:C# metoda abstrakcyjna z implementacją tylko klasy podstawowej?

Masz klasę "A", która służy jako klasa podstawowa dla wielu innych podobnych klas, np. klasa o nazwie B.

Klasa A jest użyteczny w niej (i już używana w każdym miejscu) i dlatego nie jest abstrakcyjna.

Sedno polega na tym, że teraz chcesz wymusić nowy protokół wymagający, aby wszystkie klasy dziedziczyły po A (łącznie z samym A), aby zaimplementować metodę "func()". Jeśli klasa podrzędna zapomni implementować func(), powinien wystąpić błąd kompilatora.

class A { 
    A func() { ret new A(...) } 
} 

class B : A { 
    A func() { ret new B(...) } 
} 

class C : A { 
    // Should give error : no func() 
} 

Czy problem jest jasny? Nie mogę tworzyć abstrakcji A :: func(), ponieważ chcę nadal używać A jako konkretnej klasy. Nie mogę uczynić go wirtualnym, ponieważ spowoduje to, że podklasy spowodują powrót do super klasy (i nie dadzą błędów kompilatora).

Jedyną rzeczą, która jest bliska znalezienia rozwiązania, jest utworzenie nowej abstrakcyjnej klasy A * i wszystkie typy niestandardowe dziedziczą z tego i zastępują wszystkie bieżące zastosowania A jako konkretną klasę z nową klasą "DefaultA", która dziedziczy po ZA*. To wydaje się nieporządne. Proszę mi powiedzieć, że jest jakiś inny sposób na zrobienie tego?

+0

Twoje "niechlujne" rozwiązanie wydaje mi się właściwe. –

Odpowiedz

1

Jeśli to jest to, co musisz zrobić, to tak.

Moim zdaniem jednak warto rozważyć ponowne rozważenie tego wymogu. Często zaimplementowałem podobne struktury, w których wymuszanie implementacji w pod-klasach było sprytną sztuczką. Odkryłem wtedy, że jako konsola , będę duplikować kod lub domyślną implementację (jak w pustej metodzie). Żadne z nich nie jest idealne, ponieważ zwiększają bałagan i hałas, a także zmniejszają łatwość konserwacji.

Dodatkowo, jeśli zastosowanie tego wzoru jest samo-fabryka (np. Twój przykład ma podklasy zwracające swoje wystąpienia), możesz chcieć wypróbować coś innego, na przykład właściwy wzór fabryczny. Er, i przez "spróbuj" mam na myśli wykorzystanie Inwersji Kontrolnego Kontenera, takiego jak Castle Windsor, Ninject, Unity.

+0

Dzięki za odpowiedzi! Chyba jestem po prostu paranoikiem :) Ponieważ jestem starym programistą C++, zaczynającym od C#, miałem nadzieję na coś podobnego do funkcji szablonu, która mogłaby się zepsuć, gdyby została utworzona z niewłaściwego typu .... ale ja zdaję sobie sprawę, że to nie jest bardzo C# -ish – 4ZM

+0

Wolałbym zobaczyć podklasę, która zastępuje abstrakcyjną funkcję ValidateStuff() z pustą implementacją, oraz komentarz kodu wyjaśniający, dlaczego nie wymaga żadnej weryfikacji. IMO sprawia, że ​​kod jest łatwiejszy w utrzymaniu i bardziej przydatny jako przykład dla innych programistów tworzących podobne podklasy. Porównaj to z podklasą, która nie zastępuje wirtualnej funkcji ValidateStuff(), i zastanawia mnie dlaczego. Czy nie było to konieczne, czy programista zapomniał go zaimplementować? Z tego powodu nie sądzę, że te puste funkcje zawsze są po prostu "bałaganem". – mikemanne

10

Po prostu nie ma sposobu, aby zachować konkretny typ A i wymusić błąd kompilatora, jeśli podrzędne typy nie zastępują określonej metody.

+1

+1 Prawda. Jeśli chcesz wymusić implementację metody w czasie kompilacji, musisz zastosować interfejs do WSZYSTKICH klas, które wymagają metody –

+0

Zawsze można refaktoryzować implementację, aby utworzyć "ABase" równoważne "A", ale ma 'func' jako chroniony abstrakcyjnie, a następnie wykonaj' zapieczętowany A: ABase' z domyślną implementacją. – LBushkin

0

To być może jeszcze bardziej nieprzyjemne, ale możesz wymusić B, C, D itd., Aby zaimplementować interfejs wymagający func(). Następnie upewnij się, że są one zawsze konstruowane przy użyciu metody fabrycznej. Więc coś niejasno lubię:

public class B: A, IFunc{ 
    private B(){} 
    public static B Create(){ return new B(); } 

} 
+0

Ale celem jest znalezienie każdej klasy, która nie została zmodyfikowana w celu implementacji func(). Ta metoda pominie każdą klasę, która nie została zmodyfikowana, wymaganą przez IFunc. To nie rozwiązuje problemu; to tylko porusza. –

1

Nie można utworzyć instancji klasy A, jeśli jest ona abstrakcyjna. Zatem

new A(); 

i

abstract A func(); // within the A class definition 

są sprzeczne.

Jeśli sterować od abstrakcyjny i iść zamiast wirtualnego, można uzyskać Runtime (nie w czasie kompilacji) sprawdzenie:

// In the A class definition. 
protected virtual A func() 
{ 
    if (GetType() != typeof(A)) 
     throw // ... 
} 
1

Zasadniczo prosicie nas do „Ustaw tę przerwę , ale nie rób tego."Być może pojawi się konflikt wrodzoną.

Jest to jeden z powodów, dla klasy bazowe powinny być używane tylko jako klasy bazowej, a nie używane jako siebie.

2

Cóż, jak mówisz, można utworzyć pośrednia klasa abstrakcyjna X, która dziedziczy z a i wymagają klasy pochodne dziedziczą X. X następnie deklaruje chronionej metody abstrakcyjne z podpisem dopasowanie func i zastąpić func od a do wywołanie tej metody Oto przykład:.

class A { 
    public virtual A func() { ... } 
} 

abstract class X : A { 
    protected abstract A funcImpl(); 

    public override A func() { return funcImpl(); } 
} 

class B : X { /* must now implement funcImpl() to avoid compiler error */ } 

class C : X { /* must now implement funcImpl() to avoid compiler error */ } 

Głównym problemem związanym z tym podejściem jest wymóg, aby wszystkie klasy pochodne dziedziczyły po X, a nie od A. Zmieniłeś więc problem z wymuszaniem z jednego rodzaju na inny.

Czystsze podejście, moim zdaniem, byłaby A do klasy bazowej ABase i mają func tam być abstrakcyjne. Następnie uszczelnić A i wymagają B i C dziedziczyć z ABase zamiast A:

class ABase { 
    public abstract ABase func(); } 

sealed class A : ABase { 
    public override ABase func() { ... } 
} 

class B : ABase { 
    // forced to override func() 
    public override ABase func() { ... } 
} 

Trzeba by wymienić wszystkie zastosowania A z ABase - ale istnieją pewne doskonałe narzędzia refactoring w Visual Studio (i narzędzi firm trzecich jak ReSharper), które sprawiają, że jest to znacznie mniej bolesne niż kiedyś.

Powiązane problemy