Laden...

Ableiten oder Komposition?

Erstellt von userid14268 vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.655 Views
U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren
Ableiten oder Komposition?

Hi,

Es geht darum.
Ich habe einige klassen wo vorher Services aufgelöst werden muss.
Damit ich kein Duplicate Code habe (DRY) habe ich diese Auflösungen in einer Separaten Klasse.

public class BlaBase
{
    public ApiCallerBase(Window owner)
    {
        WindowService = ServiceLocator.ResolveService<IWindowService>();
        DialogService = ServiceLocator.ResolveService<IDialogService>();
        ServerService = ServiceLocator.ResolveService<IServerService>();
        LogService = ServiceLocator.ResolveService<ILogService>();
        PleaseWaitService = ServiceLocator.ResolveService<IPleaseWaitService>();
        IThreadService threadService = ServiceLocator.ResolveService<IThreadService>();
        ThreadQueue = threadService.ResolveThreadQueue(owner);
    }

    protected IWindowService WindowService { get; set; }
    protected IDialogService DialogService { get; set; }
    protected IServerService ServerService { get; set; }
    protected ILogService LogService { get; set; }
    protected IPleaseWaitService PleaseWaitService { get; set; }
    protected ThreadQueue ThreadQueue { get; set; }
}

Die Benutzende Klasse erbt davon und kann dann die Services direkt verwenden:

public class Foo : BlaBase
{
    public void OnDo()
    {
        WindowService.ShowDialog(...);
    }
}

public class Bar : BlaBase
{
    public void OnDo()
    {
        WindowService.ShowDialog(...);
    }
}

Soweit so gut.
Nun frage ich mich aber wie es sich mit FCoI hier verhält.
Mein Gefühl sagt mir, da die Basisklasse keine weiteren Aktionen ausführt ist eine Ableitung legitim.

Ich könnte die Klasse aber nach "ServiceHelper" oder so umbenennen und in allen Klassen als Privates Feld halten und verwenden.

Was meint ihr? Trübt mich mein Gefühl?

LG
CSL

//Tags vergessen

4.207 Beiträge seit 2003
vor 13 Jahren

Hallo CSL,

der Lösung mit der Basisklasse würde ich den Vorzug geben, wobei die Basisklasse für meinen Geschmack noch abstract sein sollte.

Begründung Nr. 1: Die Basisklasse erfüllt keine "eigene" Funktionalität, es geht nur um das Auslagern von gemeinsamem Code. Insofern ist eine Basisklasse - zumal abstract - hierfür IMHO in diesem Fall die richtige Wahl.

Begründung Nr. 2: Helper-Klassen sind mir immer suspekt. Helper-Klassen haben oft so eine Aura der Art "Ich wusste nicht, wo ich das Zeug sonst hinpacken sollte".

Aber mal ganz abgesehen davon: Warum nicht per DI auflösen? Dann hättest Du das Problem gar nicht.

Viele Grüße,

Golo

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Bzgl DI habe ich bisher kein gutes Tutorial gefunden...

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo CSL,

ergänzend zu Golos aussage: Man kann auch einen Container als Kontruktor übergeben. Somit hat man nicht so viele Parameter im Konstruktor.

Dependency Injection/Inversion of Control ist im Grunde sehr simple. Empfehlen kann ich dir LightCore.

Zu deinem eigenlichen Problem: Ich würde weder eine Basisklasse noch die Komposition verwenden. Möchten bestimmte Klassen Abhängigkeiten auflösen, sollen die das selber machen. Die Basisklasse kann höhstenfalls eine Methode z.B. GetService<T> bereitstellen. DI macht die Sache um einiges einfacher.

Was hat es eigentlich mit _IThreadService _aufsich? Ist mit Thread der Thread aus .NET, also die Klasse, gemeint? Falls ja, ganz schlecht!

zero_x

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Nur das auflösen erleichtern ist nicht sinnvoll an der stelle, alle ableitenden Klassen benötigen die selben Services, das wäre dann Duplicate Code.

Der IThreadService hat mit dem Threads aus .Net nichts gemeinsam, es ist eine eigene ThreadQueue die Aktionen mit COM entgegen nimmt und brav der reihe nach ab arbeitet und die Ergebnisse jeweils ans nächste Item weiter reicht.

In der Applikation gibt es drei Thread Queues die Pro Fenster parallel Laufen können, und dessen Progress entsprechend in einem PleaseWait anzeigt, deswegen muss das aufgelöst werden.

