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 File
objetos 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 Turbine
classe viola o SRP. Suas classes Turbine
e Airfoil
nã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 Turbine
classe, que realmente modela e resolve o problema), de outros detalhes, como armazenamento. Primeiro, defino uma TurbineData
estrutura para armazenar os dados de Turbine
s que vêm do mundo exterior. Então, declaro uma TurbineRepository
classe 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 TurbineData
estruturas do mundo exterior". Essa classe abstrata também pode ser chamada de interface (uma descrição do comportamento). A TurbineFileRepository
classe implementa esse método (e, portanto, fornece esse comportamento) paraFile
s. Por fim, o TurbineFactory
usa a TurbineRepository
para obter essas TurbineData
estruturas e criar Turbine
s:
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 Turbine
s, não deve pensar no formato em que os arquivos que contêm os dados da turbina são gravados ou se File
você está lendo ou não. Você deveria estar pensando em Turbine
s, 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 Turbine
dados são armazenados nos arquivos mudarem, não deverá haver motivo para a Turbine
classe mudar, apenas classes como TurbineFileRepository
. O único motivo Turbine
para 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 Turbine
funciona, 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 TurbineFactory
todos esses detalhes e fornecer apenas um vetor de dados. É também o que TurbineFileRepository
implementa, para que nenhum detalhe seja necessário para quem quiser usarTurbineData
estruturas. 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 TurbineDatabaseRepository
e conectá-lo. Nada mais. Legal né?
Boa sorte com sua programação!