web-dev-qa-db-ja.com

個人データを公開せずにクラスの責任を分割する

プライベートデータを公開せずにクラスを分割する良い方法を見つけるのに苦労しています。 SRPについて私が読んだほとんどの記事は、分離された責任を引き受ける新しいクラスが、元のクラスに対して以前はプライベートであったデータにアクセスする方法を無視しているようです。

たとえば、グラフィカルコンピュータシミュレーションでロボットグリッパーを表すGripperクラスを考えます。このクラスは、グリッパーのロジック、アイテムのピックアップ、回転、別の位置への配置などを処理します。グリッパー自体をGUIに描画することもできます。

Gripperクラスを変更する理由は2つあります。グリッパーの動作に関するロジックへの変更と、グリッパーの描画方法に関する変更です。ただし、Gripperには、ロジックと描画部分の両方で使用されるプライベートデータメンバーがいくつかあります。一部の(const)ゲッターを介してこれらのメンバーを公開するだけでは、後退したように感じられます。私は実装の詳細を公開し、この新しい「インターフェース」をサポートすることに専念していますが、それはまったく間違っているようです。

だから私はこれを思いつきました:

class Renderer
{
public:

    /* Takes the data needed to draw a gripper and does so. */
    void
    DrawGripper(const Foo& foo, const Qux& qux);

    /* Additional methods to draw other things. */
};

class Gripper
{
public:

    void
    Draw(Renderer& renderer) const
    {
        renderer.DrawGripper(mFoo, mQux);
    }

private:

    Foo mFoo;
    Bar mBar;
    Qux mQux;
};

プロの:

  • 責任の分離の向上。 1行のコードで構成されるDraw関数を除いて、すべての描画コードはGripperからなくなりました。
  • Rendererは抽象的なインターフェイスであり、さまざまな実装を簡単に許可できます。
  • データはconst参照によってDrawGripperに渡すことができますが、単純なメンバー関数はすべてのメンバーに完全にアクセスできます。

短所:

  • Gripperには、まだDraw関数があり、Rendererについて知っています。

詐欺は扱いにくいと思います。結局、Gripperが存在する理由の1つは、最終的には画面に描画されるためです。そのため、Draw関数がまだあるという事実は、それほど悪くないようです。おそらくこれは、2つの悪の小さい方を選択する必要がある場合でしょうか。個人データを公開する代わりの方法は、はるかに悪いことです。

私はここで正しい軌道に乗っていますか?これは、このような場合に展開できる優れたシステムですか?問題またはより良い方法?

2
Unimportant

私はこれらの線に沿って何かに行くことに決めました:

_class GripperRenderer
{
public:

    virtual
    void Draw(const Foo& foo, const Qux& qux) = 0;
};

class Gripper
{
public:

    void
    AcceptRenderer(GripperRenderer& renderer) const
    {
        renderer.Draw(mFoo, mQux);
    }

private:

    Foo mFoo;
    Bar mBar;
    Qux mQux;
};
_

@Emerson Cardosoの回答に従ってゲッターを使用することは、まったく受け入れられません。描画の必要性がなければ、誰もビジネスを行っていない実装の詳細を公開します。たとえば、GetPosition()メソッドは、デカルト座標系を公開し、それをサポートすることに永久に結び付けます。クライアントは、本来の目的でGetPosition()メソッドを使用することができます(使用する予定ですが)。今後、内部座標系を変更する場合、GetPosition()メソッドを機能させるために変換をサポートする必要があります。これは面倒で、場合によっては不可能であり、確実に保守性を向上させることはできません。

これは有名な記事の背後にある考え方/動機です "getterとsetterの方法がなぜ悪いのか) アレン・ホルブによると私は信じています。

はい、GripperにはAcceptRenderer()メソッドがあるため、GripperRendererを認識しています。 Gripperのプライベートメンバーにアクセスする唯一の方法は、トラフ/ Gripper自体のアクセス許可があるためです。 Gripperの描画のためだけにプライベートへのアクセスを明示的に許可する方法と見なすことができます。これはfriend(描画に必要なメンバーのみが渡される)を使用するよりもきめ細かく、実装の詳細をすべての人に公開するよりもはるかに優れています。責任はまだ分割されています。

単純にデータを受け入れ、それを公開するGripperRenderer実装を記述できますか?もちろん。しかし、意図的な悪/愚かさから保護することはできません。そうすることは、適切なプログラマーにとってはコードの匂いになるはずですが、パブリックゲッターを使用すると(もしあれば)、それは完全に合理的なことです。

0
Unimportant

