2009-09-04 10 views
68

Rozumiem, że dziedziczenie metod statycznych nie jest obsługiwane w języku C#. Przeczytałem również wiele dyskusji (w tym tutaj), w których deweloperzy twierdzą, że istnieje taka potrzeba, na co typowa odpowiedź brzmi "jeśli potrzebujesz statycznego dziedziczenia członków, jest wada w twoim projekcie".Jaka jest poprawna alternatywa dla dziedziczenia metod statycznych?

OK, biorąc pod uwagę, że OOP nie chce, żebym nawet myślał o dziedziczeniu statycznym, muszę stwierdzić, że moja widoczna potrzeba wskazuje na błąd w moim projekcie. Ale utknąłem. Byłbym wdzięczny za pomoc w rozwiązaniu tego problemu. Oto wyzwanie ...

Chcę utworzyć abstrakcyjną klasę bazową (nazwijmy ją Owocem), która zawiera pewien złożony kod inicjalizacyjny. Tego kodu nie można umieścić w konstruktorze, ponieważ niektóre z nich będą opierać się na wywołaniach metod wirtualnych.

Owoce zostaną odziedziczone przez inne konkretne klasy (Apple, Orange), z których każda musi narazić standardową metodę fabryki CreateInstance(), aby utworzyć i zainicjować instancję.

Jeśli możliwe jest dziedziczenie statycznych elementów, umieściłbym metodę fabryczną w klasie bazowej i użyłbym wywołania metody wirtualnej do klasy pochodnej, aby uzyskać typ, z którego konkretna instancja musi zostać zainicjalizowana. Kod klienta mógłby łatwo wywołać funkcję Apple.CreateInstance() w celu uzyskania w pełni zainicjowanej instancji Apple.

Ale to nie jest możliwe, więc proszę, proszę, proszę wyjaśnić, w jaki sposób mój projekt musi się zmienić, aby dostosować tę samą funkcjonalność.

+1

Chciałbym zaznaczyć, że stwierdzenie "Ten kod nie może zostać umieszczony w konstruktorze, ponieważ część z nich będzie polegać na wirtualnych wywołaniach metod" jest niepoprawna. MOŻESZ wywoływać wirtualne metody wewnątrz konstruktora. (Będziesz musiał dokładnie zaprojektować swoje połączenia, ponieważ możesz mieć częściowo zainicjowaną klasę, gdy metody wirtualne są wywoływane, ale są obsługiwane.) Zobacz odpowiedź Ravadre, aby uzyskać więcej szczegółów. – Ruben

Odpowiedz

52

Jeden pomysł:

public abstract class Fruit<T> 
    where T : Fruit<T>, new() 
{ 
    public static T CreateInstance() 
    { 
     T newFruit = new T(); 
     newFruit.Initialize(); // Calls Apple.Initialize 
     return newFruit; 
    } 

    protected abstract void Initialize(); 
} 

public class Apple : Fruit<Apple> 
{ 
    protected override void Initialize() { ... } 
} 

I nazwać tak:

Apple myAppleVar = Fruit<Apple>.CreateInstance(); 

Bez dodatkowych klas fabrycznych potrzebne.

+8

IMHO, lepiej przenieść typ ogólny do metody CreateInstance zamiast umieszczać go na poziomie klasy. (Jak zrobiłem w mojej odpowiedzi). –

+1

Twoje preferencje są odnotowywane. Krótkie wyjaśnienie Twoich preferencji byłoby bardziej docenione. –

+6

Robiąc to, nie "zanieczyszczasz" reszty klasy; parametr type jest wymagany tylko w metodzie Create (przynajmniej w tym przykładzie). Obok tego, myślę, że: Fruit.Create (); jest bardziej czytelny niż: Owoc .Stwórz(); –

0

Powiedziałbym, że najlepszym rozwiązaniem jest stworzenie wirtualnego/abstrakcyjne metody inicjalizacji na klasy owoców, które muszą być o nazwie a następnie utworzyć klasę zewnętrznego „fabrycznego owoc”, aby utworzyć instancje:


public class Fruit 
{ 
    //other members... 
    public abstract void Initialise(); 
} 

public class FruitFactory() 
{ 
    public Fruit CreateInstance() 
    { 
     Fruit f = //decide which fruit to create 
     f.Initialise(); 

     return f; 
    } 
} 
4

Dlaczego nie utworzyć klasy fabrycznej (szablonowej) za pomocą metody create?

+0

Przejdź do niego. ;) –

15

Przenieś metodę fabryczną z typu i umieść ją w swojej własnej klasie Factory.

public abstract class Fruit 
{ 
    protected Fruit() {} 

    public abstract string Define(); 

} 

public class Apple : Fruit 
{ 
    public Apple() {} 

    public override string Define() 
    { 
     return "Apple"; 
    } 
} 

public class Orange : Fruit 
{ 
    public Orange() {} 

    public override string Define() 
    { 
     return "Orange"; 
    } 
} 

public static class FruitFactory<T> 
{ 
    public static T CreateFruit<T>() where T : Fruit, new() 
    { 
     return new T(); 
    } 
} 

Ale jak patrzę na to, nie ma potrzeby, aby przejść do metody Create własnej klasy Factory (chociaż myślę, że jest preferrable -separation z concerns-), można go umieścić w klasie Owoce:

public abstract class Fruit 
{ 

    public abstract string Define(); 

    public static T CreateFruit<T>() where T : Fruit, new() 
    { 
     return new T(); 
    } 

} 

A, aby sprawdzić, czy to działa:

