Wie sollte eine grundlegende Klassenhierarchie konstruiert werden? [geschlossen]

8

Ich kann einfache Klassen programmieren und verwenden, und ich weiß sogar, wie die Vererbung funktioniert und wie sie verwendet wird. Es gibt jedoch eine sehr begrenzte Anzahl von Anleitungen zur tatsächlichen Gestaltung der Struktur Ihrer Klassenhierarchie oder sogar zum Entwurf einer einfachen Klasse. Außerdem, wann und warum sollte ich eine Klasse erben (oder verwenden)?

Ich frage nicht wirklich nach wie , ich frage wann und warum . Beispielcodes sind immer ein guter Weg zu lernen, also würde ich sie zu schätzen wissen. Heben Sie auch den Fortschritt des Designs hervor, anstatt nur einen Satz darüber zu geben, wann und warum.

Ich programmiere hauptsächlich in C ++, C # und Python, aber ich werde wahrscheinlich die einfachen Beispiele in den meisten Sprachen verstehen.

Wenn einer der Begriffe durcheinander scheint, können Sie gerne meine Frage bearbeiten. Ich bin kein Einheimischer und ich bin mir nicht aller Wörter sicher.

    
Skamah One 21.04.2013, 15:30
quelle

5 Antworten

9

Ich werde C ++ als Beispielsprache verwenden, da es so sehr auf Vererbung und Klassen beruht. Im Folgenden finden Sie eine einfache Anleitung zum Erstellen von Steuerelementen für ein einfaches Betriebssystem, z. B. Windows. Zu den Steuerelementen gehören einfache Objekte in Ihren Fenstern, z. B. Schaltflächen, Schieberegler, Textfelder usw.

Erstellen einer Basisklasse.

Dieser Teil des Leitfadens gilt für (fast) jede Klasse. Denken Sie daran, gut geplant ist halb fertig. An welcher Art von Klasse arbeiten wir? Welche Attribute gibt es und welche Methoden braucht es? Dies sind die wichtigsten Fragen, die wir uns stellen müssen.

Wir arbeiten hier an OS-Steuerelementen, also fangen wir mit einer einfachen Klasse an, soll es Button sein. Nun, was sind die Attribute auf unserer Schaltfläche? Offensichtlich benötigt es ein position im Fenster. Außerdem möchten wir nicht, dass jede Schaltfläche dieselbe Größe hat, also ist size ein anderes Attribut. Button "benötigt" auch einen label (der Text, der auf der Schaltfläche gezeichnet wird). Das machen Sie mit jeder Klasse, Sie entwerfen sie und codieren sie dann. Jetzt weiß ich, welche Attribute ich brauche, also bauen wir die Klasse.

%Vor%

Beachten Sie, dass ich alle Getter und Setter und andere Methoden wegen des kürzeren Codes weggelassen habe, aber Sie müssten diese auch einschließen. Ich erwarte auch, dass wir Point und Size Klassen haben, normalerweise müssten wir sie selbst strukturieren.

In die nächste Klasse wechseln.

Nachdem wir nun eine Klasse ( Button ) fertiggestellt haben, können wir zur nächsten Klasse wechseln. Gehen wir mit Slider , dem Balken, der z.B. hilft Ihnen, Webseiten nach oben oder unten zu scrollen.

Beginnen wir wie auf der Schaltfläche, was braucht unsere Slider-Klasse? Es hat die Position ( position ) im Fenster und size des Sliders. Es hat auch minimale und maximale Werte (Minimum bedeutet, dass der Scroller auf den oberen Rand des Sliders gesetzt ist, und Maximum bedeutet, dass es auf der Unterseite ist). Wir brauchen auch den aktuellen Wert, d. H. Wo sich der Scroller gerade befindet. Das ist genug für jetzt, wir können unsere Klasse aufbauen:

%Vor%

Erstellen einer Basisklasse.

Nun da wir zwei Klassen haben, ist das erste, was wir bemerken, dass wir die Attribute Point m_position; und Size m_size; für beide Klassen definiert haben. Das bedeutet, dass wir zwei Klassen mit gemeinsamen Elementen haben und wir haben den gleichen Code zweimal geschrieben. Wäre es nicht großartig, wenn wir den Code nur einmal schreiben könnten und beiden Klassen sagen würden, dass sie diesen Code verwenden sollen, anstatt ihn neu zu schreiben? Nun, wir können.

