C# Zyklische Abhängigkeiten auflösen

Ghost_Rider_R

Lieutenant
Registriert
Nov. 2009
Beiträge
786
Hallo zusammen,

ich hätte da mal eine ganz allgemeine Frage. Wie löst man am besten zyklische Abhängigkeiten auf?
z.B.:

>>GUI macht was mit Controller und Controller macht was mit GUI<<<

Mal ein ganz simples Beispiel:

Ich habe eine GUI mit einem Button. Sobald dieser gedrückt wird ruft er in der Klasse Controller die Methode Berechnen(int wert1, wert2); auf. Wenn die Berechnung fertig ist möchte der Controller das Ergebnis auf ein Label der GUI schreiben. Bis dahin eigentlich kein Problem.

In meinem konkreten Beispiel dauert die Berechnung aber mehrere Sekunden und ich will die GUI nicht einfrieren lassen. Also starte ich die Methode in einem eigenen Thread und stelle den Button so lange auf Disabled, damit er nicht zweimal gedrückt werden kann. Dafür muss der Controller aber am Ende selbst die GUI kennen um dort rein zu schreiben und am Ende den Button wieder auf Enabled setzen.

Wie löst man denn solche Sachen am besten auf?

LG Ghost
 
Entweder du benachrichtigst die GUI über ein Event über das Ergebnis oder du nutzt async/await für den Aufruf Controller-Methoden um während der Berechnung den GUI-Thread nicht zu blockieren.
 
Das hat nichts mit zyklischen Abhängigkeiten zu tun. Das ist etwas anderes.

Les dir die entsprechenden UI Konzepte wie MVC, MVVM usw durch. Die behandeln das.
 
  • Gefällt mir
Reaktionen: rg88
Genau. Oder man macht die Berechnung in einem eigenen Thread um die Event-Loop nicht zu blockieren und disabled den Button halt direkt nach Klick und gibt ihn wieder frei wenn die Berechnung durch ist.

Ghost_Rider_R schrieb:
Dafür muss der Controller aber am Ende selbst die GUI kennen um dort rein zu schreiben und am Ende den Button wieder auf Enabled setzen.
Muss nicht. Es reicht ja im Prinzip ein Funktionszeiger der als Parameter dem Controller übergeben wird. Der Aufruf funktioniert dann völlig unabhängig von der Art des GUI-Controls.
 
Das sind glaube ich hier gleich zwei Sachen. Das eine ist Binding. Damit "bindest" du z.B. GUI-Elemente an Datenquellen und musst dich danach nicht mehr manuell um ihre Aktualisierung kümmern. Das andere ist das Blockieren/Einfrieren der GUI an sich. Da gibt's etwa Application.DoEvents, das dir vielleicht weiterhilft.
 
Ich habe eben noch ein wenig im Netz gestöbert, wäre es nicht eventuell richtig, wenn man hier ein Interface verwendet z.B. IController, welches der GUI dann injeziert wird? dann müsste die GUI den Controller nicht mehr kennen, kann aber über das übergebene Interface trotzdem Methoden des Controllers aufrufen und der Controller kennt einfach die GUI. Eine Abhängigkeit ist ja in Ordnung:

1645738501882.png

Quelle: http://prinzipien-der-softwaretechn...09/zyklische-abhangigkeiten-durchbrechen.html
 
Also nochmal zum Konzept von Events am Beispiel eines Buttons in der UI:
  • Der Button bietet an, beim Betätigen ein Event zu werfen (oder auch feuern genannt)
  • Dazu bietet der Button an, sogenannte Event-Handler oder auch Event-Listener bei ihm zu registrieren
  • Die Struktur (API) dieses Handlers gibt der Button vor, der Nutzer implementiert die API (wobei es natürlich durch Lambdas heute auch kürzer geht)
  • Der Button weiß nicht, welche Komponenten bei ihm Handler registrieren, es ist ihm mehr oder weniger egal, aber diejnigen, die auf das Drücken reagieren wollen, kennen den Button (oder zumindest derjenige, der den Handler registriert)

https://de.wikipedia.org/wiki/Inversion_of_Control
https://de.wikipedia.org/wiki/Beobachter_(Entwurfsmuster)
 
  • Gefällt mir
Reaktionen: Raijin
Ich möchte das ganze nicht zu sehr auf ein GUI Beispiel lenken sondern frage mich eher allgemeiner Natur, wie man sowas gut auflöst. Ich habe ein besseres Beispiel gefunden, wie wäre sowas?

1645739040471.png

Quelle: https://im-coder.com/wie-loesen-zirkelbezug.html

Beide Klassen bekommen via Dependency Injection ein Interface der Abhängigkeit injeziert, das müsste doch des Rätsels Lösung sein oder?
 
Es kokmmt immer auf das konkrete Problem an.
Dependency Injection ist eine Variante das zu lösen, aber die Lösung sollte halt zum Problem passen.
 
Wie kann man das mit Dependency Injection lösen, da ja beim erstellen die jeweils andere Klasse ja bereits erstellt sein müsste.
Ergänzung ()

Vielleicht kann das ja jemand lösen:

Public Class Henne
{
private ei;

public Henne(Ei ei)
{
this.ei = ei;
]
}

Public Class Ei
{
private henne;

public Ei(Henne henne)
{
this.henne = henne;
}
}
 