class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine (Fruit.CreateFruit<Apple>().Define()); 
      Console.WriteLine (Fruit.CreateFruit<Orange>().Define()); 

      Console.ReadLine(); 
     }   
    } 
+1

Twój kod nie zostanie skompilowany. potrzebujesz miejsca gdzie: T: new(). Jest to jednak dokładny dupek. –

+1

Spróbuj, to się kompiluje. –

+1

Chociaż można stworzyć statyczną klasę FruitFactory, byłbym zwolennikiem interfejsu IFruitFactory, wraz z możliwą do natychmiastowego dodania klasy FruitFactory. Możesz skończyć z jedną klasą FruitFactory, której metoda CreateFruit nie odwołuje się do jej instancji obiektu i mogłaby być równie dobrze metodą statyczną, ale jeśli kiedykolwiek okaże się konieczne zaoferowanie wielu sposobów tworzenia owoców lub tworzenia owoców kiedykolwiek wymaga umiejętności zachowania stanu, użycie metod instancji może okazać się przydatne. – supercat

4

chciałbym zrobić coś takiego

public abstract class Fruit() { 
     public abstract void Initialize(); 
} 

public class Apple() : Fruit { 
    public override void Initialize() { 

    } 
} 

public class FruitFactory<T> where T : Fruit, new { 
     public static <T> CreateInstance<T>() { 
      T fruit = new T(); 
      fruit.Initialize(); 
      return fruit; 
     } 
} 


var fruit = FruitFactory<Apple>.CreateInstance() 
3

Klasa WebRequest i jej typy pochodne w .NET BCL stanowią dobry przykład tego, jak ten rodzaj projektu można wdrożyć stosunkowo dobrze.

Klasa WebRequest ma kilka podklas, w tym HttpWebRequest i FtpWebReuest. Teraz ta klasa bazowa WebRequest jest także typu fabrycznego i poddaje statycznej metodzie Create (konstruktory instancji są ukryte, zgodnie z wymaganiami fabrycznego wzorca).

public static WebRequest Create(string requestUriString) 
public static WebRequest Create(Uri requestUri) 

Ta metoda Create zwraca konkretną implementację klasy WebRequest i wykorzystuje URI (lub URI string), aby określić rodzaj obiektu, aby utworzyć i powrotu.

Ma to końcowy wynik poniższego wzoru Zastosowanie:

var httpRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com/"); 
// or equivalently 
var httpRequest = (HttpWebRequest)HttpWebWebRequest.Create("http://stackoverflow.com/"); 

var ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://stackoverflow.com/"); 
// or equivalently 
var ftpRequest = (FtpWebRequest)FtpWebWebRequest.Create("ftp://stackoverflow.com/"); 

Osobiście uważam, że jest to dobry sposób, aby zbliżyć się do problemu, i to rzeczywiście wydaje się być Preferowany sposób Framework twórcy.

3

Po pierwsze, brak inicjalizacji statycznych, które mogą być wirtualne, nie oznacza, że ​​nie można stosować "standardowych" metod, które mogą być przeciążone. Po drugie, możesz wywoływać swoje wirtualne metody od konstruktorów i będą działać zgodnie z oczekiwaniami, więc nie ma problemu. Po trzecie, możesz używać generycznych, by mieć fabrykę bezpieczną dla typu.
Oto kod, który używa metody Initialize() z fabryki, która jest wywoływana przez konstruktor (i jest chroniona, więc nie musisz się martwić, że ktoś wywoła ją ponownie po utworzeniu obiektu):


abstract class Fruit 
{ 
    public Fruit() 
    { 
     Initialize(); 
    } 

    protected virtual void Initialize() 
    { 
     Console.WriteLine("Fruit.Initialize"); 
    } 
} 

class Apple : Fruit 
{ 
    public Apple() 
     : base() 
    { } 

    protected override void Initialize() 
    { 
     base.Initialize(); 
     Console.WriteLine("Apple.Initialize"); 
    } 

    public override string ToString() 
    { 
     return "Apple"; 
    } 
} 

class Orange : Fruit 
{ 
    public Orange() 
     : base() 
    { } 

    protected override void Initialize() 
    { 
     base.Initialize(); 
     Console.WriteLine("Orange.Initialize"); 
    } 

    public override string ToString() 
    { 
     return "Orange"; 
    } 
} 

class FruitFactory 
{ 
    public static T CreateFruit<T>() where T : Fruit, new() 
    { 
     return new T(); 
    } 
} 

public class Program 
{ 

    static void Main() 
    { 
     Apple apple = FruitFactory.CreateFruit<Apple>(); 
     Console.WriteLine(apple.ToString()); 

     Orange orange = new Orange(); 
     Console.WriteLine(orange.ToString()); 

     Fruit appleFruit = FruitFactory.CreateFruit<Apple>(); 
     Console.WriteLine(appleFruit.ToString()); 
    } 
} 
+1

Generalnie najlepiej unikać wywoływania metod wirtualnych od konstruktorów. Zobacz: http://blogs.msdn.com/abhinaba/archive/2006/02/28/540357.aspx – TrueWill

+0

To prawda, może to być trudne, chociaż możliwe i zawsze będzie działać tak, jak powinno, problem polega na tym, zrozumieć, co znaczy "powinno". Ogólnie rzecz biorąc, staram się unikać wywoływania bardziej skomplikowanych metod (nawet nie-wirtualnych, tj. Ponieważ są one często konstruowane w sposób, który pozwala im rzucić wyjątek, a ja nie lubię, aby moi crybowie cokolwiek wyrzucali) , ale to decyzja dewelopera. –

Powiązane problemy