Đối tượng đọc từ tập tin, vi phạm SRP?


8

Tôi đang viết một chương trình mô phỏng vật lý trong C ++. Tôi là người mới trong OOP và C ++.

Trong chương trình của tôi, một số đối tượng cần được khởi tạo dựa trên dữ liệu từ một tệp đầu vào.

Ví dụ: một tệp đầu vào tưởng tượng:

# Wind turbine input file:
number_of_blades = 2
hub_height = 120

# Airfoil data:
airfoil1 = { chord = 2, shape = naca0012}
airfoil2 = { chord = 3, shape = naca0016}

Trong ví dụ này, giả sử tôi có lớp Turbine và lớp Airfoil. Các đối tượng máy bay cần biết hợp âm và hình dạng của chúng, và đối tượng tuabin cần biết chiều cao và số lượng lưỡi dao.

Tôi có nên làm điều này để mỗi đối tượng có thể tự xây dựng từ một tệp đầu vào không?

ví dụ:

class Turbine {
 public:
    Turbine(File input_file);  // reads input file to get the number of blades
 private:
    int num_blades_;
    double height_;
};

hoặc nó nên được thực hiện với một chức năng miễn phí:

Turbine create_turbine_from_file(File input_file)
{
    Turbine t;
    t.set_num_blades(input_file.parse_num_blades());
    t.set_height(input_file.parse_height());
    return t;
};

class Turbine {
 public:
    Turbine();

    void set_height();
    void set_num_blades();

 private:
    int num_blades_;
    double height_;
};

Nhược điểm của các phương pháp là gì? Có cách nào tốt hơn?

Câu trả lời:


5

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 Filecá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 Turbinelớp vi phạm SRP. Của bạn TurbineAirfoilcá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à ( Turbinelớ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 TurbineDatacấu trúc để giữ dữ liệu cho Turbines đến từ thế giới bên ngoài. Sau đó, tôi khai báo một TurbineRepositorylớ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 TurbineDatacấ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 TurbineFileRepositorydụng cụ lớp rằng phương pháp (và do đó cung cấp cho hành vi đó) choFileS. Cuối cùng, việc TurbineFactorysử dụng a TurbineRepositoryđể có được các TurbineDatacấu trúc đó và tạo Turbines:

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ề Turbines, 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 Filebạn đang đọc có ở đó hay không. Bạn nên suy nghĩ về Turbines, 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à Turbinedữ liệu được lưu trữ trong các tệp thay đổi, sẽ không có lý do gì để Turbinelớp thay đổi, chỉ có các lớp như thế TurbineFileRepository. Lý do duy nhất Turbinenê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 Turbinehoạ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 TurbineRepositorylà ẩn khỏi TurbineFactorytấ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ì TurbineFileRepositorythực hiện để không ai biết chi tiết về nó.TurbineDatacấ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 TurbineDatabaseRepositoryvà 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!


4

Nó thường được thực hiện như là một chức năng miễn phí. Hàm đó thường được đặt tên operator>>và lấy hai đối số: in istreamvà tham chiếu đến một Turbine(và trả về istreamcái đã được truyền cho nó). Trong một trường hợp điển hình, nó sẽ là một friendlớp, vì nó cần có khả năng điều khiển trực tiếp các phần bên trong mà (trong nhiều trường hợp) thế giới bên ngoài không nên chạm vào (trực tiếp).

class Turbine {
    // ...

    friend std::istream &operator>>(std::istream &is, Turbine &t) {
        // Simplifying a bit here, but you get the idea. 
        return is >> t.num_blades_ >> t.height_;
    }
};

Điều này không chỉ đáp ứng SRP mà còn làm cho lớp hoạt động với phần còn lại của thư viện chuẩn. Ví dụ: nếu bạn muốn đọc một tệp có đầy đủ thông số kỹ thuật của Turbines (không chỉ một), bạn có thể làm một cái gì đó như thế này:

std::ifstream in("Turbines.txt");

std::vector<Turbine> turbines { 
    std::istream_iterator<Turbine>(in),
    std::istream_iterator<Turbine>()
};

2
Điều này thực sự cảm thấy như Mô hình Kho lưu trữ là giải pháp phù hợp hơn. Điều gì nếu bạn đi từ lưu trữ tập tin để sử dụng một cơ sở dữ liệu?
Greg Burghardt

Mô hình kho lưu trữ @GregBurghardt là một ý tưởng hay nhưng nó độc quyền với giải pháp này, nó chỉ có thể xây dựng trên nó và sử dụng toán tử này trong nội bộ.
kamilk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.