Jak zmienić kod, aby usunąć niepotrzebne singletony?

Byłem zdezorientowany, kiedy po raz pierwszy zacząłem widzieć anty-singleton komentarz. Użyłem wzoru Singletona w niektórych ostatnich projektach i działało pięknie. Tak bardzo, w rzeczywistości, że używałem go wiele, wiele razy.

Teraz, po napotkaniu pewnych problemów, przeczytaniu tego więc pytanie, a zwłaszcza tego {6]} postu na blogu, Rozumiem zło, które sprowadziłem na świat.

Więc: jak mam przejść usunięcie singletonów z istniejących kod?

Na przykład:
W programie do zarządzania sklepami używałem wzorca MVC. Moje Obiekty modelowe opisują sklep, interfejs użytkownika to Widok, a ja mam zestaw kontrolerów, które działają jak liason między nimi. Świetnie. Poza tym, że zrobiłem sklep w singleton (ponieważ aplikacja zawsze zarządza tylko jednym sklepem na raz), a także zrobiłem większość moich klas kontrolerów w singletons (jeden mainWindow, jeden pasek menu, jeden productEditor...). Większość mojego kontrolera klasy uzyskują dostęp do innych singletonów w ten sposób:

Store managedStore = Store::getInstance();
managedStore.doSomething();
managedStore.doSomethingElse();
//etc.

Powinienem zamiast tego:

  1. utworzyć jedną instancję każdego obiektu i przekazać odwołania do każdego obiektu, który potrzebuje dostępu do nich?
  2. używać globali?
  3. Coś jeszcze?
Globalni nadal byliby źli, ale przynajmniej nie udaliby .

Widzę # 1 szybko prowadzący do strasznie napompowanych wywołań konstruktora:

someVar = SomeControllerClass(managedStore, menuBar, editor, sasquatch, ...)
Czy ktoś jeszcze przez to przechodził? Co? czy sposób oo daje dostęp wielu poszczególnym klasom do wspólnej zmiennej bez jej globalnej czy singletonowej?
Author: Community, 2009-01-24

8 answers

Dependency Injection jest twoim przyjacielem.

Spójrz na te posty na blogu excellent Google Testing Blog :

Mam nadzieję, że ktoś zrobił framework/kontener DI dla świata C++? Wygląda na to, że Google ma wydał C++ Testing Framework i C++ Mocking Framework , które mogą Ci pomóc.

 19
Author: matt b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 21:30:38

To nie Singleton-ness jest problemem. Dobrze jest mieć obiekt, którego będzie tylko jedna instancja. Problemem jest globalny dostęp. Klasy, które używają Store, powinny otrzymać instancję Store w konstruktorze (lub mieć właściwość Store / element danych, który można ustawić) i wszystkie mogą otrzymać tę samą instancję. Store może nawet przechowywać w sobie logikę, aby zapewnić, że tylko jedna instancja została kiedykolwiek utworzona.

 3
Author: Eddie Deyo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 21:50:16

Mój sposób na uniknięcie singletonów wywodzi się z idei, że "application global" nie oznacza "vm global" (tj. static). Dlatego wprowadzam klasę ApplicationContext, która przechowuje dużo wcześniejszych informacji static singleton, które powinny być globalne dla aplikacji, jak np. sklep konfiguracyjny. Kontekst ten jest przekazywany do wszystkich struktur. Jeśli używasz dowolnego menedżera kontenerów lub usług IOC, możesz użyć tego, aby uzyskać dostęp do kontekstu.

 3
Author: David Schmitt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 10:34:52

Nie ma nic złego w używaniu globalnego lub Singletona w twoim programie. Nie pozwól, żeby ktoś cię dogmatyzował. Zasady i wzorce to miłe Zasady. Ale w końcu to twój projekt i powinieneś sam Oceniać, jak radzić sobie z sytuacjami związanymi z globalnymi danymi.