Diese Queues Supporten auch Progresses, Cancel, Abort, ThreadsStart, ThreadsEnd events etc.

Meinst du mit einem Containter eine einfache klasse die GetService bereit stellt? Gerade in den genannten klasse hätte ich dann ziemlich viel doppelt Code da alle diese 6 Services benötigen.

Bzgl DI habe ich bisher nichts gutes gefunden wo es anhand Sourcecode gut erklärt wird wie man DI (ohne 3rdPary) implementiert.

Was ich mir gerade vorstellen könnte wäre eine Klasse die alle verfügbaren Services anbietet, diese werden aber erst bei der ersten Benutzung aufgelöst.
Das könnte dann bei den Klassen durch gereicht werden (über den ctor).

N
46 Beiträge seit 2007
vor 13 Jahren

Begründung Nr. 1: Die Basisklasse erfüllt keine "eigene" Funktionalität, es geht nur um das Auslagern von gemeinsamem Code. Insofern ist eine Basisklasse - zumal abstract - hierfür IMHO in diesem Fall die richtige Wahl.

Begründung Nr. 2: Helper-Klassen sind mir immer suspekt. Helper-Klassen haben oft so eine Aura der Art "Ich wusste nicht, wo ich das Zeug sonst hinpacken sollte".

Hallo Golo,

das sind genau die Punkte, die aus meiner Sicht gegen eine Vererbung sprechen.

  1. Code auslagern und später wieder dranflanschen ist nicht Aufgabe der Vererbung. FCoI - Favour Composition over Inheritance. D.h. es reicht nicht aus, dass kein Grund gegen Vererbung spricht, es muss ein guter Grund dafür sprechen.
    Gerade, wenn es keine Mehrfachvererbung gibt verbaue ich mir durch soetwas diese Möglichkeit an den wirklich sinnvollen Stellen.

  2. Ja, Helper hört sich immer sehr unelegant an. Nennen wir das Kind einfach "Context" und es trifft genau das, was es ist. "In Deinem Context, benutze bitte diesen Logger"... Und das will ich gar nicht vererben. Das gebe ich per DI rein. Von daher Dein Hinweis schon ganz richtig 😉

Grüsse,

N1ls

T
381 Beiträge seit 2009
vor 13 Jahren

Und wenn nicht DI per Externem code, mach DI per Constuctor parameter. Klappt auch.

Das ganze wird per Reflection automatisiert. Ich denke nicht das es dafür nen großes Tutorial braucht. Bau dir sonst ne LightCore demo app zum testen. Die LightCore Klassen sind so simpel, dass ich es auch ganz ohne externe Hilfe gerafft hab 😉 Und Open Source, kannst du also auch direkt mit in dein Projekt kompilieren wenn du willst. Oder halt einfach mal in den Soruce gucken, dafür würde ichs aber vorher mal ausprobieren.

Den Ansatz mit dem Helper namens Context würde ich auch bevorzugen.

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Ich hab grad bei LightCore rein geschaut, und auch ne Demo gefunden. Sieht wirklich simpel aus 😃

Ich werde die app um die es hier geht erst einmal mit dem einfachen Service Provider machen und sobald ich zeit und lust habe damit mal rum zu spielen kann ich es ja einfach Refactoren.

Vielen Dank für eure Meinungen.

5.941 Beiträge seit 2005
vor 13 Jahren

Hoi CSL

Nur das auflösen erleichtern ist nicht sinnvoll an der stelle, alle ableitenden Klassen benötigen die selben Services, das wäre dann Duplicate Code.

Das ist ein springender Punkt, ob du alle Services an jeder stelle wirklich benutzt, ansonsten ...

Was ich mir gerade vorstellen könnte wäre eine Klasse die alle verfügbaren Services anbietet, diese werden aber erst bei der ersten Benutzung aufgelöst.
Das könnte dann bei den Klassen durch gereicht werden (über den ctor).

... kannst du auch die Features von LightCore oder anderen DI-Containern nutzen, die dir eine Func<T>, oder Lazy<T> (Ab .NET 4.0) in die Hand geben können.

Ersteres kannst du als ServiceLocator ansehen - eine Factory - allerdings nur für den angegebenen Typ, jeder Aufruf gibt eine neue Instanz.
Bei Lazy<T> verhält es sich ähnlich, ausser das du immer die gleiche Instanz zurück bekommst.

