hallo,
ich möchte euch gerne eine Artchitekturfrage stellen, Umgebung ist .net 2.0 mit Visual Studio 2005.
Ich bin nun schon länger am Überlegen und denke, dass es eine bessere Lösung für mein Problem gibt, mir fällt diese aber nicht so wirklich ein. 😉
Vorweg eine kurze Erklärung zum Hintergrund: Abgebildet werden soll ein "Redaktionssystem".
Die Basisklasse ist 'ContentObject', dieses beinhaltet Varianten.
Zu einem Text gibt es Varianten (deutsch, englisch) - also der selbe Inhalt in einer anderen Sprache.
(In Wahrheit gibt es auch noch Versionen, das ist für die Aufgabenstellung jedoch nicht relevant)
Beispiel:
ContentObjekt 1
Obiges soll durch meine Klassen abgebildet werden, Klassen implementieren jeweils die passenden Interfaces (program against an inteface, not an implementation)
public interface IContentObjectKey
{
Guid Id { get; }
}
public interface IContentObject : IContentObjectKey
{
List<IContentObjectVariant> Variants { get; }
IContentObjectVariant AddVariant(string variantType);
IContentObjectVariant AddVariant(IContentObjectVariant variant);
IContentObjectVariant GetVariant(string variantType);
void RemoveVariant(IContentObjectVariant variant);
}
public interface IDocument : IContentObjectKey
{
int DocumentNumber { get; }
// we redefine the same methods as in the base class because
// a document may only contain IDocumentVariants, NOT IContenObjectVariants
List<IDocumentVariant> Variants { get; }
IDocumentVariant AddVariant(string variantType, string variantInfo);
IDocumentVariant AddVariant(IDocumentVariant variant);
IDocumentVariant GetVariant(string variantType);
void RemoveVariant(IDocumentVariant variant);
}
Eigentlich ist ein IDocument eine Spezialisierung eines IContentObjects, also eine abgeleitete Klasse.
Warum sind die selben Methoden in IDocument dann nochmal definiert?
Der Grund dafür ist, dass man bei den entsprechenden AddVariant(...) Mehoden nicht die Basisklasse übergeben darf.
Also
co.AddVariant(...) darf NUR Contentobjektvarianten akzeptieren
doc.AddVariant(...) darf NUR Dokumentvarianten akzeptieren
Weiters soll die Methode GetVariant(...) genau den korrekten Type retourgeben, also entweder 'IContentObjectVariant' oder 'IDocumentVariant',
NICHT eine gemeinsame Basisklasse wie z.B. 'IContentObjectVariant', damit sich der User den Cast auf die korrekte Klasse erspart.
Source im Main:
static void Main(string[] args)
{
// create content object hierarchy
//
IContentObject co = new ContentObject();
IContentObjectVariant deVariant = co.AddVariant("DE"); // create variant internally
deVariant.Content = "Das ist ein Text";
IContentObjectVariant enVariant = new ContentObjectVariant("EN"); // create co variant
co.AddVariant(enVariant); // ...and add it
enVariant.Content = "This is some Text";
// create document hierarchy
//
IDocument doc = new Document(1);
IDocumentVariant deDocVariant = doc.AddVariant("DE", "This is my german variant");
deDocVariant.Content = "Das ist ein Dokumententext";
IDocumentVariant enDocVariant = new DocumentVariant("EN", "This is my english variant");
doc.AddVariant(enDocVariant);
enDocVariant.Content = "This is the content of a document";
}
Trotzdem - die Mehoden AddVariant(), GetVariant(), RemoveVariant() machen eigentlich exakt das gleiche, jedoch mit unterschiedlichen Typen.
Gibt es eine Möglichkeit, dass diese Methoden von einer gemeinsamen Klasse ausgeführt werden, jedoch mit den speziellen Typen arbeiten?
Ich habe den Ansatz über Generics versucht, bin hier jedoch zu keiner Lösung gekommen - für eine solche wäre ich sehr dankbar!
Irgendwie klingt das Ganze etwas nach Template Pattern, aber wie könnte man dieses hier sinvoll einsetzen?
Anbei die Visual Studio Solution.
thx + freundliche Grüße
hannes
Um Dir nicht Dein Beispiel konkret zu beantworten aber das Design aufzuzeigen, hier:
public interface IHelloPrinter
{
void PrintHello();
}
public class EnglishHelloPrinter : IHelloPrinter
{
public void PrintHello()
{
System.Console.WriteLine("Hello World!");
}
}
public class GermanHelloPrinter : IHelloPrinter
{
public void PrintHello()
{
System.Console.WriteLine("Hallo Welt!");
}
}
public class HelloFactory
{
public IHelloPrinter CreateHelloPrinter(string language)
{
switch (language)
{
case "de":
return new GermanHelloPrinter();
case "en":
return new EnglishHelloPrinter();
}
return null;
}
}
Beantwortet das Deine Frage?
Wenn Du das jetzt noch entsprechend weiter bearbeitest fällt auch
switch (language)
{
case "de":
return new GermanHelloPrinter();
case "en":
return new EnglishHelloPrinter();
}
EDIT: Korrektur: Um Dir zum. 1 mögliches Design aufzuzeigen...
Ob das auf Deine Anforderungen passt, musst Du Dir selbst beantworten.
Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.
hallo "dr4g0n76",
thx für deine antwort, leider passt diese nicht ganz zu meiner frage. 🙂
mir ist sinn + implementierung des factory patterns durchaus bekannt, aber in dem konkreten fall hilft es mir nicht.
Die beiden, verschiedenen Klassen werden bereits über eine Factory erzeugt (im Produktivcode, nicht in meinem vereinfachten Beispiel) und abhängig vom gewünschten Dokumententyp erzeugt mir die Factory ein ContentObjekt oder ein Document.
...es gibt sogar noch mehr Unterscheidungen.
Und genau da ist das Problem - ich möchte nicht bei jeder Spezialisierung immer die selben Funktionen implementieren (AddVariant(), GetVaraint(), RemoveVariant()), kann jedoch auch nicht mit einer gemeinsamen Basisklasse arbeiten, da ich dann den Funktionen eine Instanz der Basisklasse übergeben kann, das aber nicht erlaubt sein soll.
Soll heißen: Document.AddVariant() darf keine ContentObjekt Varianten akzeptieren, ansonsten macht die AddVariant() Funktion in beiden Klassen aber genau das selbe.
fg
hannes
Na dann mach doch eine Basisklasse, mach die AddVariant-Methoden virtual und überschreib sie dann, indem du noch eine Prüfung davor schiebst, die halt schaut, ob der Typ des angegebenen Objektes tatsächlich zb IDocumentVariant ist...
hallo "onlinegurke", gg
...interessante namen habt ihr hier.
So hatte ich das auch schon, das hat aber folgende Nachteile:
Was aber viel gravierender ist: Der Fehler tritt dann zur Laufzeit auf, nicht zur Design (compile) Zeit, also genaugenommen irgendwann, sobald jemand den Code "falsch" verwendet und das ist sicher kein gutes Design.
=> Man soll bereits beim Kompilieren eine Fehlermeldung bekommen.
fg
hannes
Dann blende die Funktion für Intellisense und in der Doku aus und überlade sie in der abgeleiteten Klasse, dann kommen eventuelle DAU-Entwickler auch nicht auf die Idee, die Methode so auszuführen, dass da was schief geht. Kosten des Verfahrens: Ein Cast, der aber gegenüber den Redudanzen, die du sonst hast, vernachlässigbar ist.
hi,
wow + thx -> hier bekommt man echt schnell antworten.
Dann blende die Funktion für Intellisense und in der Doku aus Dann sind sie nicht mehr sichtbar, aber trotzdem aufrufbar, imho. nicht schön.
...ich will hier nicht nerven/kleinlich erscheinen, aber ich hoffe doch, dass es irgendwie anders geht.
=> das schreit förmlich nach Generics, mir ist aber nicht klar, wie ich diese im genannten Fall einsetzen muss...falls ich noch "dahinterkomme", werde ich es natürlich posten.
überlade sie in der abgeleiteten Klasse
Dann muss ich alle Funktionen in allen abgeleiteten Klassen überladen und somit den Code duplizieren, genau das möchte ich nicht.
...muss noch drüber sinieren...ich denke, es geht mit Generics, aber weiß noch nicht wie.
fg
hannes
hi,
ich denke, ich habe nun die Lösung - via Generics gefunden.
ContentObject<T> bekommt als Parameter den Type der zu verwendenen Variante mit.
Die Funktionen AddVariant(), GetVariant(), RemoveVariant() arbeiten mit dem generischen Typen und sind nur 1 mal implementiert.
Anbei die Solution, falls es jemanden interessiert. 😉
fg
hannes