Nieograniczone korzystanie z globali to zła wiadomość. Ale tak długo, jak jesteś sumienny, nie zabiją twojego projektu. Niektóre obiekty w systemie zasługują na singleton. Na standardowe wejścia i wyjścia. Twój system logowania. W grze, grafiki, dźwięku i podsystemów wejściowych, a także bazy danych podmiotów gry. W GUI, okna i główne komponenty panelu. Twoje dane konfiguracyjne, menedżer wtyczek, dane serwera www. Wszystkie te rzeczy są mniej lub bardziej globalne dla Twojej aplikacji. Myślę, że twoja klasa również by się na to zdała.

Jest jasne, jaki jest koszt korzystania z globali. Każda część aplikacji może ją modyfikować. Namierzanie błędów jest trudne, gdy każda linia kodu jest podejrzana w śledztwie.

Ale co z kosztem nieużywania globali? Jak Wszystko inne w programowaniu, to kompromis. Jeśli unikniesz używania globali, będziesz musiał przekazać te stateful objects jako parametry funkcji. Alternatywnie, możesz przekazać je do konstruktora i zapisać jako zmienną członkowską. Gdy masz wiele takich obiektów, sytuacja się pogarsza. Jesteś teraz threading twój stan. W niektórych sprawy, to nie problem. Jeśli wiesz, że tylko dwie lub trzy funkcje muszą obsłużyć ten Stanowy obiekt Store, jest to lepsze rozwiązanie.

Ale w praktyce nie zawsze tak jest. Jeśli każda część aplikacji dotknie twojego sklepu, będziesz gwintować go do kilkunastu funkcji. Ponadto niektóre z tych funkcji mogą mieć skomplikowaną logikę biznesową. Kiedy przełamujesz tę logikę biznesową z funkcjami pomocniczymi, musisz -- wątek swój stan trochę więcej! Powiedzmy na przykład, że zdajesz sobie sprawę że głęboko zagnieżdżona funkcja potrzebuje pewnych danych konfiguracyjnych z obiektu Store. Nagle musisz edytować 3 lub 4 deklaracje funkcji, aby uwzględnić parametr store. Następnie musisz wrócić i dodać sklep jako rzeczywisty parametr wszędzie, gdzie zostanie wywołana jedna z tych funkcji. Może być tak, że jedynym zastosowaniem funkcji dla sklepu jest przekazanie jej do jakiejś podfunkcji, która jej potrzebuje.

Wzory to tylko Zasady. Czy Ty Zawsze używaj kierunkowskazów przed dokonaniem zmiana pasa w samochodzie? Jeśli jesteś przeciętną osobą, zazwyczaj przestrzegasz zasady, ale jeśli jedziesz o 4 nad ranem pustą drogą, kogo to obchodzi, prawda? Czasami ugryzie cię w tyłek, ale to zarządzane ryzyko.

 3
Author: Tac-Tics,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-05 16:51:38

Jeśli chodzi o Twój zawyżony problem wywołania konstruktora, możesz wprowadzić klasy parametrów lub metody fabryczne, aby wykorzystać ten problem.

A Klasa parametru przenosi część danych parametru do własnej klasy, np. tak:

var parameterClass1 = new MenuParameter(menuBar, editor);
var parameterClass2 = new StuffParameters(sasquatch, ...);

var ctrl = new MyControllerClass(managedStore, parameterClass1, parameterClass2);
To po prostu przesuwa problem gdzie indziej. Może zamiast tego będziesz chciał sprzątać swojego konstruktora. Zachowaj tylko parametry, które są ważne podczas konstruowania/inicjowania danej klasy, a resztę wykonaj z metody getter / setter (lub właściwości, jeśli robisz. NET).

Afactory method jest metodą, która tworzy wszystkie instancje klasy i ma tę zaletę, że hermetyzuje tworzenie wspomnianych obiektów. Są również dość łatwe do refaktorowania z Singletona, ponieważ są podobne do metod getInstance, które widzisz we wzorach Singletona. Załóżmy, że mamy następujący przykład prostego singletonu bez wątków:

// The Rather Unfortunate Singleton Class
public class SingletonStore {
    private static SingletonStore _singleton
        = new MyUnfortunateSingleton();

