Entwurfsmuster in C#: Vollständiger Leitfaden mit Beispielen
Entwurfsmuster (Design Patterns) sind bewährte Lösungen für typische Probleme bei der Softwareentwicklung. Im C#- und .NET-Ökosystem helfen sie, eine flexible, skalierbare und wartbare Architektur zu erstellen. In diesem Artikel analysieren wir vier Schlüsselmuster: Singleton, Factory Method, Observer und Strategy, mit realen Beispielen in C#.
Warum braucht man Entwurfsmuster in C#?
Muster sind keine fertigen Bibliotheken oder Frameworks. Es sind konzeptionelle Vorlagen, die:
- Kommunikation vereinfachen — Entwickler verwenden eine einheitliche Terminologie
- Codequalität verbessern — Kopplung verringern und Flexibilität erhöhen
- Zeit sparen — das Rad muss nicht neu erfunden werden
- Den SOLID-Prinzipien entsprechen — die meisten Muster setzen diese Prinzipien um
1. Singleton (Einzelstück)
Zweck: Stellt sicher, dass eine Klasse nur eine Instanz hat, und bietet einen globalen Zugriffspunkt darauf.
Wann verwenden:
- Logger
- Konfigurationsmanager
- Datenbankverbindungspools
- Caches
Beispiel in C# (threadsichere Implementierung):
public sealed class ConfigurationManager{ private static readonly Lazy<ConfigurationManager> _instance = new Lazy<ConfigurationManager>(() => new ConfigurationManager());
private readonly Dictionary<string, string> _settings;
private ConfigurationManager() { _settings = new Dictionary<string, string> { { "ConnectionString", "Server=localhost;Database=myDb" }, { "ApiKey", "12345-ABCDE" } }; }
public static ConfigurationManager Instance => _instance.Value;
public string GetSetting(string key) { return _settings.TryGetValue(key, out var value) ? value : null; }}
// Verwendungvar config = ConfigurationManager.Instance;Console.WriteLine(config.GetSetting("ConnectionString"));Vorteile:
- Kontrollierter Zugriff auf die einzige Instanz
- Lazy Initialisierung (Lazy<T>)
- Threadsicherheit ohne Sperren
2. Factory Method (Fabrikmethode)
Zweck: Definiert eine Schnittstelle zum Erstellen eines Objekts, ermöglicht es Unterklassen jedoch, den Typ des erstellten Objekts zu ändern.
Wann verwenden:
- Wenn der konkrete Objekttyp im Voraus nicht bekannt ist
- Wenn die Objekterstellung komplexe Logik erfordert
- Zur Trennung von Erstellungs- und Verwendungscode
Beispiel in C#:
// Abstraktes Produktpublic interface ILogger{ void Log(string message);}
// Konkrete Produktepublic class FileLogger : ILogger{ public void Log(string message) => File.AppendAllText("log.txt", $"{DateTime.Now}: {message}");}
public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine($"{DateTime.Now}: {message}");}
// Ersteller mit Fabrikmethodepublic abstract class LoggerFactory{ public abstract ILogger CreateLogger(); public void LogMessage(string message) { var logger = CreateLogger(); logger.Log(message); }}
public class FileLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new FileLogger();}
public class ConsoleLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new ConsoleLogger();}
// VerwendungLoggerFactory factory = new ConsoleLoggerFactory();factory.LogMessage("Hallo, Welt!");