2009-02-25 11 views
6

Chciałbym wymusić na podklasach implementację wzorca singleton.Wymuszenie pojedynczego wzorca w podklasach

Pierwotnie myślałem o posiadaniu abstrakcyjnej właściwości statycznej w klasie nadrzędnej, ale po bliższym choćby, to nie miało sensu (abstrakcja wymaga i przykład).

Następnie pomyślałem o interfejsie ze statyczną właściwością, ale to też nie ma sensu (interfejsy również wymagają instancji).

Czy jest to coś, co jest możliwe, czy powinienem zrezygnować z tego ciągu myśli i wdrożyć abstrakcyjną fabrykę?

+0

Przez pewien czas byłem na ogrodzeniu w odniesieniu do singletonów. Ale ten wątek ma kilka zalet, ponieważ nie używają go: http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons – JMD

+0

Istnieją setki wątków, postów na blogach i artykułów pełnych powodów używać singletonów. Niezwykle rzadko udoskonalają swój kod. Proszę to przemyśleć. Ty i każdy inny programista pracujący z tym kodem będzie żałował tynkowania singletonów wszędzie. – jalf

Odpowiedz

6

Proszę ponownie rozważyć. NIE chcesz używać tutaj singletonów. Udostępniasz niektóre funkcje użytkownikom, którzy pochodzą z Twojej klasy. W porządku. Ale ty także dyktujesz jeden konkretny sposób, w jaki musi być zawsze użyty i bez żadnego powodu. To nie jest dobre.

Może być sensowne tworzenie tylko jednego obiektu tej klasy przez większość czasu, ale w takim przypadku, wystarczy po prostu utworzyć obiekt raz. To nie jest tak, że możesz przypadkowo utworzyć tuzin obiektów bez zauważenia. Co więcej, jak można stwierdzić, że posiadanie dwóch instancji będzie przydatne? Mogę myśleć o kilku przypadkach nawet teraz.

Testowanie jednostki: Możesz chcieć każdy test, aby utworzyć instancję tego obiektu, i później go odłożyć. A ponieważ większość ludzi ma więcej niż jeden test jednostkowy, musisz go utworzyć więcej niż raz.

Lub możesz w pewnym momencie zdecydować, że będziesz mieć wiele identycznych/podobnych poziomów w grze, co oznacza tworzenie wielu instancji.

Singleton daje dwie rzeczy:

  • gwarancja, że ​​nie więcej niż jedna instancja obiektu będzie kiedykolwiek instancji, a
  • Globalny dostęp do tej instancji

jeśli nie potrzebuję obu tych rzeczy, są lepsze alternatywy. Z pewnością nie potrzebujesz globalnego dostępu. (globale są złe i zwykle są oznaką złego projektu, szczególnie w zmiennych danych, takich jak stan gry)

Ale nie potrzebujesz gwarancji, że nie uda się utworzyć więcej niż jednego wystąpienia. Czy to koniec świata, jeśli tworzę obiekt dwukrotnie? Czy aplikacja się zawiesi? Jeśli tak, potrzebujesz tej gwarancji. Ale w twoim przypadku nic złego się nie stanie. Osoba tworząca obiekt po prostu zużywa więcej pamięci niż to konieczne. Ale może mieć powód.

Po prostu dołącz do dokumentacji klasowej, że jest to bardzo duża i droga klasa, i nie powinieneś tworzyć jej częściej niż to konieczne. Problem rozwiązany. Nie usuniesz elastyczności, która może się później przydać, nie zapewniasz globalnego dostępu do danych bez powodu. Ponieważ można kontrolować, kto może zobaczyć obiekt, nie trzeba go utopić w blokadach, które staną się wąskim gardłem w aplikacjach wielowątkowych. Nie masz ukrytych zależności rozproszonych w kodzie, co utrudnia testowanie i trudniejsze do ponownego użycia.

4

Spróbuj użyć pojemnika z IOC. Większość dobrych kontenerów IOC umożliwia użycie wzorca singleton bez potrzeby implementowania go samodzielnie (tj. Framework wiosenny) - podoba mi się to lepiej niż wymuszenie statycznej metody GetInstance().

Poza tym nie jest to możliwe w java, ale działałoby w C++ z szablonami.

+0

To by nadawało się bardzo dobrze do ogólnego projektu. Ponadto nie jest to dla Java, ale dla C#. –

+0

Spring ma kontener .NET IOC. –

+0

Możesz także spróbować toczyć własny kontener IoC, jeśli nie potrzebujesz niczego skomplikowanego. Istnieje dobry podcast dnrTv, jak go zbudować tutaj: http://www.dnrtv.com/default.aspx?showNum=126 – Joseph

0

chciałbym zdefiniować szczelną klasę, która dostaje swoją funkcjonalność z delegatów przekazany do konstruktora, coś takiego:

public sealed class Shape { 
    private readonly Func<int> areaFunction; 

    public Shape(Func<int> areaFunction) { this.areaFunction = areaFunction; } 

    public int Area { get { return areaFunction(); } } 
} 

To przykład nie robi wiele sensu, to po prostu ilustruje wzór. Taki wzór nie może być używany wszędzie, ale czasami pomaga.

Dodatkowo, może być przedłużony, aby odsłonić skończoną liczbę pól statycznych:

public sealed class Shape { 
    private readonly Func<int> areaFunction; 

    private Shape(Func<int> areaFunction) { this.areaFunction = areaFunction; } 

    public int Area { get { return areaFunction(); } } 

