Architektur - Singleton-Klasse so in Ordnung?

  • C#
  • .NET (FX) 4.0

Es gibt 35 Antworten in diesem Thema. Der letzte Beitrag () ist von Trade.

    Architektur - Singleton-Klasse so in Ordnung?

    Hi,

    ich schreibe gerade an der nUpdate Administration und implementiere die soweit neu.
    Nun habe ich einige Klassen, sagen wir TransferManager, Logger, UpdateFactory, ..., welche dann die Datentransfers, Logs, Updatepakete etc. verwalten.

    Bisher hatte ich die Architektur so gehalten, dass beim Instanziieren eines neuen Dialogs in einem Dialog diese Daten einfach über den Konstruktor mitgegeben werden. Das hat mir dann aber nicht mehr gefallen, weil's imho irgendwo redundanter Code ist. Somit habe ich mir nun folgendes gebastelt:

    C#-Quellcode

    1. namespace nUpdate.Administration.Application
    2. {
    3. internal class Session : Singleton<Session>
    4. {
    5. /// <summary>
    6. /// Gets the active <see cref="UpdateProject"/> of the <see cref="Session"/>.
    7. /// </summary>
    8. internal UpdateProject ActiveProject { get; private set; }
    9. /// <summary>
    10. /// Gets the <see cref="Administration.UpdateFactory"/> of the <see cref="Session"/> for managing updates.
    11. /// </summary>
    12. internal UpdateFactory UpdateFactory { get; private set; }
    13. /// <summary>
    14. /// Gets the <see cref="PackageActionLogger"/> of the <see cref="Session"/> for the package history.
    15. /// </summary>
    16. internal PackageActionLogger Logger { get; private set; }
    17. /// <summary>
    18. /// Gets the <see cref="Administration.TransferManager"/> of the <see cref="Session"/> for data transfers.
    19. /// </summary>
    20. internal TransferManager TransferManager { get; private set; }
    21. /// <summary>
    22. /// Gets the <see cref="Sql.SQLManager"/> of the <see cref="Session"/> for managing statistics entries.
    23. /// </summary>
    24. internal SQLManager SQLManager { get; private set; }
    25. /// <summary>
    26. /// Gets the <see cref="Proxy.ProxyManager"/> of the <see cref="Session"/> for managing proxies.
    27. /// </summary>
    28. internal ProxyManager ProxyManager { get; set; }
    29. private Session()
    30. { }
    31. /// <summary>
    32. /// Initializes the <see cref="Session"/> with the specified <see cref="UpdateProject"/>.
    33. /// </summary>
    34. /// <param name="project">The <see cref="UpdateProject"/> of the <see cref="Session"/>.</param>
    35. internal void Initialize(UpdateProject project)
    36. {
    37. ActiveProject = project;
    38. UpdateFactory = new UpdateFactory(project);
    39. Logger = new PackageActionLogger(project);
    40. TransferManager = new TransferManager(project);
    41. SQLManager = new SQLManager(project);
    42. ProxyManager = new ProxyManager(project);
    43. }
    44. // Vielleicht dann später eher noch einen Flag, der jeweils beim Abrufen einer Property geprüft wird.
    45. internal void Terminate()
    46. {
    47. ActiveProject = default(UpdateProject);
    48. UpdateFactory = null;
    49. Logger = null;
    50. TransferManager.Dispose();
    51. TransferManager = null;
    52. }
    53. }
    54. }


    Das ist so der Grundriss. Ist also 'ne Singleton-Klasse, die die wichtigen Instanzen der Klassen enthält, die ich dann von überall aus z. B. mit ​Session.Instance.UpdateFactory.PushUpdate(...) aufrufen kann und die ist dann statisch im Projekt verfügbar, ohne, dass beim Öffnen des Dialogs zwangsläufig jedes Mal was weitergegeben werden muss.
    Nun weiß ich aber auch nicht, ob das so die saubere Art ist und ich das so lassen sollte.

    Ich habe hier noch eine Basisklasse für die Dialoge liegen (​BaseDialog), von der alle erben. Könnte man damit vllt. was basteln? Eine Art Zwischenschicht (Klasse), die die Dialoge jeweils verwaltet und dabei die Daten (also das Projekt) automatisiert übergibt, wurde mir auch schon vorgeschlagen, sodass ich damit meine Dialoge instanziieren könnte und schon alles hätte. Somit könnte ich auch alles durch die Oberklasse vorschreiben und jeweils einfach benutzen.
    Was meint Ihr?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    und schon alles hätte
    Sozusagen werden die Instanzen per Default gebracht und müssen nicht mehr geholt werden.
    Ich würde das ggf. nicht als Zwischenschicht, sondern als Interface programmien, dann ist das auch für andere als Dialog-Klassen verfügbar.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    Sozusagen werden die Instanzen per Default gebracht
    Hier kann ich Dir jetzt leider nicht ganz folgen.
    Wie würdest Du das mit dem Interface angehen? Damit kontrolliere ich ja nur, was die Dialoge implementieren sollen. Oder verstehe ich Dich falsch?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    implementieren sollen
    Jou.
    Wenn Du das einmal vorrätig hast, kannst Du ja das NotImplementedException durch einen Call zu diesem Code ersetzen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich weiß, wie das Implementieren einer Schnittstelle funktioniert. ;)
    Jedoch erschließt sich mir das nicht ganz. Wenn ich jetzt einem Dialog vorschreibe, was er haben muss (Logger, UpdateFactory usw.), dann kann ich das zwar in jedem ansprechen, aber initialisieren muss ich es ja trotzdem jedes Mal, wenn ich einen Dialog instanziiere.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    jedes Mal
    Das ist doch automatisierbar, halt nicht als Zwischenschicht, sondern als "Parallelschicht" (sozusagen vertikal statt horizontal).
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    OK, war mir nicht sicher, ob wir uns verstanden haben. ;)
    Gut, das könnte man machen. Um es mal noch anders in den Raum zu stellen: Was denkst Du persönlich über die Idee mit der Singleton-Architektur?

    Was ich vielleicht noch erwähnen sollte, ist, dass es vmtl. vorteilhafter ist, dass so statisch anzubieten, damit das nicht nur in Dialogen benutzt werden kann, sondern auch in anderen Klassen. Wird wahrscheinlich auch mal nötig sein, aber frag(t)e mich halt, ob man da bei den Dialogen speziell noch etwas zwischenschalten sollte. Mit Singleton wäre es vermutlich einheitlicher, aber die Frage ist halt, ob es auch noch andere Wege gibt, um das recht flexibel anzubieten.

    Mir persönlich gefällt das, wie es aktuell ist. (Würde natürlich noch richtig implementiert) - Bin mir halt nur nicht ganz sicher, ob's so gut ist.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Das sieht mir sehr gut aus. Ich werde mir die ganzen Entwurfsmuster ansehen und mal schauen, ob ich damit etwas basteln kann.
    Danke.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Singleton-Architektur
    würde ich gegen eine static class abwägen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich habe mir jetzt mal Autofac angeschaut. Ist 'ne nette Sache, allerdings ist halt fraglich, ob ich dadurch einen entsprechenden Mehrwert habe, wenn ich zuerst die Scopes definieren und dann einzeln die Komponenten abrufen muss.
    Eine einfache ​static class wäre natürlich auch noch eine Idee (+ weniger Code). Ich weiß jetzt nicht, wie ich fortfahren sollte.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    fortfahren
    Was willst Du denn erreichen? (Bitte eien Beschreibung ohne Bezug auf Code.)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich will, dass ich zu Beginn die Administration ganz normal starte. Dann soll man über den Hauptdialog ein Projekt öffnen können und somit sollen in diesem Moment alle Daten initialisiert und zur Verfügung gestellt werden. Heißt, ich lade das und in dem Moment soll mit den Daten des Projekts jeweils eine Instanz zum Verwalten gebaut werden (UpdateFactory, Logger, TransferManager etc.), sodass ich das bearbeiten kann. Weil diese verschiedenen Funktionen jetzt in verschieden Dialogen bzw. Klassen ablaufen und nicht alle auf einem Fleck, möchte ich diese Instanzen irgendwo statisch abrufen können, um Coderedundanz zu vermeiden.
    Bisher habe ich das immer von Dialog zu Dialog im Konstruktor weitergegeben.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Heißt, ich lade das und in dem Moment soll mit den Daten des Projekts jeweils eine Instanz zum Verwalten gebaut werden (UpdateFactory, Logger, TransferManager etc.), sodass ich das bearbeiten kann. Weil diese verschiedenen Funktionen jetzt in verschieden Dialogen bzw. Klassen ablaufen und nicht alle auf einem Fleck, möchte ich diese Instanzen irgendwo statisch abrufen können, um Coderedundanz zu vermeiden.
    Klingt mir bischen, als würde ein Datenmodell und Databinding dir guttun können.
    Dass "diese verschiedenen Funktionen jetzt in verschieden Dialogen bzw. Klassen ablaufen..." klingt jdfs. nicht ganz so dolle.
    Die Klassen eines Datenmodells hingegen können - je nachdem - durchaus auch anspruchsvolle Logik ausführen.

    Und ja, ich zumindest lege meine Datenmodelle meist Singletonmäßig an. Naja, nicht wirklich, aber ich sehe schon zu, dass in meiner Anwendung nur ein einziges Datenmodell vorkommt, und das oder dessen Teile werden befüllt oder auch geleert.

    ErfinderDesRades schrieb:

    Klingt mir bischen, als würde ein Datenmodell und Databinding dir guttun können.

    Klingt für mich so als solltest du mal über den Tellerrand von DataSet und Co. hinausschauen und dich mal mit Grundlagen von Softwarearchitektur und den grundlegenden Patterns befassen.
    Mit Datenmodell hat das sehr wenig zu tun. Es geht schlicht weg darum eine zentrale Stelle zu haben wo die Daten des Projektes zur Verfügung stehen und diese nicht immer an sämtliche Instanzen von Objekten über den ctor weitergegeben werden müssen.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    Also DataSet o. ä. ist da wohl jetzt für meine Fälle am wenigsten geeignet...
    Ich habe mir nun privat auch noch ein paar Meinungen eingeholt und das statische Bereitstellen scheint soweit eine gute und einfache Lösung zu sein. Daher werde ich das vmtl. auch so benutzen, da IoC über Autofac imho keinen wirklichen Mehrwert bietet. Dependency Injection nutze ich ja bereits, wenn ich das so übergebe. Ist also schon eine derartige Verwendung, aber nicht die optimale.

    Die Frage ist dann nur noch, ob Singleton oder statische Klasse. Was denkt Ihr da?

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ne, muss sie nicht. Also wäre eine statische Klasse dann wohl die Wahl.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    also ich lese, dass du Daten lädst, und die bearbeiten möchtest - eine "Instanz zum Verwalten" - was immer das sein mag.
    Und daraus schließt du, dass Dataset o.ä. für deine Fälle am wenigsten geeignet sei.
    (Hmm - wenn ich Daten laden und bearbeiten möchte, schließt das für mich ein Dataset eiglich nicht von vornherein aus, zumindest nicht in WinForms.
    Naja, wie wolle - vermutlich verfügst du über Informationen, die mir abgehen. )

    Zur Frage ob Singleton oder statische Klasse:
    Ist (mir zumindest) ziemlich egal, wenn beides ein gangbarer Weg erscheint, nimm halt eins von beiden. Falls es sich dann als ungeeignet erweist, ists nicht soo ein Problem, einen Singleton umzufrickeln auf eine static Class und vice versa.

    Gelegentlich habich aufgeschnappt, Singleton sei iwie schlecht angesehen, aber wurde immer nur erwähnt, nie begründet.
    Haste mal auf Wikipedia die Patterns angeguckt? Da gibts oft auch einen Abschnitt zu Vor- und Nachteilen. Aber auch das kritisch lesen: Dass es so einen Abschnitt gibt, heißt ja noch nicht, dass er auch gut ist, oder verständlich.

    Wobei man imo diese Pattern-Fachsimpelei auch übertreiben kann.
    Definier halt dein Problem (ungut über verschiedene Dialoge verteilte Funktionalität - habich das richtig mitgekriegt?), und bastel dir was, wasses besser löst.
    Ich empfehle, bei mehreren Möglichkeiten der einfachsten einen leichten Vorzug zu geben, also wenig Code ist ein gutes Zeichen, viele Casts sind ein schlechtes Zeichen, und wenn man auf Libraries verzichten kann, ist das auch nicht schlecht.