2

カプセル化と「言うな、聞くな」という原則を適切に使用する場合、オブジェクトから情報を求める理由はないはずです。ただし、クラス外の関数を指すメンバー変数を持つオブジェクトがある状況に遭遇しました (この設計自体がひどい場合はお知らせください)。

アプリケーションのある時点で、オブジェクトが関数を呼び出す必要があり、関数はオブジェクトのステータスに基づいて動作する必要があります。

クラスの例を次に示します。

typedef void(*fptr)(Foo*);
class Foo {
    public:
        Foo(string name, fptr function);
        void activate()
        {
            m_function(this);
        }
    private:
        string m_name;
        fptr m_function;
};

これがクラスです。開発者はクラスを次のように使用できます。

void print(Foo *sender)
{
    cout << "Print works!" << endl;
}

int main(int argc, char **argv)
{
    Foo foo("My foo", &print);
    foo.activate();
    // output: "Print works!"
}

これで問題なく動作しますが、送信者の名前を印刷したい場合はどうすればよいでしょうか? すべての関数は、他の開発者によってクラスの外部で定義されているため、プライベート変数にアクセスする方法はありません。では、キーワードをC#使用して既存のクラスにメソッドを追加できます。partialただし、これは不可能C++です。

カプセル化を無視して、セッターとゲッター、nameおよび将来関数で必要になる可能性のあるその他すべてのプロパティを作成できます。これはかなりひどい解決策です。関数はオブジェクトに対して何でもできるので、基本的にクラスにあるすべてのものに対してセッターとゲッターを作成する必要があります。カプセル化の理由に加えて、必要なときに無視する場合はどうすればよいですか?

他の解決策は、必要なプロパティを内部に保持する構造体です。

struct FooBar {
    string name;
};

typedef void(*fptr)(FooBar);

void Foo::activate()
{
    FooBar fb;
    fb.name = m_name;
    m_function(fb);
}

しかし、これはカプセル化を使用しないことと大差ありませんし、あまり良い解決策にも思えません。この問題に対する最善のアプローチは何でしょうか?

4

6 に答える 6

1

率直に言って、C++ アクセス制御はいくつかのユース ケースを念頭に置いて設計されており、通常は使用可能ですが、すべてをカバーするとは主張していません。プライベートとフレンドだけでは状況を解決できず、任意の関数が内部にアクセスできるようにする必要がある場合は、それらを公開して先に進むのが最善の方法です。

セッターはあなたを確実に前進させるわけではなく、複雑さを増すだけです。データが有効である場合は、そうではないふりをしてその事実を隠蔽しようとしないでください。

根本的な原因を探してください。一体なぜ、部外者があなたのメンバーを欲しがり、それを再編成するのか.

于 2013-06-05T12:27:34.493 に答える
1

外部から見ると、プライベート変数は存在しないため、開発者はおそらくそれらを印刷することはできません。

必要に応じて、クラス メンバー (または、内容を返すクラス内のクエリ) をパブリックにするか、関数をクラスのメンバーにするか、特定のケースでは何らかのfriendメカニズムを使用する必要があります。

要約すると、カプセル化を破ろうとしないでください。代わりに、カプセル化の背後にある抽象化を再考し、必要に応じて、クラスが設計されたときに有用であると予測されていなかったクラスのプロパティの新しいクエリを作成します。 .

于 2013-06-05T12:20:20.917 に答える
1

activate()抽象メソッドを作成し、クラスのすべてのプロパティを保護します。また、fptr次の必要はありません。

class Foo {
public:
    Foo(string name);
    virtual void activate() = 0;
protected:
    string m_name;
};

誰かがあなたのクラスを使用したい場合、彼は自分のクラスを継承するだけです:

class MyFoo : public Foo {
public:
    MyFoo(string name);
    virtual void activate()
    {
        cout << m_name << " says: Hello World!" << endl;
    }
};

int main(int argc, char **argv)
{
    MyFoo foo("My foo");
    foo.activate();
    // output: "My Foo says: Hello World!"
}

また、さまざまな機能を持つさまざまな が必要な場合Fooは、複数の関数を宣言する代わりに、複数のクラスを継承するだけです。


編集:異なるインスタンスごとに新しいクラスを継承する代わりにFoo、すべての異なるメソッドを使用して、それらすべてに対して 1 つのクラスを継承できます。activate に残されているのは、呼び出すメソッドを決定することだけです。これに使用enumします:

enum MyFooFunction {
    printName,
    printHello
};

class MyFoo : public Foo {
public:
    MyFoo(string name, MyFooFunction function);
    void printName() { cout << m_name << endl; }
    void printHello() { cout << "Hello!" << endl; }
    virtual void activate()
    {
        switch(m_function) {
        case printName:
            printName();
            break;
        case printHello:
            printHello();
            break;
        }
    }
protected:
    MyFooFunction m_function;
};
于 2013-06-05T12:24:42.547 に答える
1

const string &関数が文字列を認識できるようにする必要がある場合は、関数のパラメーターの型を に変更することをお勧めしますが、外部の世界の残りの部分はそれを認識しません。std::function<void(const string &)>また、関数型の代わりに使用することを検討することもできます。これには 2 つの基本的な利点があります。クロージャー (ラムダとも呼ばれます) をコンストラクターに渡すことができ、より簡単に読み取ることができます。編集されたコードは次のようになります。

class Foo {
    public:
        template <typename F>
        Foo(string name, F && function) 
            : m_name    (std::move(name))
            , m_function(std::forward<F>(function))
        {
        }

        void activate()
        {
            m_function(m_name);
        }
    private:
        string m_name;
        std::function<void(const string &)> m_function;
};

クライアントコードは次のようになります

int main(int argc, char **argv)
{
    Foo foo("My foo", [](const string & s){ cout << s << endl; });
    foo.activate();
    // output: "My foo"
}

クライアントが追加の関数を定義する必要はなく、単純に「インライン」で実行できることがわかります。

于 2013-06-05T12:33:43.503 に答える