Antes de tudo, parabéns por dar mais um passo à programação e pensar em como fazê-lo melhor (e por fazer uma boa pergunta). É uma ótima atitude e é absolutamente necessário levar seus programas um passo adiante. Parabéns!
Você está lidando com um problema relacionado à arquitetura do seu programa (ou design, dependendo de quem você pergunta). Não é tanto sobre o que ele faz, mas como ele faz (ou seja, a estrutura do seu programa em vez de sua funcionalidade). É muito importante ser claro sobre isso: você poderia totalmente fazer essas aulas são Fileobjetos como entrada, e seu programa ainda podia trabalhar. Se você deu um passo adiante e adicionou todo o código de tratamento de exceções e cuidou de casos extremos relacionados a arquivos e E / S (que devemser feito em algum lugar) nessas classes (... mas não lá), e elas se tornaram uma mistura de lógica de E / S e de domínio (lógica de domínio significa lógica relacionada ao problema real que você está tentando resolver), seu programa poderia " trabalhos". O objetivo, se você planeja fazer isso mais do que uma coisa simples e pontual, deve ser que funcione corretamente , o que significa que você pode alterar partes dela sem afetar os outros, corrigir os erros à medida que surgem e, esperançosamente, estendê-lo sem muito dificuldade em quando e se você encontrar novos recursos e casos de uso que deseja adicionar.
OK, agora, a resposta. Primeiro: sim, o uso de Arquivos como parâmetros de método na Turbineclasse viola o SRP. Suas classes Turbinee Airfoilnão devem saber nada sobre arquivos. E, sim, existem maneiras melhores de fazer isso. Vou falar com você sobre uma maneira de fazer isso primeiro e depois entrar em mais detalhes sobre por que é melhor depois. Lembre-se, este é apenas um exemplo (não é realmente um código compilável, mas uma espécie de pseudocódigo) e uma maneira possível de fazê-lo.
// 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, então a idéia principal aqui é isolar ou ocultar as diferentes partes do programa uma da outra. Quero especialmente isolar a parte principal do programa, onde está a lógica do domínio (a Turbineclasse, que realmente modela e resolve o problema), de outros detalhes, como armazenamento. Primeiro, defino uma TurbineDataestrutura para armazenar os dados de Turbines que vêm do mundo exterior. Então, declaro uma TurbineRepositoryclasse abstrata (significando uma classe que não pode ser instanciada, usada apenas como pai da herança) com um método virtual, que basicamente descreve o comportamento de "fornecer TurbineDataestruturas do mundo exterior". Essa classe abstrata também pode ser chamada de interface (uma descrição do comportamento). A TurbineFileRepositoryclasse implementa esse método (e, portanto, fornece esse comportamento) paraFiles. Por fim, o TurbineFactoryusa a TurbineRepositorypara obter essas TurbineDataestruturas e criar Turbines:
TurbineFactory -> TurbineRepo -> Turbine // with TurbineData as a means of passing data.
Por que estou fazendo assim? Por que você deve separar a E / S de arquivo do funcionamento interno do seu programa? Porque os dois principais objetivos do design ou arquitetura dos seus programas são reduzir a complexidade e isolar as alterações. Reduzir a complexidade significa tornar as coisas o mais simples possível (mas não mais simples), para que você possa raciocinar sobre as partes individuais de maneira adequada e separada: quando você pensa em Turbines, não deve pensar no formato em que os arquivos que contêm os dados da turbina são gravados ou se Filevocê está lendo ou não. Você deveria estar pensando em Turbines, ponto final.
Isolar a mudança significa que as mudanças devem afetar a menor quantidade possível de lugares no código, para que as chances de erros ocorram (e as possíveis áreas em que eles podem ocorrer após a alteração do código) sejam reduzidas ao mínimo absoluto. Além disso, as coisas que mudam frequentemente, ou que provavelmente mudam no futuro, devem ser separadas das coisas que não são. No seu caso, por exemplo, se o formato no qual os Turbinedados são armazenados nos arquivos mudarem, não deverá haver motivo para a Turbineclasse mudar, apenas classes como TurbineFileRepository. O único motivo Turbinepara mudar é se você adicionou modelagem mais sofisticada ou se a física subjacente foi alterada (o que é consideravelmente menos provável que a alteração do formato do arquivo) ou algo semelhante.
Os detalhes de onde e como os dados são armazenados devem ser tratados separadamente por classes, como, por exemplo TurbineFileRepository, que não têm idéia de como Turbinefunciona, ou mesmo por que os dados fornecidos são necessários. Essas classes devem implementar totalmente o tratamento de exceções de E / S, e todo o tipo de coisa chata e incrivelmente importante que acontece quando o programa fala com o mundo exterior, mas elas não devem ir além disso. A função de TurbineRepositoryé ocultar TurbineFactorytodos esses detalhes e fornecer apenas um vetor de dados. É também o que TurbineFileRepositoryimplementa, para que nenhum detalhe seja necessário para quem quiser usarTurbineDataestruturas. Como uma boa mudança de recurso possível, imagine que você queira armazenar dados de turbinas e aerofólios em um banco de dados MySQL. Para que isso funcione, tudo que você precisa fazer é implementar um TurbineDatabaseRepositorye conectá-lo. Nada mais. Legal né?
Boa sorte com sua programação!