Singleton-Muster
Teil des Abschnitts Objektorientierte Programmierung der C-Journey von Coddy. Lektion 54 von 61.
Manchmal benötigst du in deinem gesamten Programm genau eine Instanz eines Objekts: einen Konfigurationsmanager, ein Protokollierungssystem oder eine Datenbankverbindung. Das Singleton-Muster stellt sicher, dass nur eine Instanz existiert, und bietet einen globalen Zugriffspunkt darauf.
In C implementieren wir dies mithilfe einer static-Variablen innerhalb einer Quelldatei. Denke daran, dass static auf Dateiebene bedeutet, dass die Variable für diese Datei privat ist und während der gesamten Lebensdauer des Programms bestehen bleibt. Zusammen mit einer Getter-Funktion entsteht dadurch unser Singleton:
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}Die Magie geschieht in get_config(). Der erste Aufruf erstellt und initialisiert die Instanz. Jeder nachfolgende Aufruf gibt denselben Zeiger zurück.
Das Schlüsselwort static stellt sicher, dass instance seinen Wert zwischen Funktionsaufrufen beibehält und vor anderen Dateien verborgen bleibt.
Aus der Header-Datei sehen Benutzer nur die getter-Funktion. Sie können nicht direkt auf die statische Variable zugreifen oder sie ändern. Unabhängig davon, wie oft oder von wo aus Sie get_config() aufrufen, erhalten Sie immer dasselbe Objekt.
Aufgabe
EinfachErstellen wir ein Settings-Modul, das die anwendungsweite Konfiguration mithilfe des Singleton-Musters verwaltet. Unabhängig davon, wie oft dein Code die Einstellungen anfordert, sollte er immer genau dieselbe Instanz erhalten.
Du organisierst deinen Code über drei Dateien:
settings.h: Deklariere einen opaken TypSettingsmithilfe einer Vorwärtsdeklaration (der Struct-Rumpf bleibt verborgen). Deklariere eineget_settings-Funktion, die einen Zeiger auf die Singleton-Instanz zurückgibt, sowie Getter- und Setter-Funktionen für zwei Konfigurationswerte:volume(eine Ganzzahl) undbrightness(eine Ganzzahl). Include-Guards sind hier unbedingt erforderlich.settings.c: Hier geschieht die Magie. Definiere die tatsächlicheSettings-Struktur mit den Feldernvolumeundbrightness. Erstelle einestaticZeigervariable, die die einzige Instanz speichert (initialisiert mitNULL). Implementiereget_settingsso, dass der erste Aufruf die Instanz allokiert und mit Standardwerten (volume: 50, brightness: 75) initialisiert, während nachfolgende Aufrufe einfach die vorhandene Instanz zurückgeben. Implementiere die Getter und Setter, um auf die verborgenen Felder zuzugreifen und sie zu ändern.main.c: Zeige, dass das Singleton korrekt funktioniert. Lies zwei Ganzzahlen ein, die neue volume- und brightness-Werte darstellen. Rufeget_settingszweimal auf und speichere die Ergebnisse in zwei verschiedenen Zeigervariablen. Verwende den ersten Zeiger, um volume und brightness mit den Eingabewerten zu aktualisieren. Verwende anschließend den zweiten Zeiger, um beide Werte abzurufen und auszugeben. Wenn das Singleton funktioniert, sind Änderungen, die über einen Zeiger vorgenommen wurden, über den anderen sichtbar.
Dein Programm erhält zwei Eingaben:
- Den neuen volume-Wert
- Den neuen brightness-Wert
Gib volume und brightness nach ihrer Änderung über einen Zeiger und dem Auslesen über einen anderen in separaten Zeilen aus.
Beispielausgabe bei den Eingaben 80 und 100:
80
100Beispielausgabe bei den Eingaben 25 und 60:
25
60Beispielausgabe bei den Eingaben 0 und 50:
0
50Die entscheidende Erkenntnis ist, dass beide Zeiger auf dasselbe Objekt im Speicher zeigen: Das ist das Wesentliche des Singleton-Musters. Die statische Variable innerhalb deiner Quelldatei stellt sicher, dass immer nur eine Instanz existiert, und sie bleibt während der gesamten Laufzeit des Programms erhalten.
Probier es selbst
#include <stdio.h>
#include "settings.h"
int main() {
int new_volume, new_brightness;
scanf("%d", &new_volume);
scanf("%d", &new_brightness);
// TODO: Rufe get_settings zweimal auf und speichere in zwei verschiedenen Zeigervariablen
// TODO: Verwende den ersten Zeiger, um volume und brightness mit den Eingabewerten zu aktualisieren
// TODO: Verwende den zweiten Zeiger, um volume und brightness abzurufen und auszugeben
// (Dies zeigt, dass beide Zeiger auf dieselbe Singleton-Instanz zeigen)
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der modularen Programmierung
Header-DateienInclude GuardsQuelldateienStatische FunktionenWiederholung: Modularer Taschenrechner4Kapselung
Konzept der Opaque PointersOpaque Structs definierenGetter und SetterValidierung in SetternRückblick: Die geheime Box2Objekte und Methoden
Structs als ObjekteDer 'Self'-PointerConst-CorrectnessPointer vs. WertHilfsmethodenZusammenfassung: Point Manager5Projekt: Einfaches Bankkonto
Projekt-SetupImplementierung des Kontos3Objekt-Lebenszyklus
Konstruktor-MusterDestruktor-MusterStack-InitialisierungTiefe KopieRückblick: String-Wrapper6Vererbung durch Komposition
Struct-EinbettungDie First-Member-RegelZugriff auf Parent-MemberUpcastingRückblick: Formenhierarchie9Projekt: Formen-Zeichner
ProjektübersichtKreis-ImplementierungRechteck-ImplementierungPolymorphe VerwendungShape-ContainerÜbe selbstständig: Online-C-Compiler