web-dev-qa-db-ja.com

メンバー関数がクラスプロパティ/メンバー変数を使用しない場合、OOP原則に違反しますか?

私は、ファイルを開いたり、読んだり、書き込んだりできる相互作用する既存のクラスを持っています。そのためにファイルの変更を取得する必要があります新しいメソッドを追加する必要があります

これが私のクラス定義で、新しいメソッドを追加したいとします。

class IO_file
{
 std::string m_file_name;
 public:
 IO();
 IO(std::string file_name);

+ time_t get_mtime(file_name);
+       OR
+ time_t get_mtime();
};

私には2つのオプションがあります-

  1. 空のオブジェクトを作成してから、ファイルの変更時刻を取得するメソッドの引数にfile_nameを渡します。

  2. オブジェクト構築時にファイル名を渡し、メンバー変数を操作するメンバー関数を呼び出すだけです。

どちらのオプションも目的を果たします。また、2番目のアプローチは最初のアプローチよりも優れていると考えています。しかし、私が理解していないのはHowですか?メンバー変数を利用しないので、最初のアプローチは悪いデザインですか?オブジェクト指向設計のどの原則に違反しますか?メンバー関数がメンバー変数を使用しない場合、そのメンバー関数は常に静的にする必要がありますか?

8
irsis

メンバー関数がクラスプロパティ/メンバー変数を使用しない場合、OOPプリンシパルに違反しますか?

番号。

OOPは、メンバー関数がクラスプロパティまたはメンバー変数を使用するかどうかを気にしません。 OOPは、ポリモーフィズムに注意し、ハードコーディングの実装には注意しません。静的関数には用途がありますが、オブジェクトの状態に依存しないため、関数は静的であってはなりません。 OOPのせいにしないでください。そのアイデアはOOPに由来しないためです。

悪い設計はメンバー変数を利用する[しない]でしょうか?

呼び出しごとに状態を記憶する必要がない場合は、状態を使用する十分な理由はありません。

オブジェクト指向設計のどの原則に違反しますか?

なし。

メンバー関数がメンバー変数を使用しない場合、そのメンバー関数は常に静的にする必要がありますか?

いいえ。この考え方には、間違った方向に進むという意味合いの矢があります。

  • 静的関数はインスタンス状態にアクセスできません

  • 関数がインスタンス状態にアクセスする必要がない場合、関数は静的または非静的にすることができます

ここで関数を静的にするかどうかは完全にあなた次第です。しかし、そうすることで、よりグローバルな雰囲気になります。静的になる前に、関数をステートレスクラスでホストすることを検討してください。より柔軟です。


ここにOOPクラスプロパティまたはメンバー変数を使用しないメンバー関数の例があります。

メンバー関数(およびステートレスクラス)

_#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual 
                                             // function. As a consequence, Strategy 
                                             // is a so-called abstract class.
};
_


3つの異なる実装:

_class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};

class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};

class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};
_


実装の選択を保存する場所:

_class Context
{
private:
    Strategy* pStrategy;

public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }

    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};
_


使用例

_int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);

    std::cout << "\nresultA: " << resultA 
              << "\nresultB: " << resultB 
              << "\nresultC: " << resultC 
              << "\n";
}
_

出力:

_Called ConcreteStrategyAdd's execute()
Called ConcreteStrategySubstract's execute()
Called ConcreteStrategyMultiply's execute()

resultA: 7       
resultB: -1       
resultC: 12
_

そして、すべてのexecute()なしで、オブジェクトの状態を気にしません。 Strategyクラスは実際にはステートレスです。状態はContextのみです。ステートレスオブジェクトはOOPでは完全に問題ありません。

このコードが見つかりました here

8
candied_orange

get_mtimeは、ここで示したようにではなく、スタンドアロン関数またはstatic関数として、より意味をなすでしょう。

ファイルのmtimeは、ほとんどのシステムでlstatまたは類似の呼び出しから読み取られ、オープンファイル記述子を必要としないため、それを使用しても意味がありません。インスタンス化されたクラスのメンバー関数として。

5
greyfade

2番目のオプションが本能的に優れている(そしてIMO、ISより良い)と思われる理由は、最初のオプションが実際には何も表さないオブジェクトを与えるためです。

ファイル名を指定しないことにより、IO_fileクラスは実際にはたまたま具体的なファイルに似た抽象的なオブジェクトになります。メソッドを呼び出すときにファイル名を渡す場合は、代わりにメソッド全体をリフロートする純粋な関数にリファクタリングすることもできます。それを使用するためにインスタンス化する必要があるオブジェクトに関連付けておくことには、実際の利点はありません。これは単なる関数です。オブジェクトのインスタンス化のボイラープレートは、不便な追加のステップにすぎません。

一方、ファイル名を指定すると、そのオブジェクトに対して呼び出すメソッドは、Thingの特定のインスタンスに関するクエリのようになります。 OOあなたのオブジェクトには実際の意味があり、それゆえに実用性があるためです。

2
moberemk

これをCに翻訳してみましょう。最初に私たちのクラス-構造体です。

struct IO_file {
    char* m_file_name;
};

スニペットを簡単にするために、コンストラクター関数を定義しましょう(今のところメモリリークは無視しています)。

struct IO_file* IO_file(char* file_name) {
    struct IO_file* obj = malloc(sizeof(struct IO_file));
    obj.m_file_name = file_name;
    return obj;
}

オプション#2は次のようになります。

time_t get_mtime(struct IO_file*);

次のように使用します:

time_t mtime = get_mtime(IO_file("some-file"));

オプション#1はどうですか?まあ、それはこのように見えます:

time_t get_mtime(struct IO_file* this, char* file_name);

そしてそれはどのように使用されますか?基本的に、あなたは最初のパラメータとしてジャンクを渡すように求めています:

time_t mtime = get_mtime((struct IO_file*)1245151325, "some-file");

あまり意味がありませんね。

C++のオブジェクトシステムはそれを非表示にしますが、オブジェクトはメソッドの引数(thisという名前の暗黙のポインター引数)でもあります。これがオプション1の悪い点です-定義によって未使用の引数があります(たまたま未使用の引数ですが、オーバーライドで使用される可能性がありますが問題ありません)。このような引数は、関数のシグネチャ、定義、使用法を複雑にし、引数が何をすることになっているのか迷っているコードの読者を混乱させ、何が原因であるか、そしてあなたがそうであるかどうかにかかわらず、ジャンクを渡しても大丈夫なのかjunk/NULL/emptyオブジェクトをそれに渡すことは、彼らが現在解決しようとしているバグの原因です。スポイラー-そうではありませんが、彼らはその可能性を探るのに時間を無駄にしています。

ジャンク引数が暗黙的であるという事実は、影響を軽減する可能性がありますが、それでも存在し、メソッドstaticを作成することで簡単に回避できます。

2
Idan Arye