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


8

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

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

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番目のアプローチは最初のアプローチよりも優れていると思います。しかし、私が理解していないのはどのようにですか?メンバー変数を利用しないので、最初のアプローチは悪いデザインですか?オブジェクト指向設計のどの原則に違反しますか?メンバー関数がメンバー変数を使用しない場合、そのメンバー関数は常に静的にする必要がありますか?


OOPは、たとえばクライアントをインターフェースを介して実装から分離するなどの懸念を分離することですが、実装の選択をインターフェースの使用から分離し、同時に動的に共存する可能性のある複数の実装を可能にします。@CandiedOrangeの戦略の例を使用すると、戦略の選択者と戦略の消費者を分離できます。一方、静的メソッドでは、クライアントと実装の懸念が分離されていても、クライアントと(単一の)実装の選択が密接に結び付いています。
Erik Eidt 2016

2
私ができるのは悪いデザインですIO("somefile.txt").get_mtime("someotherfile.txt")。それは一体何の意味ですか?
user253751 2017

回答:


8

メンバー関数がクラスプロパティ/メンバー変数を使用しない場合、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では完全に問題ありません。

このコードはここにあります


1
+1静力学に関して「この考え方には、間違った方向に向かうという意味の矢があります。」完璧です。私は、関数が状態を必要としない場合は常に静的である会社で働いていました。つまり、アプリの60%程度(それ以上ではないにしても)は静的でした。悪夢について話します。
カーソン

本当に?私は現在の会社で正反対のことをしています(そして私はそれを主張しました)。静的にすることができるすべてのメソッドです。
gardenhead 2017年

@gardenhead反論したい場合は、作ってください。会社全体がやっていると言っても、それが良いことだとは限りません。
candied_orange 2017年

1
@candiedorange私はそれが適切な証拠ではないことに同意しますが、私は炎上戦争に入りたくありませんでした。しかし、あなたが主張するので、ここに私の反論があります:OOPはひどいです、そして、それのより少ないものはよりよく使われました。ハッピー?
gardenhead 2017年

@gardenhead OOPはひどいので、静的は良いですか?staticを使用することなく、純粋な関数型コードを記述できます。火炎戦を探していませんか?
candied_orange 2017年

5

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

mtimeファイルのはへの呼び出しから、ほとんどのシステムでは、読み取りであるlstatか類似し、それがインスタンス化されるクラスのメンバ関数としてそれを持ってしても意味がありませんので、開いているファイルディスクリプタを必要としません。


mtimeはファイル記述子を開く必要がないので、これがメンバー変数m_file_nameから独立させたい理由です。したがって、私がその部分を理解したクラスでそれを静的にすることはより多くなります。しかし、私は最初のオプションでどのOPPの概念に違反するのかを、学術的/理論的な観点から理解したいと思います。
irsis 2016年

2
@Rahul本当に良い答えはありません。つまり、インスタンスデータへの依存を暗示することによって抽象化に違反していると言えるかもしれませんが、それが抽象化の本質ではありません。正直なところ、その理由について心配しすぎていると思います。一般的な経験則として、メンバー関数がインスタンスにアクセスする必要がない場合、それは非staticメンバーであってはならないということを理解してください。
greyfade 2016年

1
+1しかし、はい、実際にはそれが良い考えではないことはわかっていましたが、「なぜ」という理解を明確にしたいと思いました。CandidOrangeの答えはそれを説明します。
irsis

2

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

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

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


2

これを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が悪いことです-定義によって未使用の引数があります(たまたま未使用の引数ですが、オーバーライドで使用される可能性がありますがOK)。そのような引数は、関数のシグネチャ、定義、使用法を複雑にし、引数が何をすることになっているのかについて疑問を残しているコードの読者を混乱させる一方で、何も貢献しません。 junk / NULL/ emptyオブジェクトをそれに渡すことが、彼らが現在解決しようとしているバグの原因です。ネタバレ-そうではありませんが、その可能性を探るのに時間を浪費することになります。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.