ファイルからのオブジェクトの読み取り、SRPの違反?


8

C ++で物理シミュレーションプログラムを書いています。私はOOPとC ++の初心者です。

私のプログラムでは、入力ファイルのデータに基づいていくつかのオブジェクトを初期化する必要があります。

たとえば、架空の入力ファイル:

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

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

この例では、TurbineクラスとAirfoilクラスがあるとします。翼オブジェクトは翼弦と形状を知る必要があり、タービンオブジェクトは翼の高さと数を知る必要があります。

各オブジェクトが入力ファイルから自分自身を構築できるように、これを行う必要がありますか?

例えば:

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

それとも無料の関数で行う必要があります:

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_;
};

各方法の長所と短所は何ですか?もっと良い方法はありますか?

回答:


5

まず第一に、プログラミングをさらに一歩進めて、それをよりよくする方法について疑問に思っている(そして良い質問をする)おめでとうございます。それは素晴らしい態度であり、プログラムをさらに一歩進めることが絶対に必要です。賞賛!

ここで扱っているのは、プログラムのアーキテクチャー(または、誰に尋ねるかによっては設計)に関連する問題です。それはそれが何をするではなく、どのようにそれを行うか(つまり、機能ではなくプログラムの構造)についてです。それはこれについて明確にすることが非常に重要です:あなたは可能性があり、完全にこれらのクラスを取る作るFile入力などのオブジェクトを、そしてあなたのプログラムはまだ仕事ができます。さらに一歩進んですべての例外処理コードを追加し、ファイルとI / O(これどこかで行われる)これらのクラス(...は存在しない)で、I / Oとドメインロジック(ドメインロジックは、解決しようとしている実際の問題に関連するロジックを意味する)の寄せ集めになり、プログラムは "作業"。これを単純な1回限りのものにすることを計画している場合の目標は、それが適切に機能することです。つまり、他の人に影響を与えることなくその一部を変更し、表面に出たときにバグを修正し、うまくいけばあまり多くせずに拡張することができます。追加したい新機能やユースケースが見つかった場合、その困難さ。

さて、今、答え。まず、はいTurbine。クラスのメソッドパラメータとしてファイルを使用すると、SRPに違反します。あなたTurbineAirfoilクラスファイルについては何も知らないはず。そして、はい、それを行うにはより良い方法があります。最初にそれを行う方法の1つを説明した後、それが後で改善される理由について詳しく説明します。これは単なる例であり(実際にはコンパイル可能なコードではなく、一種の疑似コード)、それを行うための1つの可能な方法です。

// 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、ですから、ここでの主なアイデアは、プログラムのさまざまな部分を互いに分離または隠すことです。特に、ドメインロジックがあるプログラムのコア部分(Turbine実際に問題をモデル化して解決するクラス)を、ストレージなどの他の詳細から分離したいと思います。まず、外界からのs TurbineDataのデータを保持する構造を定義しますTurbine。次に、TurbineRepository抽象クラス(インスタンス化できないクラスで、継承の親としてのみ使用されるクラス)を仮想メソッドで宣言します。基本的には、「TurbineData外部から構造を提供する」動作を記述します。この抽象クラスは、インターフェース(動作の説明)とも呼ばれます。TurbineFileRepositoryこの方法は、(したがって、その動作を提供する)ことクラスが実装するためのFiles。最後に、TurbineFactoryはa TurbineRepositoryを使用してこれらのTurbineData構造を取得し、Turbines を作成します。

TurbineFactory -> TurbineRepo -> Turbine // with TurbineData as a means of passing data.

なぜ私はこのようにそれをしているのですか?プログラムの内部動作からファイルI / Oを分離する必要があるのはなぜですか?プログラムの設計またはアーキテクチャの2つの主な目標は、複雑さを軽減し、変更を分離することです。複雑さを軽減するということは、個々の部分について適切かつ個別に推論できるように、物事を可能な限り単純にする(ただし単純ではない)ことを意味します。sについて考えるときTurbineは、タービンデータが書き込まれるか、またはFileあなたが読んでいるものがあるかどうか。Turbines、期間について考える必要があります。

変更を分離するとは、変更がコード内の最小限の場所に影響を与えるため、バグが発生する可能性(およびコードを変更した後に発生する可能性のある領域)が最小限に抑えられることを意味します。また、頻繁に変更されるもの、または将来変更される可能性のあるものは、変更されないものから分離する必要があります。たとえば、Turbineデータがファイルに格納される形式が変更された場合、Turbineクラスが変更される理由はなく、のようなクラスのみTurbineFileRepositoryです。Turbine変更する必要がある唯一の理由は、より洗練されたモデリングを追加したか、基礎となる物理学が変更されたか(ファイル形式の変更よりもかなり可能性が低い)、または類似したものである場合です。

データが格納される場所と方法の詳細は、などのクラスで個別に処理する必要があります。TurbineFileRepositoryその結果、はどのように機能するの、さらにはデータが提供するデータが必要な理由すらわかりません。これらのクラスは完全にI / O例外処理実装する必要があります。また、プログラムが外界と対話するときに発生するあらゆる種類の退屈で非常に重要なものを実装する必要がありますが、それを超えてはなりません。の機能は、これらすべての詳細から隠し、データのベクトルのみを提供することです。それは実装するものでもあるので、それを使用したい人には誰もそれを知る必要はありません。TurbineTurbineRepositoryTurbineFactoryTurbineFileRepositoryTurbineData構造。考えられる素晴らしい機能変更として、タービンと翼のデータをMySQLデータベースに保存したいとします。それが機能するために必要なことは、を実装しTurbineDatabaseRepositoryてプラグインすることだけです。かっこいい?

あなたのプログラミングで頑張ってください!


4

通常、フリー関数として実装する必要があります。その関数は通常名前が付けられoperator>>、2つの引数を取る必要があります:in istreamとaへの参照Turbine(そして、istream渡されたを返します)。典型的なケースでfriendは、クラスの1つになります。これは、(多くの場合)外部の世界が(直接)触れてはならない内部を直接操作できる必要があるためです。

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_;
    }
};

これにより、SRPが満たされるだけでなく、クラスが他の標準ライブラリでも機能するようになります。たとえば、(1つだけではなく)タービンの仕様が満載のファイルを読みたい場合は、次のようにします。

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

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

2
これは本当に、リポジトリパターンがより適切なソリューションであると感じています。ファイルストレージからデータベースを使用する場合はどうなりますか?
Greg Burghardt 2016年

@GregBurghardtリポジトリパターンは良いアイデアですが、これはこのソリューションに限定されており、その上に構築してこの演算子を内部で使用するだけです。
kamilk 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.