Das Erstellen einer Basisklasse ist "immer" (es gibt Ausnahmen, aber Anfänger sollten sich keine Gedanken darüber machen), wenn wir zwei ähnliche Klassen mit gemeinsamen Attributen haben, in diesem Fall Button und Slider . Sie sind beide Kontrollen auf unserem Betriebssystem mit size und position . Daraus erhalten wir eine neue Klasse namens Control :

%Vor%

Erben ähnlicher Klassen aus der gemeinsamen Basisklasse.

Nun, da wir unsere Control -Klasse haben, die die allgemeinen Elemente für jedes Steuerelement enthält, können wir unseren Button und Slider mitteilen, dass sie davon erben. Das spart uns Zeit, Speicher des Computers und schließlich Zeit. Hier sind unsere neuen Klassen:

%Vor%

Nun könnten einige Leute sagen, dass das Schreiben von Point m_position; Size m_size; zweimal viel einfacher ist als das zweimalige Schreiben von : public Control und das Erstellen der Klasse Control . Dies mag in einigen Fällen der Fall sein, aber es wird empfohlen, den gleichen Code nicht zweimal zu schreiben, insbesondere nicht beim Erstellen von Klassen.

Außerdem, wer weiß, wie viele gemeinsame Attribute wir schließlich finden werden. Später können wir feststellen, dass wir Control* m_parent member in der Control -Klasse benötigen, die auf das Fenster (oder Panel oder so) zeigt, in dem unser Steuerelement gespeichert ist.

Eine andere Sache ist, wenn wir später erkennen, dass wir zusätzlich zu Slider und Button auch TextBox benötigen, können wir einfach ein Textfeld-Steuerelement erstellen, indem wir class TextBox : public Control { ... } sagen und nur das textbox-spezifische Element schreiben Variable, statt Größe, Position und Eltern immer wieder in jeder Klasse.

Letzte Gedanken.

Grundsätzlich sollten Sie immer dann, wenn Sie zwei Klassen mit gemeinsamen Attributen oder Methoden haben, eine Basisklasse erstellen. Dies ist die Grundregel, aber Sie dürfen Ihr eigenes Gehirn verwenden, da es einige Ausnahmen geben kann.

Ich bin auch kein professioneller Programmierer, aber ich lerne und ich habe dir alles beigebracht, wie meine Erzieher es mir beigebracht haben. Ich hoffe, dass Sie (oder zumindest jemand) diese Antwort nützlich finden. Und obwohl einige Leute sagen, dass Python und andere Duck Typing-Sprachen nicht einmal Vererbung verwenden müssen, liegen sie falsch. Durch die Verwendung der Vererbung sparen Sie bei größeren Projekten so viel Zeit und Geld, und schließlich danken Sie sich für die Erstellung der Basisklassen. Die Wiederverwendbarkeit und Verwaltung Ihres Projekts wird milliardenfach einfacher.

    
user2032433 21.04.2013, 18:17
quelle
1

Sie müssen Vererbung verwenden, wenn Sie eine Situation haben, in der es zwei Klassen gibt, die die Attribute einer einzelnen Klasse enthalten, oder wenn es zwei Klassen gibt, von denen die eine von der anderen abhängig ist. ZB)

%Vor%

Hier gibt es zwei Klassen, Hund und Katze, die die Attribute der Klasse animal haben. Hier spielt die Vererbung eine Rolle.

%Vor%

Hier sind Eltern und Kind zwei Klassen, wobei das Kind vom Elternteil abhängig ist, wobei das Kind die Attribute des Elternteils und seine eigenen einzigartigen Eigenschaften hat. Also, Vererbung wird hier verwendet.

    
Aswin Murugesh 21.04.2013 15:42
quelle
1

Das hängt von der Sprache ab.

In Python zum Beispiel brauchen Sie normalerweise nicht viel Vererbung, weil Sie jedes Objekt an irgendeine Funktion übergeben können und wenn die Objekte die richtigen Methoden implementieren, wird alles in Ordnung sein.

%Vor%

Im obigen Code sind Dog und Cat nicht verwandte Klassen, aber Sie können eine Instanz von beiden an eine Funktion übergeben, die name verwendet und die Methode sing aufruft.

In C ++ müssten Sie stattdessen eine Basisklasse hinzufügen (z. B. Animal ) und diese beiden Klassen als abgeleitet erklären.

