Klassendesign die xte

  • VB.NET

Es gibt 33 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Klassendesign die xte

    yo Leute,

    hab mal wieder eine Frage zum Klassendesign. ( Ja wieder mal...)
    Je mehr ich mich mit dem Ganzen beschäftige desto "einleuchtender" werden manche Sachen.
    Nun hab ich nie so wirklich verstanden warum man seine eigens erstellen Klassen so vorbereiten sollte, dass diese nach der Vererbung auch gut weiterverwendet werden könne.
    Denn "warum" sollte ich eine eigens erstelle Klasse denn vererben wollen?

    Da kam mir so eine Art Geistesblitz und ich wollte mal von euch Erfahrenen wissen ob dieser Blitz seiner Gerecht wird.

    Man kann ja ein Dll-Projekt einem anderen Projekt (zB.: Konsolenapplikation) hinzufügen. Somit stehen in der ProjektMappe dann beide Projekte zur Verfügung.
    Setzt ich nun in der KonsolenApp einen Verweis auf das Dll-Projekt kann ich normal darauf zugreifen (wie bei jedem anderen Import halt auch).

    Würde ich aber jetzt Änderungen an der Klasse benötigen würde ich ja dann nicht das Dll-Projekt ansich verändern sondern eine neue Klasse in der Konsolen-App erstellen,
    welche von der Klasse im Dll-Projekt erbt. Denn würde ich das Dll-Projekt selbst ändern hätte das ja vermutlich gravierende Auswirkungen auf eventuell andere Projekte welche auch dieses Dll-Projekt eingebunden hätte.

    Ich würde nun nicht mehr die Klasse aus dem Dll-Projekt verwenden, sondern meine eigens neu erstellte welche halt von der Klasse des Dll-Projekts erbt.

    Bin hier so mit meiner These richtig oder hab ich hier einen Knoten in meinem "Blitz"?
    Sollte man hierzu auf gewisse Sachen aufpassen?
    Wie hendelt ihr solch einen Fall?

    Würde mich ma so intressieren :)

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Vielleicht als Beispiel Regression.
    0) Basisklasse, enthält Array(s) der Werte, Validitätsflags
    1a) von 0) abgeleitete Klasse, enthält mathematische Routinen für eine 2-dimensionale Regression sowie Arrays zur 2d-Regression
    1b) von 0) abgeleitete Klasse, enthält mathematische Routinen für eine 3-dimensionale Regression sowie Arrays zur 3d-Regression
    2a i ii iii) von 1a) abgeleitete Klassen, Spezifika für 2D-Gerade, 2D-Ebene, 2D-Parabel, 2D-Kreis, ...
    2b i ii) von 1b) abgeleitete Klassen, Spezifika für 3D-Gerade, 3D-Ebene, ...
    3a iii) von 2D-Kreis abgeleitete Klasse für 2D-Ellipse
    usw.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    zu Vererbung greife ich, wenn ich feststelle, dass ich mehrere verschiedene Klassen brauche, die aber auch eine Anteil an Funktionalität haben, der bei allen identisch ist.
    Dann lagere ich die identische Funktionalität in eine Basisklasse aus, und meine verschiedenen Klassen können die Funktionalität einfach erben.

    gugge etwa in VersuchsChat die Klassen Client und Server.
    Beide müssen Disposable sein, beide müssen die Events "Disposed", "ChatMessage" und "StatusMessage" auslösen können - daher ist das Zeugs inne Basisklasse.

    Darüberhinaus müssen beide Klassen aber auch über die Methode "Send" verfügen, aber diese Methode jeweils ganz unterschiedlich implementieren.
    Das erzwinge ich durch das MustOverride-Schlüsselwort.
    Hm das mit dem Vererben ansich ist mir im Großen und Ganzen ja klar.
    Auf was ich eigentlich raus will ist, dass es gewisse Klassen ja in jedem Projekt geben kann (gutes Beispiel->Log Klasse zum Aufzeichnen von Fehlern oder was auch immer).
    Diese immer neu zu implementieren wäre deswegen ja unnötig. Wenn ich aber nun dieses Projekt einem anderen Projekt hinzufüge darf ich ja die Log-Klasse nicht angreifen (oder doch?) da diese ja eventuell von anderen Projekten auch verwendet wird.

    Was ich erreichen will ist eigentlich, dass gewisse Klassen ja in mehreren Projekten Sinn ergeben (da will ich gar nicht so weit gehen wie bei deinem Beispiel @RodFromGermany: ).
    Beispiele:
    - Log Klasse zum Schreiben von Log-Files.
    - Extensions
    - Ini Dateien lesen/schreiben
    - und und und

    Wie gesagt. Sind nur Beispiele.
    Wie handle ich das Ganze nun korrekt, damit ich diese Klassen in mehreren Projekten verwenden kann ohne, dass es irgendwelche Konflikte gibt?

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Gibts kein Patentrezept, nur Richtlinien.
    Also grobe Richtlinie ist, maximale Kapselung anzustreben, damit die Zugreifbarkeit von aussen möglichst wenige und möglichst allgemeingültige Member aufweist (etwa ein IList(Of T) ist einer konkreten List(Of T) vorzuziehen)
    Wiederverwendbare Klassen muß man halt besonders umsichtig designen, weil wie du sagst: wenn die bereits in vielen Projekten verwendet werden, ists schlecht, an der Zugreifbarkeit noch einschneidende Änderungen vorzunehmen.
    Innerhalb der Klasse, was nach aussen hin nicht sichtbar ist, kann man durchaus alles mögliche ummodeln und optimieren, solange das Verhalten nach aussen unverändert bleibt.
    Ok wenn ich das so lese ist, denk ich, ist mein Gedankengang richtig.
    Dass die Basisklasse sehr gut im Grunddesign aufgebaut sein muss ist einleuchtend. Da es ja auch nicht wirklich viel bringt in jedem Projekt diese zu vererben und mehr als die Hälfte umzumodeln. Das mit der Sichtbarkeit ist auch ein guter Hinweis.

    Werde das mal ein bisschen antesten im kleinen Rahmen und sehen wie ich damit zu Recht komme.
    Wichtig war mir nur, dass der Ansatz den ich verfolge KEIN schlechter ist :)

    lg

    EDIT: Falls dem hier noch jemand was hinzuzufügen hat, bitte nicht davor scheuen!
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „fichz“ ()

    ErfinderDesRades schrieb:

    und möglichst allgemeingültige Member aufweist (etwa ein IList(Of T)
    oder noch allgemeiner:

    VB.NET-Quellcode

    1. Dim xx As IEnumerable(Of String) = New List(Of String)
    2. Dim yy As IEnumerable(Of String) = New String() {"ljkhjkl", "kjhkjh"}
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    fichz schrieb:

    Wenn ich aber nun dieses Projekt einem anderen Projekt hinzufüge darf ich ja die Log-Klasse nicht angreifen (oder doch?) da diese ja eventuell von anderen Projekten auch verwendet wird.

    Prinzipiell darfst Du das schon, solange und nur solange Du Du nichts an den verwendeten Schnittstellen veränderst.

    Dieses zu gewährleisten, ist aber bei einer stark gewarteten Klasse, wie eben einem LOG, nicht leicht. Als Entwickler kennt man ja die Klasse und ist immer versucht daran rumzubasteln, bzw. Funktionen zu benutzen die später dazugekommen sind.

    Daher ist es bei solchen Klassen meist sinnvoll sie zu kapseln, z.B. durch
    - Interfaces ( = Kontrakte, Verträge) -> 5.10 Schnittstellen

    Nur die darin publizierten Methoden, Properties , Funktionen, Enums sollten nach ausserhalb sichtbar sein (Public) alles andere nicht (Friend,Private).

    Wichtig: man vereinbart nur die maximal benötigte Funktionalität, also keine Liste oder Array wenn ein IEnumerable vollkommen ausreicht. Das gilt für Parameter von Funktionen, Properties, Return-Werte von Funktionen.

    Mehr dazu: OODesign.Com Design Principles

    Bei einer solch angelegten Klasse ist es später sogar möglich Versionen davon nach Bedarf dynamisch hinzuzuladen (Stichwort MEF / MAF).
    @Kangaroo:
    Sorry, dass ich jetzt erst schreibe nur ich wollte das Ganze mal ein bisschen auf mich wirken lassen.

    So ganz komm ich mit der Interface Geschichte nocht nicht klar.
    Bleiben wir am Besten bei dem Log Beispiel.
    Angenommen in Version 1 kann man nur in eine Textdatei speichern. Soweit so gut.
    In Version 2 käme die Idee, man erweitere die Klasse, dass man auch in die Ereigenisanzeige speichern wolle. (Sollte ja nicht allzu problematisch ein, da es ja eine neue Funktion ist. -> Somit werden bestehende Projekte ja immer noch funktionieren.)
    Version 3:
    Es soll eine der Funktionen verändert werden, da es in bestimmten Fällen zu einer Exception kommt. (Gibts hier dann schon Probleme?)
    Version 4:
    Die selbe Funktion gibt nun nicht mehr (nur als Beispiel...) keinen String mehr zurück welcher "OK" oder "Nicht OK" beschreibt sondern nun einen Boolean mit True oder False. (Hier gibts ja dann sicher Probleme). (Hier könnte ich nicht mal überladen, da sich nur der Rückgabewert unterscheidet)

    Wie würde ich so ein Beispiel korrekt lösen, dass alles noch funktioniert? (Auch in den einzelnen Projekten).

    Das mit MEF /MAF ist vermutlich erst mit späteren FW Lösungen zu bewerkstelligen. Hierzu sind wir (zur Zeit noch) auf FW 3.5 beschränkt.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Machen wir ein Beispiel: Du definierst 1 Interface und 2 Klassen, die dieses Interface implementieren:
    "Code"

    VB.NET-Quellcode

    1. Public Interface ILog
    2. Sub Write(ByVal text As String) ' nur 1 Funktion zum Schreiben
    3. End Interface
    4. ' eine Klasse die in das Direktfenster schreibt
    5. Public Class TraceLogClient : Implements ILog
    6. Public Sub Write(ByVal text As String) Implements ILog.Write
    7. Trace.WriteLine(text)
    8. End Sub
    9. End Class
    10. ' eine Klasse die ins Eventlog schreibt
    11. Public Class EventLogClient : Implements ILog
    12. Public Sub Write(ByVal text As String) Implements ILog.Write
    13. EventLog.WriteEntry(System.Reflection.Assembly.GetExecutingAssembly.FullName, text)
    14. End Sub
    15. End Class

    Das kannst Du gerne in eine DLL auslagern.

    Jetzt möchtest Du in Deinem Programm Log-Einträge schreiben:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Public LOG As ILog = New TraceLogClient
    3. 'Public LOG As ILog = New EventLogClient
    4. Private Sub OnKeyDownHandler(ByVal sender As Object, ByVal e As KeyEventArgs) Handles Me.KeyDown
    5. LOG.Write("Es wurde folgende Taste gedrückt: " & e.KeyData.ToString)
    6. End Sub
    7. Private Sub OnKeyUpHandler(ByVal sender As Object, ByVal e As KeyEventArgs) Handles Me.KeyUp
    8. LOG.Write("Es wurde folgende Taste losgelassen: " & e.KeyData.ToString)
    9. End Sub
    10. End Class

    Hier schreibt das Programm überall in das Direktfenster. Soll Dein Programm lieber in das Eventlog schreiben, so tauscht Du Zeile 2 gegen Zeile 3 aus. Nirgendwo sonst muss nur 1 Zeile Code verändert werden.

    ad Frage 3) wenn Du irgendetwas in den beiden Klassen TraceLogClient oder EventLogClient änderst ( z.B. einen Zeitstempel mitsschreibst), ist das vollkommen egal.

    ad Frage 4) Du möchtest jetzt statt der SUB eine Funktion haben , die einen Boolean Wert zurückgibt. Dazu müsstest Du den Vertrag (=Interface) verändern. Als Folge müssten alle Programme die Deine DLL benutzen neu kompiliert werden.

    Beachte: das ist nur ein Beispiel, keinesfalls keine gute Implementierung einer Log-Klasse.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Kangaroo“ ()

    Ok nun leuchtet mir das mehr ein.
    Also sollte man das was man zu 100% benötigt (wie in diesem Fall nur das Write) in ein Interface auslagern.

    Vorteile sind ja daran klar ersichtlicht.
    Der Nachteil jedoch mMn ist aber, dass ein Interface ansich nicht groß verändert werden darf (zB es kommen 2 Member hinzu), da dies ja dann von den Klassen implementiert werden muss. Ich denke da halt trotzdem, dass man soetwas im Vornhinein nicht zu 100% planen kann.

    Aber man wird darüber nicht hinweg kommen es so zu machen. Ist ja auch die Frage wie oft, dass so vorkommen wird.

    Werde es mal so im kleinen Rahmen eines Testprojekts machen und sehen wie ich zurecht komme :)

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    fichz schrieb:

    in ein Interface auslagern.
    In ein Interface kannste nix auslagern.
    Dort kannste nur Prozeduren und Properties deklarieren, die dann von der ableitenden Klasse implementiert werden müssen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    fichz schrieb:

    Der Nachteil jedoch mMn ist aber, dass ein Interface ansich nicht groß verändert werden darf (zB es kommen 2 Member hinzu)

    Richtig. Deshalb wird es ja manchmal auch als Vertrag/Contract bezeichnet. Insofern muss man ein Interface sorgfältig planen. Die Klassen TraceLogClient/EventLogClient dürfen natürlich weitere Properties oder Funktionen haben. Ein Interface definiert nur was mindestens vorhanden sein muss.

    Aber Du kannst ein Interface auch beerben, z.B. so:

    VB.NET-Quellcode

    1. Public Interface ILogExtended : Inherits ILog
    2. Overloads Sub Write(ByVal level As Integer, ByVal text As String) ' schreibt Severity-Level + text
    3. End Interface

    Das ist dann halt ein erweiterter Vertrag. Die beiden Beispielklassen können damit auch beide Verträge implementieren oder noch beliebig andere dazu wie IDisposable. Das ist die Billigversion von Polymorphismus (Multibeerbung) in .Net

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Kangaroo“ ()

    @RodFromGermany:
    Hab mich da falsch ausgedrückt. Ansich weiß ich aber was gemeint ist, bzw. wie Interfaces aufgebaut sind. Mir erschien der Sinn von Interfaces in diesem Bereich jedoch noch nicht ganz klar :)

    @Kangaroo: Ok denke so werde ich weiterkommen :) besten Dank dafür!

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    yo Leute,

    Ich muss das Thema leider nochmal ausgraben.
    Ich habe nun einmal ein Log Interface erstellt welches eigentlich nur 3 (mit Enum 5) Member aufweist.
    Dies ist einfach mal ein LogLevel inkl. Enum. Ein LogType inkl. Enum (Info, Error, ...) und eine Write Methode mit Paramteter LogType (um welchen Typ es sich handelt), dem LogLevel (ab wann soll diese geschrieben werden) und dem eigentlichen Text.

    Nun erstellt ich sagen wir mal 3 Klassen. Die eine schreibt wie hier im Thread bereits mal vorgekommen einfach per Trace.Writeline. Diese Klasse benötige nichts ausser diese Member des Interfaces da sie ja recht simpel ist.
    Anders siehts ja zB schon aus bei einer Klasse welches eine Datei beschreiben soll. Hierbei wird zusätzlich ein FileInfo - Objekt benötigt.
    So und die dritte Klasse schreibt das in das Windows Event Log wo vielleicht noch fein wäre auszusuchen welches Log (System, Anwendung,...) den befüllt werden soll.

    Ich kann alle 3 Klassen so instanzieren

    VB.NET-Quellcode

    1. Dim log as ILogging = New TracerLog/FileLog/Eventlog


    IntelliSense zeigt mir hierbei natürlich nur die Member des Interfaces an. Wie aber setze ich zB bei der FileLog Klasse den Dateipfad?
    Natürlich könnte ich jetzt dies entweder im Konstruktor machen. Jedoch kann ich diesen ja auch im Nachhinein nicht mehr ändern.
    Natürlich könnte ich das Log-Objekt nach FileLog casten. Jedoch erscheint mir dann der Sinn des Interfaces nicht mehr so ganz weil die Methode von @Kangaroo: nicht mehr funktionieren würde (das Austauschen der Variablen in Post 10 [VB.NET] Klassendesign die xte).

    Wie würde man das am "korrektesten" anstellen?
    Ich hoffe es geht hierbei heraus auf was ich genau raus will :)

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    naja - zunächstmal sollteste uns mitteilen, wie ILogging definiert ist. DEnkbar wäre

    VB.NET-Quellcode

    1. public Interface ILogging: Sub Write(message As STring):End Interface
    Gut. Und TracerLog/FileLog/Eventlog implementieren diese.

    Jo, nun

    fichz schrieb:

    Wie aber setze ich zB bei der FileLog Klasse den Dateipfad?
    sehe ich das Problem nicht - denkbar wäre doch

    VB.NET-Quellcode

    1. Dim log as ILogging = New FileLog("<meine Log-Datei>")
    vorrausgesetzt, FileLog verfügt über den entsprechenden Konstruktor.
    Das Interface sieht zur Zeit so aus:

    VB.NET-Quellcode

    1. Public Interface ILogging
    2. Sub Write(ByVal LogType As LogTypeEnum, ByVal LogLevel As LogLevelEnum, ByVal szText As String)
    3. Property LogLevel() As LogLevelEnum
    4. ReadOnly Property LogDate() As Date
    5. Enum LogLevelEnum
    6. OnlyError = 1
    7. Level1 = 1
    8. Level2 = 2
    9. Level3 = 4
    10. Level4 = 8
    11. Level5 = 16
    12. Level6 = 32
    13. All = 64
    14. Level7 = 64
    15. End Enum
    16. Enum LogTypeEnum
    17. INF = 1
    18. WAR = 2
    19. ERR = 4
    20. End Enum
    21. End Interface


    Wie gesagt im Konstruktor kann ich es machen. Nur wie kann ich den Dateipfad dann wieder ändern? Als Beispiel kann man irgendwo in den Einstellungen der Applikation den Dateipfad anpassen.

    lg
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Die Member LogDate und LogLevel haben im Interface nix verloren.
    Auch die Enum-Definitionen eigentlich nicht - die könnten ebenso auf Namespace-Ebene stehen oder sonstwo. Ich wusste garnet, dass man innerhalb eines Interfaces Konstanten wie Enums definieren kann - und das hat ja auch keinerlei Vertrags-Charakter, den die das Interface implementierenden Klassen einhalten müssten. Aber stört wohl auch nicht.

    Mit deinem "Date" - Member etwa schreibst du den Klassen vor, ein Datum zu haben. Was aber soll eine FileLog-Klasse mit einem Datum?

    Wenn du das aktuelle Datum in die Logs schreiben willst, dann soll die Write-Implementierung der FileLog-Klasse halt Date.Now mit reinschreiben - eine Property "Date" braucht sie nicht dazu.
    Auch LogLevel sollte keine Eigenschaft der FileLog-Klasse sein, sondern ein Parameter der Write-Methode:

    VB.NET-Quellcode

    1. Public Interface ILogging
    2. Sub Write(ByVal LogType As LogTypeEnum, ByVal LogLevel As LogLevelEnum, ByVal szText As String)
    3. Enum LogLevelEnum
    4. OnlyError = 1
    5. Level1 = 1
    6. Level2 = 2
    7. Level3 = 4
    8. Level4 = 8
    9. Level5 = 16
    10. Level6 = 32
    11. All = 64
    12. Level7 = 64
    13. End Enum
    14. Enum LogTypeEnum
    15. INF = 1
    16. WAR = 2
    17. ERR = 4
    18. End Enum
    19. End Interface
    (warum heist dein Text-Parameter "szText"?)

    tja, das Logfile auch nachträglich ändern ist über das Interface nicht möglich.
    Bist du dir sicher, dass das ühaupt eine gute Idee wäre?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Das mit dem Date hab ich mir schon fast gedacht. Hab ich mal entfernt.
    Ich hab das Interface und die Klassen nun in einen eigenen Namespace "Log" gepflanzt. Hierzu hab ich eine Klasse LogConst erstellt wo unter anderem dann die Enums platziert werden.
    LogLevel werd ich mir noch ansehen. Hatte da irgendso eine Idee, jedoch grenzt es ja die Klassen wieder etwas mehr ein.

    Dann hätte mein Interface genau eine Methode. Hm. Ist das so üblich oder irgendwie schlimm?

    lg

    EDIT: Gerade rausgefunden, dass man Enums auch direkt im Namespace als Public deklarieren kann. Somit ist die Klasse LogConst Geschichte.
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „fichz“ ()