    private SingletonStore() {
        // Do some privatised constructing in here...
    }

    public static SingletonStore getInstance() {
        return _singleton;
    }  

    // Some methods and stuff to be down here
}

// Usage: 
// var singleInstanceOfStore = SingletonStore.getInstance();

Łatwo jest refakturować to w kierunku metoda fabryczna. Rozwiązaniem jest usunięcie odniesienia statycznego:

public class StoreWithFactory {

    public StoreWithFactory() {
        // If the constructor is private or public doesn't matter
        // unless you do TDD, in which you need to have a public 
        // constructor to create the object so you can test it.
    }

    // The method returning an instance of Singleton is now a
    // factory method. 
    public static StoreWithFactory getInstance() {
        return new StoreWithFactory(); 
    }
}

// Usage:
// var myStore = StoreWithFactory.getInstance();

Użycie jest nadal takie samo, ale nie jesteś ugrzęzły z posiadania jednej instancji. Oczywiście przeniesiesz tę metodę fabryczną do własnej klasy, ponieważ klasa Store nie powinna zajmować się tworzeniem samej siebie (i przypadkowo podąża za zasadą jednej odpowiedzialności jako efekt przeniesienia metody fabrycznej).

Stąd masz wiele możliwości, ale zostawię to jako ćwiczenie dla siebie. Tutaj łatwo jest przesadzić (lub przegrzać) wzory. Moją wskazówką jest stosowanie wzoru tylko wtedy, gdy istnieje potrzeba .
 2
Author: Spoike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:27

Dobra, po pierwsze, pojęcie "singletony są zawsze złe" jest złe. Używasz Singletona, gdy masz zasób, który nie będzie lub nie może być duplikowany. Nie ma sprawy.

To powiedziawszy, w twoim przykładzie, jest oczywisty stopień swobody w aplikacji: ktoś mógłby przyjść i powiedzieć " ale chcę {3]} dwa {4]} sklepy."

Istnieje kilka rozwiązań. Tym, który występuje przede wszystkim jest zbudowanie klasy fabrycznej; gdy poprosisz o sklep, daje Ci jeden o nazwie z jakąś uniwersalną nazwą (np. URI.) Wewnątrz tego sklepu, musisz mieć pewność, że wiele kopii nie nadepnie na siebie, poprzez krytyczne regiony lub jakąś metodę zapewnienia atomiczności transakcji.

 1
Author: Charlie Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 21:32:30

Miško Hevery ma ładną serię artykułów na temat testowalności, między innymi singleton , gdzie mówi nie tylko o problemach, ale także o tym, jak można je rozwiązać (patrz "naprawianie wady").

 1
Author: falstro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 21:33:51

Lubię zachęcać do używania singletonów tam, gdzie jest to konieczne, zniechęcając jednocześnie do używania wzoru Singletona. Zwróć uwagę na różnicę w przypadku tego słowa. Singleton (małe litery) jest używany wszędzie tam, gdzie potrzebujesz tylko jednej instancji czegoś. Jest on tworzony na początku programu i przekazywany konstruktorowi klas, które go potrzebują.

class Log
{
  void logmessage(...)
  { // do some stuff
  }
};

int main()
{
  Log log;

  // do some more stuff
}

class Database
{
  Log &_log;
  Database(Log &log) : _log(log) {}
  void Open(...)
  {
    _log.logmessage(whatever);
  }
};

Użycie Singletona daje wszystkie możliwości anty-pattern Singletona, ale sprawia, że kod jest łatwiejszy do rozszerzenia, i sprawia, że jest testowalny(w znaczeniu słowa zdefiniowanego na blogu Google testing). Na przykład, możemy zdecydować, że potrzebujemy zdolność do logowania się do usługi internetowej w niektórych momentach, jak również, używając singleton możemy łatwo to zrobić bez znaczących zmian w kodzie.

Dla porównania, wzór Singletona jest inną nazwą zmiennej globalnej. Nigdy nie jest używany w kodzie produkcyjnym.

 1
Author: 1800 INFORMATION,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-23 23:04:44