Natürlich ist Vererbung auch in Python implementiert und nützlich, aber in vielen Fällen, in denen es notwendig ist, in C ++ oder Java, können Sie es dank "ente typing" einfach vermeiden.

Wenn Sie jedoch zum Beispiel die Implementierung einiger Methoden erben möchten (in diesem Fall des Konstruktors), dann könnte die Vererbung auch mit Python mit

verwendet werden %Vor%

Die dunkle Seite der Vererbung ist, dass Ihre Klassen mehr gekoppelt und in anderen Kontexten, die Sie nicht vorhersehen können, schwieriger wiederzuverwenden sind.

Jemand hat gesagt, dass man bei der objektorientierten Programmierung (eigentlich klassenorientierte Programmierung) manchmal nur eine Banane braucht und stattdessen einen Gorilla bekommt, der eine Banane und einen ganzen Dschungel hält.

    
6502 21.04.2013 15:57
quelle
1

Ich würde mit der Definition der Klasse von wikipedia beginnen:

  

In der objektorientierten Programmierung ist eine Klasse ein Konstrukt, das verwendet wird   Erzeuge Instanzen von sich selbst - bezeichnet als Klasseninstanzen, Klasse   Objekte, Instanzobjekte oder einfach Objekte. Eine Klasse definiert   konstituierende Mitglieder, die ihren Instanzen ermöglichen,   Verhalten. Datenfeldmitglieder (Mitgliedsvariablen oder Instanzvariablen)   Aktivieren Sie eine Klasseninstanz, um den Status beizubehalten. Andere Arten von Mitgliedern,   insbesondere Methoden, ermöglichen das Verhalten von Klasseninstanzen. Klassen   Definieren Sie den Typ ihrer Instanzen

Oft sehen Sie Beispiele, die Hunde, Tiere, Katzen und so weiter verwenden. Aber lass uns zu etwas Praktischem kommen.

Der erste und geradlinigste Fall, wenn Sie eine Klasse benötigen, ist, wenn Sie (oder vielmehr Sie) bestimmte Funktionen und Methoden zusammen kapseln müssen, weil sie einfach zusammen Sinn ergeben. Stellen wir uns etwas Einfaches vor: HTTP-Anfrage.

Was benötigen Sie beim Erstellen einer HTTP-Anfrage? Server, Port, Protokoll, Header, URI ... Sie könnten all das in dict wie {'server': 'google.com'} setzen, aber wenn Sie die Klasse dafür verwenden, machen Sie es nur explizit, dass Sie diese Attribute zusammen benötigen und verwenden werden sie diese eine bestimmte Aufgabe zu tun.

Für die Methoden. Sie könnten die Methode fetch(dict_of_settings) erneut erstellen, aber die gesamte Funktionalität ist an Attribute von HTTP class gebunden und macht ohne sie keinen Sinn.

%Vor%

Ist es nicht nett und lesbar?

Abstrakte Klassen / Interfaces

Dieser Punkt, nur schnell ... Angenommen, Sie möchten Dependency Injection in Ihrem Projekt für diesen speziellen Fall implementieren: Sie möchten, dass Ihre Anwendung unabhängig von der Datenbank-Engine ist.

