Trước hết, xin chúc mừng vì đã lập trình một bước xa hơn và tự hỏi về cách làm nó tốt hơn (và để hỏi một câu hỏi hay). Đó là một thái độ tuyệt vời và hoàn toàn cần thiết để đưa các chương trình của bạn tiến thêm một bước. Thanh danh!
Những gì bạn đang giải quyết ở đây là một vấn đề liên quan đến kiến trúc chương trình của bạn (hoặc thiết kế, tùy thuộc vào người bạn hỏi). Nó không quá nhiều về những gì nó làm, nhưng nó làm như thế nào (tức là cấu trúc chương trình của bạn thay vì chức năng của nó). Điều rất quan trọng là phải rõ ràng về điều này: bạn hoàn toàn có thể làm cho các lớp đó lấy File
các đối tượng làm đầu vào và chương trình của bạn vẫn có thể hoạt động. Nếu bạn đã tiến thêm một bước và thêm tất cả mã xử lý ngoại lệ và xử lý các trường hợp cạnh liên quan đến tệp và I / O ( cần phải cóđược thực hiện ở đâu đó) trong các lớp đó (... nhưng không phải ở đó) và chúng trở thành một hodgepodge của I / O và logic miền (logic miền có nghĩa là logic liên quan đến vấn đề thực tế mà bạn đang cố gắng giải quyết), chương trình của bạn có thể " công việc". Mục tiêu, nếu bạn có kế hoạch thực hiện điều này hơn là một việc đơn giản, một lần, nên là nó hoạt động đúng , nghĩa là bạn có thể thay đổi các phần của nó mà không ảnh hưởng đến người khác, sửa lỗi khi chúng nổi lên và hy vọng mở rộng nó mà không cần quá nhiều khó khăn khi và nếu bạn tìm thấy các tính năng mới và sử dụng các trường hợp bạn muốn thêm.
OK, bây giờ, câu trả lời. Đầu tiên: có, sử dụng Tệp làm tham số phương thức trong Turbine
lớp vi phạm SRP. Của bạn Turbine
và Airfoil
các lớp không nên biết bất cứ điều gì về các tập tin. Và, vâng, có nhiều cách tốt hơn để làm điều đó. Tôi sẽ nói với bạn thông qua một cách tôi sẽ làm trước và sau đó đi vào chi tiết hơn về lý do tại sao nó tốt hơn sau này. Hãy nhớ rằng, đây chỉ là một ví dụ (không thực sự là mã có thể biên dịch được, nhưng là một loại mã giả) và một cách có thể để làm điều đó.
// 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, vì vậy, ý tưởng chính ở đây là cô lập hoặc ẩn các phần khác nhau của chương trình với nhau. Tôi đặc biệt muốn tách biệt phần cốt lõi của chương trình, trong đó logic miền là ( Turbine
lớp, thực sự mô hình hóa và giải quyết vấn đề), từ các chi tiết khác, chẳng hạn như lưu trữ. Đầu tiên, tôi định nghĩa một TurbineData
cấu trúc để giữ dữ liệu cho Turbine
s đến từ thế giới bên ngoài. Sau đó, tôi khai báo một TurbineRepository
lớp trừu tượng (có nghĩa là một lớp không thể khởi tạo, chỉ được sử dụng làm cha mẹ để thừa kế) với một phương thức ảo, về cơ bản mô tả hành vi "cung cấp các TurbineData
cấu trúc từ thế giới bên ngoài". Lớp trừu tượng này cũng có thể được gọi là một giao diện (mô tả hành vi). Các TurbineFileRepository
dụng cụ lớp rằng phương pháp (và do đó cung cấp cho hành vi đó) choFile
S. Cuối cùng, việc TurbineFactory
sử dụng a TurbineRepository
để có được các TurbineData
cấu trúc đó và tạo Turbine
s:
TurbineFactory -> TurbineRepo -> Turbine // with TurbineData as a means of passing data.
Tại sao tôi làm theo cách này? Tại sao bạn nên tách I / O tệp khỏi hoạt động bên trong của chương trình của bạn? Bởi vì hai mục tiêu chính của thiết kế hoặc kiến trúc của các chương trình của bạn là để giảm sự phức tạp và cô lập sự thay đổi. Giảm độ phức tạp có nghĩa là làm cho mọi thứ đơn giản nhất có thể (nhưng không đơn giản hơn) để bạn có thể suy luận về các phần riêng lẻ một cách chính xác và riêng biệt: khi bạn nghĩ về Turbine
s, bạn không nên nghĩ về định dạng chứa các tệp dữ liệu tuabin được ghi, hoặc liệu File
bạn đang đọc có ở đó hay không. Bạn nên suy nghĩ về Turbine
s, thời gian.
Thay đổi cách ly có nghĩa là các thay đổi sẽ ảnh hưởng đến số lượng vị trí ít nhất có thể có trong mã, do đó khả năng xảy ra lỗi (và các khu vực có thể xảy ra sau khi bạn thay đổi mã) được giảm đến mức tối thiểu. Ngoài ra, những thứ thay đổi thường xuyên, hoặc có khả năng thay đổi trong tương lai, nên tách biệt với những thứ không có. Trong trường hợp của bạn, ví dụ, nếu định dạng mà Turbine
dữ liệu được lưu trữ trong các tệp thay đổi, sẽ không có lý do gì để Turbine
lớp thay đổi, chỉ có các lớp như thế TurbineFileRepository
. Lý do duy nhất Turbine
nên thay đổi là nếu bạn đã thêm mô hình phức tạp hơn vào nó hoặc vật lý cơ bản đã thay đổi (ít có khả năng thay đổi định dạng tệp) hoặc một cái gì đó tương tự.
Chi tiết về nơi và cách lưu trữ dữ liệu nên được xử lý riêng bởi các lớp, do TurbineFileRepository
đó, do đó, sẽ không biết về cách thức Turbine
hoạt động của chúng, hoặc thậm chí tại sao dữ liệu chúng cung cấp là cần thiết. Các lớp này hoàn toàn nên thực hiện xử lý ngoại lệ I / O và tất cả các loại nhàm chán và cực kỳ quan trọng xảy ra khi chương trình của bạn nói chuyện với thế giới bên ngoài, nhưng chúng không nên vượt quá điều đó. Chức năng của TurbineRepository
là ẩn khỏi TurbineFactory
tất cả các chi tiết đó và chỉ cung cấp cho nó một vectơ dữ liệu. Đó cũng là những gì TurbineFileRepository
thực hiện để không ai biết chi tiết về nó.TurbineData
cấu trúc. Là một thay đổi tính năng tốt có thể, hãy tưởng tượng bạn muốn lưu trữ dữ liệu tuabin và máy bay trong cơ sở dữ liệu MySQL. Để làm việc đó, tất cả những gì bạn cần làm là triển khai TurbineDatabaseRepository
và cắm nó vào. Không còn gì nữa. Thật tuyệt phải không?
Tốt nhất của may mắn với chương trình của bạn!