Hi,
an sich ist es recht schwer Deine Frage "korrekt" zu beantworten. Wie auch bei der strukturierten Programmierung gibt es bei der Objekt Orientierung (OO) keine eindeutigen Lösungen bzw. Lösungsansätze. Vielmehr können verschiedene Wege gewählt werden, welche für unterschiedliche Lösungen auch unterschiedlich gut geeignet sein dürften.
An sich versucht man beim OO - Ansatz vorallem den intuitiven Ansatz zu verfolgen. Häufig wirst Du hier den Hinweis finden, dass die OO versucht die Natur nachzubilden. An sich ist es wichtig, dass Du verstehst, was eine Klasse sein soll. Während die strukturierte Programmierung sehr strikt zwiscchen Variablen und Methoden trennt, geht die
OOP einen gänzlich anderen Weg. Hier wird zusammengefasst, was zusammen gehört.
Um hier auf das Beispiel mit der Natur zurück zu kommen, man klassifiziert. Der Begriff der Klasse ist entspricht nahezu dem, was Du aus der Biologie kennst. Es gibt z.B. die Klasse der Säugetiere, die der Primaten, die der Affen, die der Gorillas, die der Berggorillas, ... Während man in der Biologie wohl auch Arten, Gattungen, Familien, usw. unterscheidet würde man all das in der Informatik immer nur als Klasse bezeichnen.
Eine Klasse fasst also eine bestimmte Menge von Eigenschaften zusammen. Spreche ich von einem Säugetier, dann wird es sicherlich Lungen besitzen (kenne zumindestens keines ohne), Sauerstoff atmen und natürlich seine Kinder säugen. Eine speziellere Klasse sind dann die Meeressäuger oder die Landsäuger, die eben alle Eigenschaften haben die schon ein Säugetier besitzt, zusätlich aber eben im Meer oder an Land leben. Wichtig ist es diese Hierachie im Hinterkopf zu behalten, sie bietet einen der Vorteile der
OOP. Verlangst Du ein Säugetier, so ist es Dir erstmal egal, ob es sich hier um einen Gorilla oder den Schwertwal Willy handelt. Auf beide dürfte die Bezeichnung Säugetier zutreffen (auch wenn es sich um völlig unterschiedliche Exemplare handelt!).
Eine Klasse ist in erster Linie also eine Art Bauplan. Da stehen die Eigenschaften drin, die alle Exemplare dieser Klasse besitzen. Über Vererbung kann man dann beliebig speziell werden (Säugetier -> Meersäuger -> Wal -> Schwertwal -> ... -> Willy). Von links nach rechts kommen nur neue, speziellere Eigenschaften hinzu, wobei keine verloren geht. Willy ist ein konkretes Exemplar, den gibt es dann auch wirklich. Der Rest ist eher abstrakt und man kann halt sagen, dass Willy ein Wal, ein Schwertwal oder eben auch ein Säugetier ist.
Beim OO - Design überlegst Du dir also erstmal, was für Komponenten Du hast und wie deren Eigenschaften aussehen. Deine Problemlösung zerlegst Du dabei immer in die unabhängigen Teillösungen (so wie man immer rangeht) und fässt hier immer dass zusammen, was wirklich zusammen gehört. Du hast eigentlich schon eine gute Vorgabe gemacht. So gibt es Zellen, die haben bestimmte Eigenschaften (die gelten für jede Zelle einzeln), z.B. könnte das der enthaltene String sein oder halt auch seine Formatierung.
Ebenso gibt es dann Zeilen und Spalten, die setzen sich aus Zellen zusammen.
Ein Stylesheet wiederum setzt sich aus Zeilen und Spalten zusammen, hat sicherlich auch einen Titel usw.
Eine Excel-Datei wiederum umfasst all das.
Alles was hier also genannt wurde hat recht individuelle Eigenschaften und kann entsprechend in eine eigene Klasse gepackt werden. Schwierig (das kommt mit der Erfahrung) ist jetzt noch die Abstraktion, wo man Dinge allgemein zusammenfasst und dann eben spezieller vererbt / ableitet. Dazu musst Du immer schauen, was man einfach und sinnvoll wiederverwenden kann. Das ist wie gesagt nicht immer leicht und der Blick schärft sich mit der Erfahrung.
Wenn Du eine Excel - Datei nimmst, dann ist es halt leicht zu übersehen was Du da schon hast. So siehst Du eine offene Datei, gehst Du hier ins Detail, setzt die sich eben aus den genannten Komponenten zusammen, die eigene Probleme lösen. Die kannst Du halt bis auf Zellen runterbrechen (auch der Inhalt einer Zelle könnte eine eigene Klasse sein, z.B. die Klasse Formel, wenn eine Formel enthalten ist).
Je abstrakter Du modellierst, desto einfacher sind später die Möglichkeiten der Anpassung. Wie bereits gesagt, ein Wal ist ein Säugetier, alles was ich mit Säugetieren machen kann, kann ich halt auch mit Walen machen. Wale haben aber spezielle Eigenschaften, die ein Elefant nicht hätte.
So ähnlich sieht es dann auch mit Excel Dateien aus. Du kannst bestimmte Probleme einfach für Excel - Dateien lösen. So haben Excel - Dateien eben Sheets, die sich aus Zellen zusammensezten. Eine Zelle kann über ihre Position angesprochen und der Inhalt gesetzt / ausegelesen werden. Ebenso kannst Du eine Excel - Datei laden oder speichern. Das ist jetzt aber sehr allgemein und abstrakt. Wie man tatsächlich eine Zelle manipuliert oder eine Datei öffnet kann ziemlich stark variieren. Intuitiv kannst Du hier Eigenschaften finden, die für eine Excel 2003 Tabelle gelten, die Du so auch für eine Excel 2007 Tabelle findest. Trotzdem werden die total unterschiedlich geladen und intern verarbeiet.
Das was die gemeinasm haben steckst Du einfach in eine abstrakte Klasse oder ein Interface. Aus diesen leitest Du dann die spezielleren Klassen ab (die haben alle Eigenschaften / Methoden der abstrakten Klassen / des Interface). Ableiten heißt, dass Du zwar einen Funktionsumfang garantierst, die tatsächlich Umsetzung bleibt aber verborgen und kann leicht ausgetauscht werden.
So, nun mal etwas praktischer am Beispiel, dann verstehst Du das auch sicher besser:
Problem, man möchte eine Excel - Datei laden. Wie bereits gesagt, es gibt Excel 2007 Dateien und Excel 2003 Dateien, die unterschiedlich aufgebaut sind (
XML vs. binär - Format). Allgemein / abstrakt würdest Du einfach eine Excel - Datei öffnen wollen, im speziellen möchtest Du das jeweilige Format öffnen. In der
OOP würdest Du also erstmal ganz abstrakt modellieren
Delphi-Quellcode:
TExcelFileLoader = abstract class(TObject)
public
// Methode zum Laden einer Excel - Datei
function openFile(const path: String): TExcelFile; virtual; abstract;
end;
TExcelFile ist jetzt irgendeine Repräsentation des geöffneten Files. Wichtig ist hier, dass Du eine abstrakte Klasse hast. Die gibt einfach nur vor, dass eine TExcelFileLoader Klasse eine Methode openFile besitzt, die eben genauso aussieht. Da die Klasse aber abstrakt ist, kennst Du hier noch keine Implementierung. Entsprechend kannst Du auch kein Exemplar erzeugen, die Methode ist ja schließlich nicht implementiert.
Die tatsächliche Implementierung findet erst in den jeweiligen Ableitungen fest.
Delphi-Quellcode:
TExcel2007FileLoader = class(TExcelFileLoader)
public
// Methode zum Laden einer Excel - Datei
function openFile(const path: String): TExcelFile; override;
end;
TExcel2003FileLoader = class(TExcelFileLoader)
public
// Methode zum Laden einer Excel - Datei
function openFile(const path: String): TExcelFile; override;
end;
...
implementation
function TExcel2003FileLoader.openFile(const path: String); TExcelFile;
begin
// Spezieller Code zum öffnen der 2003er Dateien
end;
function TExcel2007FileLoader.openFile(const path: String); TExcelFile;
begin
// Spezieller Code zum öffnen der 2007er Dateien
end;
Hier hast Du nun zwei verschiedenen Implementierungen der Methode openFile. Ok, soweit ist das erstmal ziemlich umständlich, immerhin hättest Du auch einfach zwei Funktionen definieren können, wobei eine eben 2007er Dateien öffnet und die andere 2003er. Wo also ist der Vorteil der
OOP?!
Nun ja, mehrfach viel schon das Wort abstrakt, so auch jetzt. Durch die Abstraktion ist eben die Implementierung austauschbar. Du kannst einfach eine Klasse schreiben, welche Dir ein TExcelFileLoader zurückgibt. Was wir über ein TExcelFileLoader wissen ist, dass der eben die Möglichkeit besitzt ein Excel - File zu öffnen, was der dabei macht wissen wir nicht (ist ja abstrakt). Am ehesten nimmt man wahrscheinlich eine Fabrik:
Delphi-Quellcode:
TExcelFileLoaderFactory = class(TObject)
public
class function getExcelFileLoaderBySuffix(const suffix: String): TExcelFileLoader;
end;
...
implementation
class function TExcelFileLoaderFactory.getExcelFileLoaderBySuffix(const suffix: String): TExcelFileLoader;
begin
if (LowerCase(suffix) = '.xls') or
(LowerCase(suffix) = 'xls') then
begin
result := TExcel2003FileLoader.create();
end
else if (LowerCase(suffix) = '.xlsx') or
(LowerCase(suffix) = 'xlsx') then
begin
result := TExcel2007FileLoader.create();
end
else
// Fehlerbehandlung
end;
Wenn Du nun also eine Excel - Datei laden möchtest, dann ist für Dich erstmal unwichtig, was für einen TExcelFileLoader du wirklich bekommst. Du übergibst das Suffix der Datei und bekommst einen ExcelLoader, der die Datei öffnen kann
Delphi-Quellcode:
procedure XYZ.loadFile(const Path: String);
var suffix: String;
ExcelFileLoader: TExcelFileLoader;
begin
// irgendwie das suffix ermitteln
suffix := self.extractSuffix(path);
// Exemplar der Klasse TExcelFileLoader erzeugen
ExcelFileLoader := TExcelFileLoaderFactory.getExcelFileLoaderBySuffix(suffix);
// verwenden
with ExcelFileLoader.openFile(path) do
begin
...
Free;
end;
// aufräumen
ExcelFileLoader.Free;
end;
Was hier wichtig ist, ist die Tatsache dass Du nichts mehr über das zurückgegebene TExcelFileLoader Exemplar wissen musst. In XYZ weißt Du nur von TExcelFileLoader und TExcelFileLoaderFactory. TExcel2003- und TExcel2007FileLoader brauchst Du hingegen nicht zu kennen. Entsprechend können die beliebig ersetzt werden. Kommt z.B. Excel 2011 heraus und besitzt ein total neues Format, so kannst Du den ganzen Code, beibehalten der auf die geöffnete Datei zurückgreift und die Datei öffnet. Du erstellst nur eine weitere Implementierung von TExcelFileLoader, die eben in der Lage ist dieses Format in Deine Repräsentation zu überführen und "registriest" diese in der Factory - Klasse, fertig. Alle Programme, die eben auf die Fabrik zurückgreifen können danach auch Excel 2011 - Dateien verwenden.
Der krasse Gegensatz dazu wäre der direkte Aufruf einer speziellen Methode für jedes Format. Hättest Du also die Funktionen open2003File und open2007File verwendet, müsstest Du nun überall eine weitere Funktion namen open2011File einfügen, in jeder
Unit, welche eine Excel-Datei öffnen können soll. Das ist unübersichtlich, aufwendig und fehleranfällig. Die
OOP kann somit den Ansatz zentralisieren und erlaubt den einfacheren Austausch gegen verschiedene Varianten (z.B. auch gegen schnellere oder fehlerkorrigierte Versionen).
So, nochmal zusammenfassend, alles was zusammengehört solltest Du auch zusammengehörig modellieren. Hier kannst Du sehr allgemein anfangen und Dich ins Spezielle vorarbeiten. Eine Klasse beschreibt dabei nur die gleichartigen Elemente, ein Exemplar ist etwas konkretes, was diese Eigenschaften besitzt (aber auch einen eigenen Zustand besitzt). Verhalten oder Implementierungen, die sich mehrere Klassen teilen kann man in eine gemeinsame Basisklasse stecken, Eigenschaften die sich unterscheiden kommen in die Ableitungen. Gemeinsamer Code kann bereits in der Basisklasse implementiert werden, gleiche Methoden mit unterschiedlichem Verhalten werden in der Basisklasse als abstrakt markiert und müssen entsprechend in den Ableitungen implementiert werden. Individuelle Eigenschaften gehören hingegen auf die Hierachiestufe, auf der sie für alle untergeordneten Klassen gilt!
Besten Gruß,
Der Unwissende