folgendes Szenario¹: Software für eine Kraftfahrzeugvermietung. Es gibt
eine abstrakte Basisklasse 'cFahrzeug' und abgeleitete Klassen 'cAuto',
'cLkw' und 'cMotorrad'. Der Code zum Speichern und Laden der Kfz-Daten
steht als shared- bzw. static-Methode in der Basisklasse:
Public Shared Function LeseFahrzeug(kennzeichen as String) As IFahrzeug
Dieser Code kann nicht in den abgeleiteten Klassen stehen, da beim
Aufruf der Methode nicht bekannt ist, zu welchem Fahrzeugtyp das
Kennzeichen gehört.
Jetzt habe ich das Problem, dass ich in der LeseFahrzeug-Methode das
korrekte Objekt erzeugen muss, also entweder cAuto, cLkw oder cMotorrad.
Dazu speichere ich den Typ des jeweiligen Kfz ebenfalls in der Datenbank
und erzeuge dann beim Lesen per Reflection den korrekten Typ:
Dim ret as IFahrzeug
dim typ as String = LeseTyp(kennzeichen)
ret = DirectCast(CreateInstance(typ, False, BindingFlags.Default, _
Nothing, New Object() {sDatenFuerKonstruktor}, _
CultureInfo.InvariantCulture, Nothing), Interfaces.IFahrzeug)
Irgendwie gefällt mir aber nicht, dass ich hierfür Reflection einsetzen
muss, da es nicht wirklich elegant und vergleichsweise langsam ist.
Die offensichtliche Alternative mit Select Case:
Select Case sTyp
Case "Firma.Vermietung.cAuto"
ret = new cAuto
Case "Firma.Vermietung.cLkw"
ret = new cLkw
[...]
End Select
Gefällt mir aber auch nicht so, da ich jetzt schon weiß, dass ich
vergessen werde, diese Stelle ebenfalls anzupassen, sobald die Software
um cSchneemobil erweitert werden soll. Und elegant ist auch etwas anderes.
Gibt es noch weitere Wege, das gewünschte zu erreichen? Gerne auch
Hinweise, wenn man dieses Szenario völlig anders abdecken konnte oder
sollte.
Danke,
Wolf
¹: Tatsächlich geht es natürlich nicht um eine Autovermietung, aber
Autos scheinen ja das kanonische Standardbeispiel für Vererbung zu sein :-)
--
Oh Gravity, thou art a heartless bitch.
Hier mal eine Demo, wie ich es machen würde:
Option Strict On
Imports System.Reflection
Module Module1
Sub main()
Dim f As IFahrzeug
f = cFahrzeug.LeseFahrzeug("ConsoleApplication1.Fahrzeuge.cMotorrad")
If f IsNot Nothing Then
Console.WriteLine("Klasse: {0}", f.Klasse)
End If
Console.WriteLine("--- fertig ---")
Console.ReadKey()
End Sub
End Module
Public Interface IFahrzeug
ReadOnly Property Klasse As String
End Interface
Public MustInherit Class cFahrzeug
Implements IFahrzeug
Public Shared Function LeseFahrzeug(ByVal kennzeichen As String) As
IFahrzeug
Dim t As Type = GetFahrzeugtyp(kennzeichen)
If t Is Nothing Then Return Nothing
Return CType(Activator.CreateInstance(t), IFahrzeug)
End Function
Private Shared Function GetFahrzeugtyp(ByVal kennzeichen As String) As
Type
'
Dim gewuenschterTyp As String = kennzeichen ' aus DB laden
'
Dim ass As [Assembly] = [Assembly].GetExecutingAssembly
Return ass.GetType(gewuenschterTyp)
End Function
Public MustOverride ReadOnly Property Klasse As String Implements
IFahrzeug.Klasse
End Class
Namespace Fahrzeuge
Public Class cAuto
Inherits cFahrzeug
Public Overrides ReadOnly Property Klasse As String
Get
Return "Auto"
End Get
End Property
End Class
Public Class cLkw
Inherits cFahrzeug
Public Overrides ReadOnly Property Klasse As String
Get
Return "Lkw"
End Get
End Property
End Class
Public Class cMotorrad
Inherits cFahrzeug
Public Overrides ReadOnly Property Klasse As String
Get
Return "Motorrad"
End Get
End Property
End Class
End Namespace
--
Viele Gruesse
Peter
Du hast die beiden programmatischen Möglichkeiten mit ihren Vor-/Nachteilen
eigentlich schon genannt.
Mich würde bei dem Beispiel eher interessieren, warum der Datentyp in der Datenbank
steht. Gibt es denn nicht unterschiedliche Tabellen für die unterschiedlichen Datentypen,
d.h. eine Auto- und eine LKW-Tabelle (zusätzlich zur gemeinsamen Fahrzeugtabelle)?
Ich meine, wenn Autos und Lkw in derselben Tabelle stehen obwohl sie zwar gemeinsame
aber auch jeweils eigene Felder haben, dann sollte man zuerst das Datenbank-Design
angehen (Normalisierung). Und gibt es dann mal unterschiedliche Tabellen, dann
muss im Code ohnehin abhängig von der Tabelle, in der sich das Fahrzeug
befindet, differenziert werden, welche (Objekt-)Instanz erzeugt wird.
Irgendwo muss es also einen "Select Case" geben, an welcher Stelle auch immer.
Automatisiert, also ohne etwas am Code ändern zu müssen, wenn die Schneemobile
noch hinzukommen, sehe ich keine Möglichkeit ohne Reflection.
Meine Wahl, da ich Reflection nur dann verwende, wenn es gar nicht anders geht,
wäre also die "select case"-Variante. Einen zusätzlichen Typ musst du sowieso
im ganzen Projekt berücksichtigen, also überall, wo du nicht mit IFahrzeug
arbeitest sondern mit dem konkreten Typ. Da ist das Laden aus der Datenbank
nur eine Sache, die man vergessen könnte.
--
Armin
--
Viele Gruesse
Peter
Warum wiederholst du nochmal, was ich schon geschrieben habe?
--
Armin
> Mich w�rde bei dem Beispiel eher interessieren, warum der Datentyp in der
> Datenbank steht.
--
Viele Gruesse
Peter
Das kannst du doch nicht beantworten sondern Wolf. Um den richtigen Instanztyp
zu erkennen, muss nicht der Klassenname in der Datenbank stehen. Das hat
auch nichts damit zu tun, ob du im Programm einen flexiblen Automatismus per
Reflection nutzt oder nicht.
--
Armin
--
Viele Gruesse
Peter
Klar kann er das. Habe auch nichts Gegenteiliges behauptet sondern hatte
gefragt, warum er dort steht. Wenn es darum geht, zu erkennen, um welchen Typ
es sich handelt, gibt es auch noch andere Möglichkeiten ohne
den Klassennamen in der DB abzulegen. Wenn es z.B. zusätzlich zum Satz in einer
gemeinsamen Fahrzeugtabelle nur einen Satz in einer Auto-Tabelle gibt, dann wäre die
Information in der Fahrzeugtabelle, dass es sich um ein Auto handelt (und nicht
um einen LKW) handelt, redundant. Deswegen hatte ich bei Wolf _nachgefragt_, wie
es derzeit im (imaginären) Fall gelöst ist und ausgeführt, wie es bei einer
normalisierten Datenbank aussehen könnte. Daran ist auch nicht's verkehrt, auch
wenn ich mir's nochmal durchlese. Verstehe deine Kritik immer nicht.
> Wenn sich die Klassen in anderen dll's
Von anderen Dlls ist auch gar nicht die Rede, zumindest nicht von mir
und auch nicht von Wolf.
--
Armin
--
Viele Gruesse
Peter
Klar. Du scheinst aber davon auszugehen, dass Reflection - und nur dafür bräuchte
man den Klassennamen in der Datenbank - verwendet werden _muss_. Diese Annahme
ist aber nicht so ganz richtig, denn Wolf schrieb ja schon:
"Irgendwie gefällt mir aber nicht, dass ich hierfür Reflection einsetzen
muss, da es nicht wirklich elegant und vergleichsweise langsam ist."
Er hat ja selbst die beiden Alternativen, mit und ohne Reflection, aufgezeigt.
--
Armin
Verwende doch den XmlSerializer um das ganze zu speichern... der nimmt
Dir alles ab...
Greetings
Jochen
Am 16.07.2011 22:18, schrieb Armin Zingler:
> Mich würde bei dem Beispiel eher interessieren, warum der Datentyp in der Datenbank
> steht. Gibt es denn nicht unterschiedliche Tabellen für die unterschiedlichen Datentypen,
> d.h. eine Auto- und eine LKW-Tabelle (zusätzlich zur gemeinsamen Fahrzeugtabelle)?
Nein, eine Tabelle für alle Fahrzeugtypen.
> Ich meine, wenn Autos und Lkw in derselben Tabelle stehen obwohl sie zwar gemeinsame
> aber auch jeweils eigene Felder haben, dann sollte man zuerst das Datenbank-Design
> angehen (Normalisierung).
In der Tabelle für die Fahrzeugtypen stehen nur die gemeinsamen
Eigenschaften (z.B. Kennzeichen). Alle weiteren Eigenschaften (je nach
Fahrzeugart unterschiedlich) werden in einer weiteren Tabelle
FahrzeugId | EigenschaftsId | Wert
------------------------------------
xxxx | nnnn | foobar
gespeichert. Jeder Fahrzeugtyp definiert einmal, welche Eigenschaften er
hat, das Speichern erfolgt für alle Eigenschaften über den selben Weg.
Zur Zeit sind die vorhandenen Eigenschaften noch pro abgeleiteter Klasse
fest in der jeweiligen Klasse definiert, aber so habe ich die Option,
die Eigenschaften pro Fahrzeugtyp z.B. in einer Konfigurationsdatei
abzulegen.
> Meine Wahl, da ich Reflection nur dann verwende, wenn es gar nicht anders geht,
> wäre also die "select case"-Variante. Einen zusätzlichen Typ musst du sowieso
> im ganzen Projekt berücksichtigen, also überall, wo du nicht mit IFahrzeug
> arbeitest sondern mit dem konkreten Typ.
Ja, für das Frontend der Anwendung ist mir das bewusst. Es wäre nur
schön, wenn man zumindest in der Geschäftslogik auf solche
Unterscheidungen verzichten könnte.
Schönen Gruß,
Wolf
Am 18.07.2011 09:51, schrieb Jochen Kalmbach [MS MVP]:
> Verwende doch den XmlSerializer um das ganze zu speichern... der nimmt
> Dir alles ab...
Danke für das Stichwort, werde ich mir mal anschauen.
Schönen Gruß,
Wolf