    public static readonly Shape Rectangle = new Shape(() => 2 * 3); 
    public static readonly Shape Circle = new Shape(() => Math.Pi * 3 * 3); 
} 
+0

Cały punkt wywołania statycznego w celu zwrócenia instancji jest taki, że konstruktory są niedostępne i tworzą wystąpienia poza klasą są niemożliwe. Twoje rozwiązanie nie implementuje pojedynczego wzorca. –

+0

Klasa statyczna jest zawsze pojedynczą (istnieje kilka scenariuszy, w których klasa statyczna jako singleton nie może być używana). W tym rozwiązaniu istnieją statyczne pola/właściwości, które dają ten sam wynik. –

1

Dlaczego? Jeśli ktoś chce użyć wielu wystąpień podklasy twojej klasy, może mieć do tego uzasadniony powód.

Jeśli chcesz zrobić coś, co powinno być zrobione tylko raz dla każdej klasy, która podklasy klasy (dlaczego, nie mam pojęcia, ale możesz mieć powód), użyj słownika w klasie bazowej.

+0

Klasy są dość dużymi obiektami, które reprezentują scenę w grze 2D.Nie chcę ponownie tworzyć tych scen, jeśli zostały one wcześniej utworzone i chcę, aby informacje o stanie każdej sceny pozostały, gdy nie są używane. –

+0

Powinieneś edytować swoje pytanie i dodać je do tekstu. Myślę, że wpłynie to na odpowiedzi udzielane przez ludzi. –

+0

W takim przypadku chcesz, aby klasa była zarówno zapieczętowana, jak i pojedyncza. Można również rozważyć umieszczenie sceny w oddzielnej klasie, która jest pojedyncza i zapieczętowana, a następnie zrzucić wymaganie singletonu na oryginalnej klasie. – erikkallen

0

Myślę, że będzie ci lepiej, jeśli masz uczciwy wzór fabryczny. Lub użyj narzędzia IoC, jak zaleca Brian Dilley. W świecie C# jest mnóstwo, oto najbardziej popularne: Castle/windsor, StructureMap, Unity, Ninject.

Poza tym pomyślałem, że fajnie byłoby spróbować rozwiązać problem! Spójrz na to:

//abstract, no one can create me 
public abstract class Room 
{ 
    protected static List<Room> createdRooms = new List<Room>(); 
    private static List<Type> createdTypes = new List<Type>(); 

    //bass class ctor will throw an exception if the type is already created 
    protected Room(Type RoomCreated) 
    { 
     //confirm this type has not been created already 
     if (createdTypes.Exists(x => x == RoomCreated)) 
      throw new Exception("Can't create another type of " + RoomCreated.Name); 
     createdTypes.Add(RoomCreated); 
    } 

    //returns a room if a room of its type is already created 
    protected static T GetAlreadyCreatedRoom<T>() where T : Room 
    { 
     return createdRooms.Find(x => x.GetType() == typeof (T)) as T; 
    } 
} 

public class WickedRoom : Room 
{ 
    //private ctor, no-one can create me, but me! 
    private WickedRoom() 
     : base(typeof(WickedRoom)) //forced to call down to the abstract ctor 
    { 

    } 

    public static WickedRoom GetWickedRoom() 
    { 
     WickedRoom result = GetAlreadyCreatedRoom<WickedRoom>(); 

     if (result == null) 
     { 
      //create a room, and store 
      result = new WickedRoom(); 
      createdRooms.Add(result); 
     } 

     return result; 
    } 
} 

public class NaughtyRoom :Room 
{ 
    //allows direct creation but forced to call down anyway 
    public NaughtyRoom() : base(typeof(NaughtyRoom)) 
    { 

    } 
} 

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     //Can't do this as wont compile 
     //WickedRoom room = new WickedRoom(); 

     //have to use the factory method: 
     WickedRoom room1 = WickedRoom.GetWickedRoom(); 
     WickedRoom room2 = WickedRoom.GetWickedRoom(); 

     //actually the same room 
     Debug.Assert(room1 == room2); 

     NaughtyRoom room3 = new NaughtyRoom(); //Allowed, just this once! 
     NaughtyRoom room4 = new NaughtyRoom(); //exception, can't create another 
    } 
} 

WickedRoom to klasa, która właściwie implementuje system. Każdy kod klienta uzyska klasę singleton WickedRoom. NaughtyRoom nie implementuje poprawnie systemu, ale nawet tej klasy nie można utworzyć dwa razy. Druga instancja powoduje wyjątek.

+0

To bardzo podobne do mojego obecnego wdrożenia. Moim problemem jest to, że nic nie powstrzymuje NaughtyRoom lub WickedRoom przed wprowadzaniem metod Get. Chcę wymusić na podklasach implementację tych metod Get. –

0

Chociaż nie spowoduje to, że użytkownik będzie miał podklasę singleton, można wymusić na użytkowniku utworzenie tylko jednego wystąpienia klasy (lub jej podklas), jak poniżej. Spowoduje to zgłoszenie błędu w przypadku utworzenia drugiej instancji dowolnej podklasy.

public abstract class SuperClass { 
private static SuperClass superClassInst = null; 

public SuperClass() { 
    if(superClassInst == null) { 
     superClassInst = this; 
    } 
    else { 
     throw new Error("You can only create one instance of this SuperClass or its sub-classes"); 
    } 
} 

public final static SuperClass getInstance() { 
    return superClassInst; 
} 

public abstract int Method1(); 
public abstract void Method2(); 
}