Sie schlagen also eine Schnittstelle vor (die durch die abstrakte Klasse repräsentiert wird), die jeder Datenbankkonnektor implementieren sollte, und sich dann auf generische Methoden in Ihrer Anwendung verlassen. Nehmen wir an, Sie definieren DatabaseConnectorAbstract (Sie müssen nicht wirklich in Python definieren, aber Sie tun in C ++ / C #, wenn Sie eine Schnittstelle vorschlagen) mit Methoden:

%Vor%

Klassenhierarchie

Python-Ausnahmen . Schauen Sie sich kurz die Hierarchie an.

ArithmeticError ist generisch Exception und in manchen Fällen kann es so spezifisch sein wie FloatingPointError . Dies ist sehr nützlich beim Umgang mit Ausnahmen.

Sie können dies besser auf .NET-Formularen beim Objekt realisieren muss beim Hinzufügen zum Formular eine Instanz von Control sein, kann aber praktisch alles andere sein. Der springende Punkt ist, dass das Objekt DataGridView ist, während es immer noch Control ist (und alle Methoden und Eigenschaften implementiert). Dies ist eng mit abstrakten Klassen und Schnittstellen verbunden und eines von vielen Beispielen aus der Praxis könnte HTML-Elemente sein:

%Vor%

Ich habe versucht, es so kurz wie möglich zu machen, also frag es bitte im Kommentar.

    
Vyktor 21.04.2013 19:06
quelle
1

Da Sie sich hauptsächlich für das Gesamtbild und nicht für die Mechanismen des Klassendesigns interessieren, möchten Sie sich vielleicht mit dem SOLID Prinzipien des objektorientierten Designs. Es ist kein strenges Verfahren, sondern ein Satz oder Regeln, die Ihr eigenes Urteilsvermögen und Ihren Geschmack unterstützen.

  1. Das Wesentliche ist, dass eine Klasse eine einzelne Verantwortung (das S) repräsentiert. Es macht eine Sache und macht es gut. Es sollte eine Abstraktion darstellen, vorzugsweise eine, die einen Teil der Logik Ihrer Anwendung darstellt ( kapselt Verhalten und Daten, um dieses Verhalten zu unterstützen). Es könnte auch eine Aggregationsabstraktion von mehreren verwandten Datenfeldern sein. Die Klasse ist die Einheit einer solchen Verkapselung und ist verantwortlich für die Aufrechterhaltung der Invarianten Ihrer Abstraktionen.

  2. Der Weg zum Erstellen von Klassen besteht darin, sowohl offen für Erweiterungen als auch geschlossen für Änderungen (das O) zu sein. Identifizieren Sie wahrscheinliche Änderungen in den Abhängigkeiten Ihrer Klasse (entweder Typen oder Konstanten, die Sie in ihrer Schnittstelle und Implementierung verwendet haben). Sie möchten, dass die -Schnittstelle vollständig genug ist , damit sie erweitert werden kann. Dennoch möchten Sie, dass die -Implementierung so robust ist , dass sie dafür nicht geändert werden muss.

    Das sind zwei Prinzipien der Klasse als Grundbaustein. Bauen Sie nun Hierarchien auf, die Klassenbeziehungen darstellen.

  3. Hierarchien werden durch Vererbung oder Komposition erstellt. Das Schlüsselprinzip hierbei ist, dass Sie nur die Vererbung verwenden, um die strikte Liskov-Substituierbarkeit (das L) zu modellieren. Dies ist eine originelle Art zu sagen, dass Sie Vererbung nur für is-a Beziehungen verwenden. Für alles andere (abgesehen von einigen technischen Ausnahmen, um einige geringfügige Implementierungsvorteile zu erhalten) verwenden Sie Komposition. Dadurch wird Ihr System so locker wie möglich gehalten.

  4. Irgendwann könnten viele verschiedene Kunden aus verschiedenen Gründen von Ihren Klassen abhängig sein. Dadurch wird Ihre Klassenhierarchie erweitert, und einige der Klassen in der Hierarchie können übermäßig große ( "Fett" ) Schnittstellen erhalten. Wenn das passiert (und in der Praxis ist es eine Frage des Geschmacks und des Urteilsvermögens), segmentieren Sie Ihre allgemeine Klassenschnittstelle in viele kundenspezifische Schnittstellen (das I).

  5. Wenn Ihre Hierarchie noch weiter wächst, kann es aussehen, als würde sie eine Pyramide bilden, wenn Sie sie mit den darunter liegenden Basisklassen und ihren Unterklassen oder Komposita darunter zeichnen. Dies bedeutet, dass Ihre übergeordneten Anwendungsebenen von Details auf niedrigeren Ebenen abhängen . Eine solche Sprödigkeit (die sich zum Beispiel durch große Kompilierzeiten oder sehr große Kaskaden von Änderungen nach kleineren Refactorings manifestiert) kann man vermeiden, indem man sowohl die übergeordnete Ebene als auch die unterlagerte Ebene von Abstraktionen abhängig macht (dh Schnittstellen, die in C ++ z kann als abstrakte Klassen oder Vorlagenparameter implementiert werden. Eine solche Abhängigkeitsinversion (das D) hilft wiederum, Kopplungen zwischen den verschiedenen Teilen Ihrer Anwendung zu lockern.

Das ist es: fünf solide Ratschläge , die mehr oder weniger sprachunabhängig sind und den Test der Zeit. Software-Design ist schwer, diese Regeln sollen dich von den am häufigsten auftretenden Arten von Ärger fernhalten, alles andere kommt durch Übung.

    
TemplateRex 21.04.2013 19:59
quelle