In beiden Fällen ist es so, das die Instanz erst aufgelöst wird, wenn du die Func<T> oder Lazy<T> Instanz aufrufst, bwz. bei Lazy auf die .Value Eigenschaft zugreifst.

Die Idee mit der "Dependency / Context" Klasse, die anstelle eines Helpers genutzt wird, ist eine gute Alternative.
Dazu reicht eine konkrete Klasse, die im Grund ähnlich aussieht wie deine "BlaBase", allerdings über den Konstruktor den Container (Zum auflösen), oder direkt alle Abhängikeiten reinbekommt.

Die benutzende Klasse, fordert dann dieser Context an und kann mit diesem arbeiten.
Auch das geht per Lazy<T>, etc.

Du musst nur beachten, wie lange - und in welchem Context - die Context-Instanz dann gültig sein soll.

  1. ContainerBuilder erstellen.
  2. Alle Abhängigkeiten registrieren.
  3. die Context-Klasse registrieren.
  4. Container anfordern und ab geht die Post.

Bzgl DI habe ich bisher nichts gutes gefunden wo es anhand Sourcecode gut erklärt wird wie man DI (ohne 3rdPary) implementiert.

Es ist wohl nicht genau das was du suchst, sollte aber trotzdem helfen:

BTW: Doku gibts seit dem 1.4 Release auf Deutsch und English, Grundlagen sollten aber schon vorhanden sein.

Danke an alle, die mir so indirektes Feedback zu LightCore gegeben haben, das bestätigt mich in meiner Arbeit.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Ich habe es jetzt so das die einzelnen klasse solch einen "DataContext" Container bekommen.
Dieser löst die Referenzen nun Lazy auf.
public class ServicesDataContext

{
	public ServicesDataContext()
	{
		_dialogService = new Lazy<IDialogService>(() => { return ServiceLocator.ResolveService<IDialogService>(); });
		_logService = new Lazy<ILogService>(() => { return ServiceLocator.ResolveService<ILogService>(); });
		_messageBoxService = new Lazy<IMessageBoxService>(() => { return ServiceLocator.ResolveService<IMessageBoxService>(); });
		_pleaseWaitService = new Lazy<IPleaseWaitService>(() => { return ServiceLocator.ResolveService<IPleaseWaitService>(); });
		_serverService = new Lazy<IServerService>(() => { return ServiceLocator.ResolveService<IServerService>(); });
		_threadService = new Lazy<IThreadService>(() => { return ServiceLocator.ResolveService<IThreadService>(); });
		_windowService = new Lazy<IWindowService>(() => { return ServiceLocator.ResolveService<IWindowService>(); });
	}

	public IDialogService DialogService { get { return _dialogService.Value; } }
	private Lazy<IDialogService> _dialogService;

	public ILogService LogService { get { return _logService.Value; } }
	private Lazy<ILogService> _logService;

	public IMessageBoxService MessageBoxService { get { return _messageBoxService.Value; } }
	private Lazy<IMessageBoxService> _messageBoxService;

	public IPleaseWaitService PleaseWaitService { get { return _pleaseWaitService.Value; } }
	private Lazy<IPleaseWaitService> _pleaseWaitService;

	public IServerService ServerService { get { return _serverService.Value; } }
	private Lazy<IServerService> _serverService;

	public IThreadService ThreadService { get { return _threadService.Value; } }
	private Lazy<IThreadService> _threadService;

	public IWindowService WindowService { get { return _windowService.Value; } }
	private Lazy<IWindowService> _windowService;
}

Die benutzenden klasse können dann einfach

_services.ThreadService.Enqueue(...) aufrufen etc.

Finde die Lösung recht angenehm.

Es ist im Prinzip so,
Ich habe verschiedene Aktionen, diese können von 3 verschiedenen Orten aufgerufen werden (3 Fenster).
Die Fenster können parallel laufen, und jedes hat seinen eigenen ThreadQueue, seine eigene Log und auch seine eigene PleaseWait.

Nun war es so das den Aktionen immer direkt das Fenster mitgegeben wurde, und es wurde dann direkt aufgerufen

Main.ThreadQueue.Enqueue(...)

Und das fand ich total mies.
Ich wollte es erreichend das die einzelnen klassen unabhängig von dem aufrufenden Fenster sind, aber dessen Queue, Log etc verwenden.
Daher hab ich es so gelöst das die Aufrufer die eigenen Objekte in den Service registrieren, so ist es den Klassen egal wer der Aufrufer ist.
(Nun könnte ich auch in den Units eigene Services registrieren.)