Facade Pattern und ein Anwendungsbeispiel: SessionFacade
Vor einiger Zeit habe ich über das Strategy-Pattern und Singleton-Pattern geschrieben, dort sind auch Links zu der Definition und den Grundlagen von Designpatterns enthalten.
Die Definition des Facade Patterns (Oder Fassade Musters) liest sich wie folgt:
Das Facade-Muster bietet eine vereinheitlichte Schnittstelle für einen Satz von Schnittstellen eines Basis-
systems. Die Fassade definiert eine hochstufigere Schnittstelle, die die Verwendung des Basissystems vereinfacht.
Mit anderen Worten geht es darum den Zugriff auf ein System innerhalb einer Klasse zu kapseln und anhand einer neuen (vereinfachten / erweiterten) Schnittstelle zur Verfügung zu stellen.
Der SessionState wird im Arbeitsspeicher des Servers gehalten, darum lässt sich jeder - noch so komplexe - Typ darin speichern.
Über den Sinn und Unsinn der Verwendung des SessionStates, sowie dessen Funktionsweise habe ich schon einmal geschrieben.
Der SessionState ist nicht...
- ...typsicher -> Allle Objekte werden von einer Auflistung von Objekten gehalten und zurückgegeben (Typ: object)
- ...sicher gegen Tippfehler-> Beim Speichern kann der Name für eine Session bspw. "UserId" lauten und beim Aufrufen "UsrId".
- ...kontextsicher -> Es gibt keine Garantie das beim Hinzufügen eines Objektes der Name irgendwo im Code noch nie verwendet wurd
Ein erster Schritt zur Besserung wäre die Einführung von Konstanten, die beim Abrufen der Session-Variablen benutzt werden, bspw.:
private const string _user = "User";
... (Irgendwo weiter im Code)
User user = Session[_user];
Session[_user] = <Wert>;
Wenn diese Konstanten global gehalten werden, ist man schon mal das Tippfehler-Problem los.
Jedoch ist die Vorgehensweise immer noch nicht typsicher und bietet auch keine anderen Vorteile.
Keine Typsicherheit bedeutet unter anderem, dass beim setzen der Variable bspw. ein String übergeben wird und beim auslesen versucht wird, diesen in ein DateTime zu casten - was einen Laufzeitfehler zur Folge hätte.
Für diese Situation ist das Facade-Pattern bestens geeignet.
Das Basissystem und dessen Schnittstellen stellt in diesem Fall die HttpSessionState-Klasse von ASP.NET dar.
Die Facade implementiert alles um die oben genannten Nachteile des direkten Zugriffs über die HttpSessionState-Klasse nach aussen hin zu beseitigen.
Folgend eine Implementation des SessionFacade bei der eine Session-Variable typisiert zur Verfügung gestellt wird:
/// <summary>
/// Stellt eine Fassade für den typsicheren Zugriff auf den HttpSessionState dar.
/// </summary>
public static class SessionFacade
{
#region Private Constants
private const string _user = "User";
#endregion
#region Private Helpermethods
/// <summary>
/// Gibt einen typisierten Sessionwert zurück
/// </summary>
/// <typeparam name="T">Typ des Sessionwertes</typeparam>
/// <param name="name">Der Schlüsselname des Sessionwertes</param>
/// <returns></returns>
private static T GetSessionValue<T>(string name) {
object value = HttpContext.Current.Session[name];
if (value != null)
return (T)value;
return default(T);
}
/// <summary>
/// Setzt einen Sessionwert
/// </summary>
/// <param name="name">Der Schlüsselname des Sessionwertes</param>
/// <param name="value">Zu speicherndes Objekt</param>
private static void SetSessionValue(string name, object value) {
if (value == null)
HttpContext.Current.Session.Remove(name);
HttpContext.Current.Session[name] = value;
}
#endregion
#region Public Properties
/// <summary>
/// Setzt den aktuellen Benutzer oder gibt in zurück
/// </summary>
public static User User {
get {
return GetSessionValue<User>(_user);
}
set {
SetSessionValue(_user, value);
}
}
#endregion
}
Benutzt werden kann die Facaden-Klasse z.B. so:
// Abrufen
User currentUser = SessionFacade.User;
if(currentUser != null)
// Mach was mit dem User
// Setzen
SessionFacade.User = user;
Die Facade-Klasse und dessen Methoden sind statisch gehalten um einen einfachen Zugriff zu ermöglichen.
Durch die Eigenschaft "HttpContext.Current.Session" ist sichergestellt dass das SessionState-Objekt auch threadsicher ist.
Die Hilfsmethoden GetSessionValue und SetSessionValue kapseln den Zugriff auf den SessionState, geben bei Nichtvorhandensein eines Wertes jeweils Standardwerte zurück (DateTime.MinValue, Guid.Empty, 0, ...), ersparen Schreibarbeit und Casting in den Eigenschaften.
Die Eigenschaften können auch noch weitere Logik enthalten um bspw. auf leere oder ungültige Werte anders zu reagieren.
Im Normalfall gibt es einige Session-Variablen die nach aussen zur Verfügung gestellt werden, im Beispiel habe ich es bei einer belassen.
Das Hinzufügen von weiteren Zugriffsmöglichkeiten auf Session-Variablen erfolgt durch die Deklaration einer Konstante und
einer korrespondierenden Eigenschaft dazu.
Die Vorteile liegen klar auf der Hand:
- Starke Typisierung beim Ein- und Auslesen mithilfe der Facade
- Dadurch ist kein Casting mehr nötig
- Schreibfehler beim Namen für die Session-Variablen sind - bei sorgfältiger Erstellung der Facade - praktisch ausgeschlossen
- Der Zugriff erfolgt durch eine einzelne Klasse
- Die konkrete Implementation wird durch die Facade vor dem Cientcode (Bspw. Codebehind einer Seite) versteckt und...
- ...kann intern verändert und erweitert werden, ohne den Clientcode zwingend anpassen zu müssen
Viele von euch werden jetzt vielleicht denken:
- Wozu der ganze Aufwand?
- Für jede Session-Variable muss ich die SessionFacade-Klasse ändern?!
Nun, der Aufwand ist im Vergleich zum Nutzen und Komfort (Siehe Vorteile) extrem klein.
Ausserdem animiert eine solche Facade auch zur sinnvollen Nutzung des SessionStates, da immer zweimal überlegt wird, ob eine Session-Variable verwendet werden soll oder nicht.