> Oder gibt es das schon?
jeder vernünftige DI Container muss einen Graph über die Abhängigkeiten
aufbauen da mit Hilfe des Graphen durch pre-order Traversieren alle
erforderlichen Objekte instanziiert werden können. Castle Windsor legt
diesen Graph offen und du kannst ihn traversieren.
Herzliche Grüße,
Stefan Lieser
P.S. Erstellen des Bitmaps musst du noch ergänzen ;-)
using System;
using System.Text;
using Castle.Core;
using Castle.Windsor;
namespace TryIt
{
public class Dependencies
{
public interface IServiceA { }
public interface IServiceB { }
public interface IServiceC { }
public interface IServiceD { }
public class ServiceA : IServiceA
{
private IServiceB _serviceB;
public ServiceA(IServiceB serviceB) {
_serviceB = serviceB;
}
}
public class ServiceB : IServiceB
{
private IServiceC _serviceC;
private IServiceD _serviceD;
public ServiceB(IServiceC serviceC, IServiceD serviceD) {
_serviceC = serviceC;
_serviceD = serviceD;
}
}
public class ServiceC : IServiceC { }
public class ServiceD : IServiceD { }
public static void Example()
{
WindsorContainer container = new WindsorContainer();
container.AddComponent("A", typeof(IServiceA),
typeof(ServiceA));
container.AddComponent("B", typeof(IServiceB),
typeof(ServiceB));
container.AddComponent("C", typeof(IServiceC),
typeof(ServiceC));
container.AddComponent("D", typeof(IServiceD),
typeof(ServiceD));
var serviceA = container.Resolve<IServiceA>();
var nodes = container.Kernel.GraphNodes;
TraversTheGraph(nodes, 0);
}
public static void TraversTheGraph(GraphNode[] nodes, int level) {
foreach (var node in nodes) {
Console.WriteLine("{0}Node: '{1}':", Indent(level),
((ComponentModel)node).Name);
TraversTheGraph(node.Dependents, level + 1);
}
}
private static string Indent(int level) {
StringBuilder indent = new StringBuilder(level * 2);
for(int i = 0; i < level; i++) {
indent.Append(" ");
}
return indent.ToString();
}
}
}
Mir ist nicht klar warum die Abhängigkeiten in ihrer Baumstruktur
beschrieben werden sollen. Es genügt doch die verwendeten Interfaces
nebst ihrer konkreten Implementierung im Container zu definieren. Der
Graph kann daraus generiert werden. Oder habe ich da was falsch verstanden?
Herzliche Grüße,
Stefan Lieser
> Windsor baut nämlich den Graphen nur aufgrund der über ctor-Injection
> wahrgenommenen Abhängigkeiten auf.
> Wenn ich also von DI umschalte auf Microkernel, d.h. statt
[...]
> class ServiceA : IServiceA
> {
> private IServiceB _sb;
> public ServiceA()
> {
> _sb = DIContainer.Instance.Get<IServiceB>();
> ...
>
> dann geht es nicht mehr. Windows traversiert also nicht Assembly-
> Referenzen, sondern "nur" Injection-Referenzen.
ah jetzt verstehe ich worauf du hinaus willst.
Ich sehe bei dem Ansatz folgende Probleme:
1) Mit "_sb = DIContainer.Instance.Get<IServiceB>();" handelst du dir
Probleme bei Unit Tests ein da statische Methoden nicht so einfach zu
mocken sind (meines Wissens nach geht das z.Zt. nur mit TypeMock).
2) Unabhängig von der Idee den Dependency Graph visualisieren zu wollen
wird die Abhängigkeit so auch beim Lesen des Codes nicht sofort
deutlich. Der Constructor spricht sozusagen nicht die ganze Wahrheit.
3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
und aktuell halten. DRY - Don't Repeat Yourself.
4) Ich sehe das Problem dass dies in einem Projekt an dem mehrere
Entwickler arbeiten nicht durchzuhalten ist. Und sobald ein einziger
Fehler im Graph ist wird das Vertrauen in ihn rapide sinken. Zu lösen
ist das Problem meiner Ansicht nach nur durch automatisiertes Erstellen
des Graphen (z.B. durch Constructor Injection).
Natürlich bist du damit an ein konkretes DI Framework gebunden. Aber zum
einen wird man dies in einem Projekt in aller Regel nicht so oft (wenn
überhaupt!) austauschen. Zum anderen dürften wie schon geschrieben alle
Container den Graphen intern aufbauen ao dass der Ansatz prinzipiell mit
allen Container funktionieren sollte.
Herzliche Grüße,
Stefan
Ralf Westphal schrieb:
> Probleme bei Unit Tests gibt es nicht. Ich mache das ständig so.
> Außerdem kann ich immer noch beides machen:
[...]
> Ich sehe das ganz entspannt - wie auch Martin Fowler (http://
> martinfowler.com/articles/injection.html#UsingAServiceLocator).
Einverstanden. Ist halt Geschmackssache.
>> 2) Unabhängig von der Idee den Dependency Graph visualisieren zu wollen
>> wird die Abhängigkeit so auch beim Lesen des Codes nicht sofort
>> deutlich. Der Constructor spricht sozusagen nicht die ganze Wahrheit.
>
> Sollte er?
Ich meine ja.
> Ich sage mal provokant: Dass eine Klasse c von einem Service s mit dem
> Interface i abhängt, ist ein Implementationsdetail.
Der Konsument von c sollte meiner Ansicht nach die Abhängigkeit kennen
denn er muss sie ja auflösen (sei es direkt, oder indirekt über ein DI
Framework). Wohlgemerkt der Konsument von c, nicht von ck (Kontrakt für
c) sollte es wissen!
> Warum sollte das auch nur irgendwie im expliziten Interface i oder im
> impliziten Interface (zu dem der ctor gehört) zu sehen sein.
> Das verletzt das Prinzip der Kapselung.
Völlig richtig! Ich sprach von einem Constructor und der taucht im
Interface nicht auf.
> Wogegen ich mich wehre: Die sklavische Notwendigkeit, DI zu verwenden.
> Und an "die Wahrheit des ctor" glaube ich nicht.
Es ist Geschmackssache, wie ich oben schon schrieb. Ich persönlich mag
die direkte Abhängigkeit im ctor eben viel lieber als die indirekte.
>> 3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
>> und aktuell halten. DRY - Don't Repeat Yourself.
>
> Achja... DRY... das ist schön. Sollte man auch immer im Hinterkopf
> haben.
[...]
> Und genauso, wie auch du noch überhaupt ein Mapping explizit
> definierst (was nicht sein müsste, wenn du den Code ein wenig mehr
> annotieren würdest), so würde ich gern ein wenig weiter gehen. Ein
> Mapping wie heute üblich hat auch keinen weiteren Nährwert. Es ist nur
> nervig.
Wer sagt denn dass ich DI Mappings explizit definiere? Der Container
kann sie sich per Konventionen selber einsammeln.
> Wenn allerdings das Mapping ein Abfallprodukt einer wertschöpfenden
> Tätigkeit wie der Manifestation eines wesentlichen Architekturaspekts
> wäre, das wäre ein Gewinn!
Richtig.
>> 4) Ich sehe das Problem dass dies in einem Projekt an dem mehrere
>> Entwickler arbeiten nicht durchzuhalten ist.
>
> Warum?
> Wo liegt die unüberwindbare Schwierigkeit gegenüber dem heute üblichen
> Mapping?
Das Mapping wird per Unit Tests geprüft.
>> Und sobald ein einziger
>> Fehler im Graph ist wird das Vertrauen in ihn rapide sinken. Zu lösen
>> ist das Problem meiner Ansicht nach nur durch automatisiertes Erstellen
>> des Graphen (z.B. durch Constructor Injection).
>
> Naja, sobald ein einziger Fehler im Mapping ist, hat dazu auch niemand
> mehr Vertrauen.
Was der Unit Tests aber rechtzeitig entdeckt.
> Deshalb habe ich auch auf imperative Mappings bzw. Mappings im Code
> umgestellt. Die helfen nämlich, Fehler zu vermeiden.
> In einer XML-Datei, in der nichts geprüft wird, schleichen sich viel
> schneller Fehler ein. Da reicht schon falsche Groß/Kleinschreibung.
XML mag ich auch nicht.
Herzliche Grüße,
Stefan
Hallo Ralf,
Hier muss ich dir widersprechen. DRY ist ein Prinzip, welches stets zu
100% erfüllt sein sollte.
Es gibt Situationen, wo die Verletzung von DRY (bewusst oder unbewusst)
trotzdem funktionieren. Allerdings sind das entweder reine Glücksfälle
oder Fälle bei denen die Auswirkungen unkritisch sind.
Deine Idee der Generierung einer Architekturdoku, die auf einer
Verletzung von DRY basiert, würde ich als gewagt bezeichnen.
Ciao,
Jens
Ah, ich sehe, was du meinst. Gut, dass wir drüber reden :-) Hier liegt
> DIContainer.Instance
> .Root<IServiceA, ServiceA>()
> .uses<IServiceB, ServiceB>(); // <== Stelle 1
>
> class ServiceA : IServiceA
> {
> private IServiceB _sb; // <== Stelle 2a
>
> public ServiceA()
> {
> _sb = DIContainer.Instance.Get<IServiceB>(); // <== Stelle 2b
> }
>
> }
nämlich eines unserer Missverständnisse:
Deine Prämisse ist: "Ein Kontrakt (bzw. ein Interface) wird nur von
einer Implementation implementiert."
> Kennst du das Managed Extensibility Framework (MEF) schon?http://www.codeplex.com/mefDavon hatte ich vor einiger Zeit mal was gesehen, aber da war noch
> Vor kurzem gab es den ersten Code Drop. Ich habe ihn mir schon kurz
> angeschaut. Ich kann mir vorstellen, dass MEF für die diskutierte Idee
> nützlich ist. Muss aber noch weitere Anforschungen anstellen.
nicht soviel Doku.
Sieht jetzt schon interessanter aus. Bisher aber mehr oder weniger nur
DI ohne explizites Mapping.
Das ist noch nicht so dolle. Gerade weil die Typen noch explizit zum
CompositionContainer hinzugefügt werden müssen, finde ich den Schritt
voran noch nicht so groß. Wen schon Attribute, dann doch bitte auf das
Hinzufügen von Assemblies geschränken.
Aber egal.
Ich werde jetzt mein DI Mapping über einen Abhängigkeitsgraphen
aufbauen.