En primer lugar, felicidades por llevar la programación un paso más allá y preguntarse cómo hacerlo mejor (y por hacer una buena pregunta). Es una gran actitud y absolutamente necesaria para llevar sus programas un paso más allá. ¡Prestigio!
Lo que está tratando aquí es un problema relacionado con la arquitectura de su programa (o diseño, dependiendo de a quién le pregunte). No se trata tanto de lo que hace, sino de cómo lo hace (es decir, la estructura de su programa en lugar de su funcionalidad). Es muy importante ser claro en esto: usted podría totalmente hacer esas clases tienen File
objetos como entrada, y su programa todavía se podía trabajar. Si fue un paso más allá y agregó todo el código de manejo de excepciones y se ocupó de casos extremos relacionados con archivos y E / S (que deberíanhacer en algún lugar) en esas clases (... pero no allí), y se convirtieron en una mezcolanza de E / S y lógica de dominio (la lógica de dominio significa lógica relacionada con el problema real que está tratando de resolver), su programa podría " trabajo". El objetivo, si planea hacer que esto sea algo más que simple, debe ser que funcione correctamente , lo que significa que puede cambiar partes sin afectar a otras, corregir errores a medida que surgen y, con suerte, extenderlo sin demasiado dificultad cuando y si encuentra nuevas características y casos de uso que desea agregar.
Bien, ahora, la respuesta. Primero: sí, el uso de Archivos como parámetros de método en la Turbine
clase viola el SRP. Tu Turbine
y las Airfoil
clases no deben saber nada sobre archivos. Y sí, hay mejores formas de hacerlo. Te hablaré de una forma en que lo haría primero y luego entraré en más detalles sobre por qué es mejor más tarde. Recuerde, este es solo un ejemplo (no un código realmente compilable, sino una especie de pseudocódigo) y una posible forma de hacerlo.
// 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, entonces, la idea principal aquí es aislar u ocultar las diferentes partes del programa entre sí. Especialmente quiero aislar la parte central del programa, donde está la lógica del dominio (la Turbine
clase, que realmente modela y resuelve el problema), de otros detalles, como el almacenamiento. Primero, defino una TurbineData
estructura para contener los datos de Turbine
s que provienen del mundo exterior. Luego, declaro una TurbineRepository
clase abstracta (es decir, una clase que no se puede instanciar, solo se usa como padre para la herencia) con un método virtual, que básicamente describe el comportamiento de "proporcionar TurbineData
estructuras del mundo exterior". Esta clase abstracta también se puede llamar una interfaz (una descripción del comportamiento). La TurbineFileRepository
clase implementa ese método (y por lo tanto proporciona ese comportamiento) paraFile
s. Por último, TurbineFactory
usa a TurbineRepository
para obtener esas TurbineData
estructuras y crear Turbine
s:
TurbineFactory -> TurbineRepo -> Turbine // with TurbineData as a means of passing data.
¿Por qué lo hago de esta manera? ¿Por qué debería separar las E / S de archivo del funcionamiento interno de su programa? Porque los dos objetivos principales del diseño o la arquitectura de sus programas son reducir la complejidad y aislar el cambio. Reducir la complejidad significa hacer las cosas lo más simples posible (pero no más simples) para que pueda razonar sobre las partes individuales de manera adecuada y por separado: cuando está pensando en Turbine
s, no debería haber pensado en el formato en el que se encuentran los archivos que contienen se escriben los datos de la turbina, o si el File
que está leyendo está allí o no. Deberías estar pensando en Turbine
s, punto.
Aislar el cambio significa que los cambios deberían afectar la menor cantidad posible de lugares en el código, de modo que las posibilidades de que ocurran errores (y las posibles áreas donde pueden ocurrir después de cambiar el código) se reducen al mínimo absoluto. Además, las cosas que cambian a menudo, o es probable que cambien en el futuro, deben estar separadas de las cosas que no lo son. En su caso, por ejemplo, si Turbine
cambia el formato en el que se almacenan los datos en los archivos, no debería haber ninguna razón para que la Turbine
clase cambie, solo clases como TurbineFileRepository
. La única razón por la que Turbine
debería cambiar es si le agregaste un modelado más sofisticado o si la física subyacente cambió (que es considerablemente menos probable que cambie el formato del archivo), o algo similar.
El detalle de dónde y cómo se almacenan los datos debe manejarse por separado por clases, como, por lo tanto TurbineFileRepository
, que, en consecuencia, no tienen idea de cómo Turbine
funcionan, o incluso por qué se necesitan los datos que proporcionan. Estas clases deberían implementar totalmente el manejo de excepciones de E / S y todo el tipo de cosas aburridas e increíblemente importantes que suceden cuando su programa habla con el mundo exterior, pero no deberían ir más allá de eso. La función de TurbineRepository
es esconderse de TurbineFactory
todos esos detalles y solo proporcionarle un vector de datos. También es lo que se TurbineFileRepository
implementa para que no se necesite conocer ningún detalle sobre quien quiera usarTurbineData
estructuras Como un posible cambio de características, imagine que desea almacenar datos de turbinas y perfiles en una base de datos MySQL. Para que eso funcione, todo lo que necesita hacer es implementar TurbineDatabaseRepository
ay enchufarlo. Nada más. Genial, ¿eh?
¡Mucha suerte con tu programación!