IMO、新しいアプローチから、RendererクラスとGripperクラスの両方がSRPを壊します。

  1. Rendererは、レンダリングされる各アイテムのすべての詳細の知識を必要とします。この問題の神のクラスになる可能性があります(次のように考えてください:このクラスには複数のソースまたは変更する理由があります);
    • 追加:このシミュレーションに追加するすべての新しいオブジェクトについて、このクラスにコードを「挿入」する必要があります(これは、「O」の原則の反対です:コードは拡張用に開いている必要がありますしかし、変更のために閉じられました);
  2. Gripperクラスは、あなたが言ったように、まだdrawメソッドを持っています。

提案

  1. SRPのみを使用しないでください。すべてのSOLID原則を使用してみましょう。たとえば、「I」(具体化ではなく、インターフェイスへのプログラム)を例に取ってみましょう);
  2. ドローアブルオブジェクト(Renderer)を表すインターフェイス(またはcppの抽象クラス)を作成します。
  3. すべての具象レンダラーを実装します(例:GripperRenderer-この具象クラスに必要なすべての特定のものは、コンストラクターに渡す必要があります。そうしないと、Drawメソッドシグネチャを標準化できません);
  4. Gripperクラスは、その動作/ロジックを処理する責任を負ってください。他のクラスからアクセスする必要があるすべての情報は、getterメソッドで公開する必要があります。
  5. シミュレーション「エンジン」でGripperをインスタンス化するときは、そのGripperRendererもインスタンス化し、レンダリング操作に必要なすべての情報をレンダリングに渡す必要があります。

サンプルソース

上記の私の提案の簡単な図を以下に示します(コーディングエラーがある場合はお詫び申し上げます)。

class Renderer
{
public:

    // Only has a draw method, according to this class' responsibility
    virtual void Draw() = 0;

};

class GripperRenderer : public Renderer
{
public:

    GripperRenderer(const Foo& foo, const Qux& qux) {
        // Receives any data, or pointer to data, needed
        // for the rendering
    }

    void Draw() {
        // draws the gripper, according to class' responsibility
    }

};

class Gripper
{
public:

    // Methods that handle only gripper logic, according to class' responsibility
    ...

    // Getters for stuff used by external objects
    Foo& getFoo();
    Qux& getQux();

private:

    Foo mFoo;
    Bar mBar;
    Qux mQux;
};


IMO、これは理解しやすく、簡単に保守できます。

これについてさらに支援が必要な場合は、おそらく、問題のより複雑で現実的な例を示す必要があります。

5
Emerson Cardoso

GripperRendererを保持しますか? Gripperが不変のデータGestureを提供し、RendererGestureを要求するようにすることをお勧めします。上記の操作は、画面の再編集のためのオブジェクトによって行われます。

Gestureをインターフェースとして公開することにより、Gripperを変更する人は、公開データが変更されないことを認識できます。

Fooをリ​​ファクタリングしてもRendererは壊れません。

また、関連しないオブジェクトBarが、Rendererで使用される可能性を削減します。

2
Louis Go

SRPについて読んで、SRPのためにクラスを分割する必要があると考え、その結果、公開したくないプライベートメンバーを公開するという問題が発生した場合は、後戻りして検討する必要があります。この:

SRPはあなたの人生をより簡単にするためにあります。 SRPをフォローすると生活が困難になる場合は、SRPを誤解している可能性が高いです。 SRPはおそらくそれらすべての中で最も誤解されているガイドラインです。そして、たとえそれが正しかったとしても、それがあなたの人生をより簡単にするのではなくより困難にするならば、それからそれをしないでください。

あなたの場合:ロボットアームの動作方法を変更すると、グリッパーの動作コードとレンダリング方法のコードの両方を変更する必要が生じる可能性が高くなります。

もちろん、OpenGLを使用してレンダリングするレンダリングコードを記述していて、Metalを使用してレンダリングするように変更したい場合があります。さて、その場合、あなたの問題は完全に別の場所にあります。レンダリングコードは、レンダリングするアイテムの説明を生成するだけで、使用するテクノロジーとは無関係である必要があります。

0
gnasher729

多くの場合、複雑な状態を含むオブジェクトには、他のオブジェクトに物事を委任するという高レベルの責任があり、その状態の知識を使用して構成します。

レンダラーオブジェクトは、グリッパーの現在の状態のグラフィック表現を作成する役割を果たし、グラバーの状態から派生した情報を使用してグラバーによって構成されます。

この他のオブジェクトがグラバーと同じモジュールの一部である場合、それらは内部状態の要素の表現のための非公開コードを共有できます。

しかし、ごく単純な場合を除いて、関連する責任を持つ2つの関連オブジェクトが状態表現全体を共有することはまれです。

レンダラーは、グリッパーによって提供される構成に関する知識があるため、可能なことのみを実行します。使用中の描画APIと互換性のある形式で幾何学的表現を作成する。

0
yeoman