Zuletzt bearbeitet:
Für Dependency Injection wird üblicherweise von Frameworks übernommen, ich bin da mehr im Java-Umfeld unterwegs, dort z.B. via Spring... ich glaube ASP.NET Core macht das auch.

Üblicherweise übernimmt dann halt das Framework die instantiierung der Klassen und das Auflösen der gegenseitigen Abhängigkeiten. Du könntest so etwas aber auch manuell machen. Also dass du irgendwo deine verschiedenen Klasssen erzeugst und danach die erzeugten Objekte via Setter gegenseitig bekannt machst.

Über Constructor-Injection wie in deinem schlecht formatierten Code-Beispiel ist das aber kaum zu realisieren - von null-Referenzen mal abgesehen. Bitte das code-Tag verwenden.
 
Solche Abhängigkeiten kann man mit einem Interface lösen. Der Controller kennt nach wie vor keine konkrete Implementierung (GUI), sondern nur eine Schnittstelle, die er in Form einer Methode oder eines Events anspricht.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Hätte eventuell jemand Lust das Henne Ei Beispiel mit einem Interface darzustellen? Das würde mir sehr helfen.
 
Interface hilft bei Henne Ei kaum.

Sondern sowas:
Code:
public void CreateHenneEi() {
  this.henne = new Henne();
  this.ei = new Ei();
  this.henne.SetEi(this.ei);
  this.ei.SetHenne(this.henne);
}
Rein fiktiv. Wenn du es via Dependency Injection im Constructor machen willst, wird es etwas komplizierter...

Im Grunde macht ein Dependency-Injection-Framework auch "nur" das, wenn keine Constructor-Injection verwendet wird, gut, nur dynamisch und nicht von Hand beschrieben und mit viel mehr weiteren Abstraktionen zusätzlich, um noch mehr fancy Dinge zu realisieren.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
@tollertyp so habe ich es aktuell umgesetzt, nur bin ich mir nicht sicher, ob das eine ,,Schöne" Lösung ist.

Anfürsich arbeite ich gerne mit Dependeny Injection.
 
Und das ist an und für sich das Konzept hinter Dependency Injection. Häufig werden auch Interfaces statt konkreten Klassen als Abhängigkeiten verwendet, als IHenne und IEi als Beispiele, um Ei von der konkreten Implementierung von Henne unabhängig zu machen und umgekehrt.
Frameworks erlauben halt, dass man es nicht explizit als unschönen Code schreiben muss, sondern machen vieles automatisch.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Halten wir mal Fest, erste Möglichkeit von @tollertyp würde funktionieren, hat jemand Lust noch eine Konstruktor Injection davon aufzuzeigen?

Anbei der Code, damit Ihr nicht so viel Tippen müsst:

C#:
public interface IHenne
{
}

public class Henne : IHenne
{
    private Ei ei;

    public Henne(Ei ei)
    {
        this.ei = ei;
    }
}

public interface IEi
{
}

public class Ei : IEi
{
    private Henne henne;

    public Ei(Henne henne)
    {
        this.henne = henne;
    }
}

public class Container
{
    public static void Main()
    {
        //Und hier?...
        IEi ei = new Ei();
        IHenne henne = new Henne();
    }
}

Die Main-Methode hat zwei Fehler, da die Objekte vorher nicht bekannt sind.
 
Constructor-Injection würde ich vermuten würde fast nur mit Reflection funktionieren und/oder anderen Hacks. Ich bin grundsätzlich kein Fan von Constructor-Injection, auch wenn ich durchaus auch verstehen kann, warum man es gerne machen würde.

Anderersetis: Wenn ein Service irgendwann 10 Abhängigkeiten hat, dann hat man auch keine Lust auf die ganzen Parameter im Konstrutkor...

Wie man Constructor-Injection womöglich versuchen könnte zu faken hier? Ein Delegator erzeugen vom Typ IEi, das alles an eine später zugewiesene Ei-Instanz delegiert. Da die Interfaces keine Methoden haben etwas abstrakt.
Das Problem ist aber: Wenn Henne im Constructor etwas mit Ei machen möchte, und Ei etwas mit Henne, dann knallt's halt.

Deshalb haben auch alle von mir genutzten DI-Frameworks entsprechende Möglichkeiten auf Änderungen im Life-Cycle eines Objekts zu reagieren (also z.B. PostConstruct in Spring, was man als Konstrutkor-Ersatz dann nutzen kann).
 
Wenn sich zyklische Abhängigkeiten ergeben, macht man meistens was falsch beim Design.

Was die Henne und das Ei angeht: zweifach verkettete Liste, mit Forward ref auf Ei und Backward ref auf Henne.
Oder als Baumstruktur mit Henne als Parent und Ei als Child, was ja mehr oder weniger auch die Realität so widerspiegelt.

Die zweifach verkettete Liste hätte allerdings den Vorteil, daß man da den prev / next Knoten bei einem beliebigen Element in der Liste einfach auf null setzen kann - sonst würde man das ad absurdum führen und der Speicherplatz ist aber trotzdem irgendwie begrenzt.

Zum Thema UI <=> codebehind wurde ja schon alles gesagt.

TLDR - kreisfreie Graphen sind das Ziel, wie mehr oder weniger fast überall.
 
Ich glaube bei Henne und Ei ging es weniger darum die Natur widerzuspiegeln, es sollte als einfaches Beispiel dienen.
 
  • Gefällt mir
Reaktionen: Ghost_Rider_R
Zurück
Oben