Zunächst einmal herzlichen Glückwunsch, dass Sie die Programmierung weiterentwickelt und sich gefragt haben, wie Sie es besser machen können (und eine gute Frage gestellt haben). Es ist eine großartige Einstellung und absolut notwendig, um Ihre Programme einen Schritt weiter zu bringen. Ein großes Lob!
Hier geht es um ein Problem im Zusammenhang mit der Architektur Ihres Programms (oder dem Design, je nachdem, wen Sie fragen). Es geht nicht so sehr darum, was es tut, sondern wie es es tut (dh die Struktur Ihres Programms anstelle seiner Funktionalität). Es ist sehr wichtig , darüber im Klaren zu sein: Sie könnten vollständig machen diese Klassen nehmen File
Objekte als Eingabe und Ihr Programm könnte noch funktionieren. Wenn Sie noch einen Schritt weiter gegangen sind und den gesamten Code für die Ausnahmebehandlung hinzugefügt und sich um Randfälle im Zusammenhang mit Dateien und E / A gekümmert haben (was sollte)irgendwo gemacht werden) in diesen Klassen (... aber nicht dort), und sie wurden zu einem Durcheinander von E / A- und Domänenlogik (Domänenlogik bedeutet Logik in Bezug auf das eigentliche Problem, das Sie lösen möchten), könnte Ihr Programm " Arbeit". Das Ziel, wenn Sie vorhaben, dies mehr als eine einfache, einmalige Sache zu machen, sollte sein, dass es richtig funktioniert , was bedeutet, dass Sie Teile davon ändern können, ohne andere zu beeinflussen, Fehler beheben, wenn sie auftauchen, und sie hoffentlich ohne zu viel erweitern Schwierigkeiten, wann und ob Sie neue Funktionen und Anwendungsfälle finden, die Sie hinzufügen möchten.
OK, jetzt die Antwort. Erstens: Ja, die Verwendung von Dateien als Methodenparameter in der Turbine
Klasse verstößt gegen die SRP. Ihre Turbine
und Airfoil
Klassen sollten nichts über Dateien wissen. Und ja, es gibt bessere Möglichkeiten, dies zu tun. Ich werde Ihnen eine Möglichkeit erläutern, wie ich es zuerst tun und dann genauer erläutern würde, warum es später besser ist. Denken Sie daran, dies ist nur ein Beispiel (nicht wirklich kompilierbarer Code, sondern eine Art Pseudocode) und eine Möglichkeit, dies zu tun.
// TurbineData struct (to hold the data for turbines)
struct TurbineData
{
int number_of_blades;
double hub_height;
}
// TurbineRepository (abstract) class
class TurbineRepository
{
// Defines an interface for Turbine repositories, which return Vectors of TurbineData structures.
public:
virtual std::Vector<TurbineData> getAll();
}
// TurbineFileRepository class
class TurbineFileRepository: public TurbineRepository
{
// Implements the TurbineRepository "interface".
public:
TurbineRepository(File inFile);
std::Vector<TurbineData> getAll();
private:
File file;
}
TurbineFileRepository::TurbineFileRepository(File inFile)
{
// Process the File and handle everything you need to read from it
// At some point, do something like:
// file = inFile
}
std::Vector<TurbineData> TurbineFileRepository::getAll()
{
// Get the data from the file here and return it as a Vector
}
// TurbineFactory class
class TurbineFactory
{
public:
TurbineFactory(TurbineRepository *repo);
std::Vector<Turbine> createTurbines();
private:
TurbineRepository *repository;
}
TurbineFactory::TurbineFactory(TurbineRepository *repo)
{
// Create the factory here and eventually do something like:
// repository = repo;
}
TurbineFactory::createTurbines()
{
// Create a new Turbine for each of the structs yielded by the repository
// Do something like...
std::Vector<Turbine> results;
for (auto const &data : repo->getAll())
{
results.push_back(Turbine(data.number_of_blades, data.hub_height));
}
return results;
}
// And finally, you would use it like:
int main()
{
TurbineFileRepository repo = TurbineFileRepository(/* your file here */);
TurbineFactory factory = TurbineFactory(&repo);
std::Vector<Turbines> my_turbines = factory.createTurbines();
// Do stuff with your newly created Turbines
}
OK, die Hauptidee hier ist es, die verschiedenen Teile des Programms voneinander zu isolieren oder zu verbergen. Ich möchte insbesondere den Kernteil des Programms, in dem sich die Domänenlogik befindet (die Turbine
Klasse, die das Problem tatsächlich modelliert und löst), von anderen Details wie dem Speicher isolieren . Zuerst definiere ich eine TurbineData
Struktur, um die Daten für Turbine
s zu speichern , die von der Außenwelt kommen. Dann deklariere ich eine TurbineRepository
abstrakte Klasse (dh eine Klasse, die nicht instanziiert werden kann und nur als übergeordnetes Element für die Vererbung verwendet wird) mit einer virtuellen Methode, die im Wesentlichen das Verhalten des "Bereitstellens von TurbineData
Strukturen von außen" beschreibt. Diese abstrakte Klasse kann auch als Schnittstelle (Beschreibung des Verhaltens) bezeichnet werden. Die TurbineFileRepository
Klasse implementiert diese Methode (und stellt somit dieses Verhalten bereit) fürFile
s. Schließlich TurbineFactory
verwendet das a TurbineRepository
, um diese TurbineData
Strukturen zu erhalten und Turbine
s zu erstellen :
TurbineFactory -> TurbineRepo -> Turbine // with TurbineData as a means of passing data.
Warum mache ich das so? Warum sollten Sie Datei-E / A vom Innenleben Ihres Programms trennen? Denn die beiden Hauptziele des Designs oder der Architektur Ihrer Programme sind die Reduzierung der Komplexität und die Isolierung von Änderungen. Um die Komplexität zu reduzieren, müssen Sie die Dinge so einfach wie möglich (aber nicht einfacher) gestalten, damit Sie die einzelnen Teile richtig und getrennt betrachten können: Wenn Sie an Turbine
s denken , sollten Sie nicht über das Format nachdenken, in dem die Dateien enthalten sind Die Turbinendaten werden geschrieben oder ob das, was File
Sie lesen, vorhanden ist oder nicht. Sie sollten über Turbine
s, Punkt nachdenken .
Das Isolieren von Änderungen bedeutet, dass Änderungen die geringstmögliche Anzahl von Stellen im Code betreffen sollten, damit die Wahrscheinlichkeit, dass Fehler auftreten (und die möglichen Bereiche, in denen sie nach dem Ändern des Codes auftreten können), auf das absolute Minimum reduziert wird. Außerdem sollten Dinge, die sich häufig ändern oder sich in Zukunft wahrscheinlich ändern, von den Dingen getrennt sein, die dies nicht sind. Wenn sich in Ihrem Fall beispielsweise das Format Turbine
ändert, in dem Daten in den Dateien gespeichert sind, sollte es keinen Grund für die Turbine
Änderung der Klasse geben, sondern nur Klassen wie TurbineFileRepository
. Der einzige Grund, der Turbine
sich ändern sollte, besteht darin, dass Sie eine komplexere Modellierung hinzugefügt oder die zugrunde liegende Physik geändert haben (was erheblich weniger wahrscheinlich ist als die Änderung des Dateiformats) oder ähnliches.
Die Details darüber, wo und wie die Daten gespeichert werden, sollten von Klassen getrennt behandelt werden, z. B. TurbineFileRepository
die keine Ahnung haben, wie sie Turbine
funktionieren oder warum die von ihnen bereitgestellten Daten benötigt werden. Diese Klassen sollten die Behandlung von E / A-Ausnahmen und all die langweiligen und unglaublich wichtigen Dinge, die passieren, wenn Ihr Programm mit der Außenwelt spricht, vollständig implementieren, aber sie sollten nicht darüber hinausgehen. Die Funktion von TurbineRepository
besteht darin, sich vor TurbineFactory
all diesen Details zu verstecken und nur einen Datenvektor bereitzustellen. Es ist auch das, was TurbineFileRepository
implementiert wird, so dass keine Details darüber bekannt sein müssen, wer es verwenden möchteTurbineData
Strukturen. Stellen Sie sich als nette mögliche Funktionsänderung vor, Sie möchten Turbinen- und Tragflächendaten in einer MySQL-Datenbank speichern. Damit dies funktioniert, müssen Sie lediglich ein implementieren TurbineDatabaseRepository
und einstecken. Mehr nicht. Cool was?
Viel Glück bei Ihrer Programmierung!