質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.00%

クラス内のメンバのまとめ方

受付中

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,585

hiramaru

score 46

お世話になっています。

クラスの分割、メンバの配置についてご意見を伺いたく投稿します。

・メンバ変数はまとめて宣言すべきか(統合クラス1)
・メンバは部品ごとにわけるべきか(統合クラス2)
・メンバは扱うオブジェクトごとに分けるべきか(統合クラス3)
・子オブジェクトのメンバを親オブジェクトで再定義するのは冗長なので子オブジェクトへの参照を公開すべきか(統合クラス4)

などのさまざまなパターン、自分ならばこうするといったご意見をいただければ幸いです。

どうぞよろしくお願いします。

// 値を保持するクラス
public class ComponentA
{
    public int intValue;
    public string stringValue;
}

// クラスAの値を表示するクラス
public class ComponentB
{
    readonly ComponentA componentA;

    public ComponentB( ComponentA componentA ) { this.componentA = componentA; }

    public void ShowIntValue() { Console.WriteLine( string.Format( "Parent Int Value is {0}", componentA.intValue ) ); }
    public void ShowStringValue() { Console.WriteLine( string.Format( "Parent String Value is {0}", componentA.stringValue ) ); }
}

// クラスAの値を更新するクラス
public class ComponentC
{
    readonly ComponentA componentA;

    public ComponentC( ComponentA componentA ) { this.componentA = componentA; }

    public void UpdateIntValue() { componentA.intValue = 256; }
    public void UpdateStringValue() { componentA.stringValue = "text"; }
}

// 統合クラス パターン1
public class IntegratedClass1
{
    readonly ComponentA componentA;
    readonly ComponentB componentB;
    readonly ComponentC componentC;

    public IntegratedClass1()
    {
        componentA = new ComponentA();
        componentB = new ComponentB( componentA );
        componentC = new ComponentC( componentA );
    }

    // ComponentA
    public int intValue { get { return componentA.intValue; } }
    public string stringValue { get { return componentA.stringValue; } }

    // ComponentB
    public void ShowIntValue() { componentB.ShowIntValue(); }
    public void ShowStringValue() { componentB.ShowStringValue(); }

    // ComponentC
    public void UpdateIntValue() { componentC.UpdateIntValue(); }
    public void UpdateStringValue() { componentC.UpdateStringValue(); }
}

// 統合クラス パターン2
public class IntegratedClass2
{
    public IntegratedClass2()
    {
        componentA = new ComponentA();
        componentB = new ComponentB( componentA );
        componentC = new ComponentC( componentA );
    }

    // ComponentA
    readonly ComponentA componentA;
    public int intValue { get { return componentA.intValue; } }
    public string stringValue { get { return componentA.stringValue; } }

    // ComponentB
    readonly ComponentB componentB;
    public void ShowIntValue() { componentB.ShowIntValue(); }
    public void ShowStringValue() { componentB.ShowStringValue(); }

    // ComponentC
    readonly ComponentC componentC;
    public void UpdateIntValue() { componentC.UpdateIntValue(); }
    public void UpdateStringValue() { componentC.UpdateStringValue(); }
}

// 統合クラス パターン3
public class IntegratedClass3
{
    public IntegratedClass3()
    {
        componentA = new ComponentA();
        componentB = new ComponentB( componentA );
        componentC = new ComponentC( componentA );
    }

    // IntValue
    public int intValue { get { return componentA.intValue; } }
    public void ShowIntValue() { componentB.ShowIntValue(); }
    public void UpdateIntValue() { componentC.UpdateIntValue(); }

    // StringValue
    public string stringValue { get { return componentA.stringValue; } }
    public void ShowStringValue() { componentB.ShowStringValue(); }
    public void UpdateStringValue() { componentC.UpdateStringValue(); }

    // Components
    readonly ComponentA componentA;
    readonly ComponentB componentB;
    readonly ComponentC componentC;
}

// 統合クラス パターン4
public class IntegratedClass4
{
    public IntegratedClass4()
    {
        componentA = new ComponentA();
        componentB = new ComponentB( componentA );
        componentC = new ComponentC( componentA );
    }

    // Components
    public readonly ComponentA componentA;
    public readonly ComponentB componentB;
    public readonly ComponentC componentC;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

0

こんにちは。

う~ん、ComponentA, B, Cのように分割すると折角のクラスの良さを享受できませんね。
なので、そのように分割すること自体が私はほぼないです。(たぶん99.9%くらい?)
ですので、パターン3は私の選択肢には入らないです。
パターン4はそもそもクラス化する意味がないので、namespaceを使います。

バターン1と2に近い悩みはあります。メンバ変数とそれを主に操作する変数を近くに記述するのか、メンバ変数をまとめて記述するのかについて、結構悩ましいです。
関係がより深いメンバ変数とメンバ関数が近くにあった方が追いかけやすいです。しかし、どんなメンバー変数を持つのか纏めて見たい時もあります。この両方を同時に満たせる案があるとよいのですが...
結局、この点については、方針を持っていませんが、現在はまとめて記述する方に気持ちは偏っています。

メンバ変数をまとめると見通しが悪くなるような大きなクラスはクラス分割するべきなのかも知れないからです。まだ確信はないです。統合的なクラスは、本質的に密接な関係にある機能をまとめたものですから、下手に分割するとクラス間I/Fが増えて、却って見通しが悪くなります。常にそうならないように分割できるのか?トライしている最中です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/07 02:21

    ご回答ありがとうございます。

    たしかにこのクラス分割の場合、データ、入力、出力を分けただけであまり良い例ではないかもしれませんね…難しいです。

    実際に統合しているのはたとえば、
    ・座標を保持するクラス
    ・座標の移動を非同期で行うクラス
    ・座標の情報をビューに反映するクラス
    などです。

    パターン1と2は常に悩みます。
    統一したほうがよいとは思うのですが、クラスによってどちらの記述に寄るか変わってくる気がします。
    たしかにまとめて見通しが悪くなるということは、メンバ変数の粒度がばらばらだということなのかもしれません。

    パターン3は記述としては変更箇所がばらばらに配置されてしまうので危険だと思います。
    しかしこのようにグループ分けして確認したい場合も多々あるので悩みます。
    私が知らないだけかもしれませんが、VisualStudioの機能でソースコードをリアルタイムで表や図にしてくれる機能があったりしないでしょうか……。

    個人的にはパターン4も統合クラスの記述が減るので統合クラスのインスタンスを大量に生成する場合などは選択肢としてありかなと思っています。
    オブジェクト.座標コンポーネント.x
    オブジェクト.移動コンポーネント.目標地点に移動( 目標地点, 時間 )
    などと書けるためです。
    ただ、統合クラスには公開したいが統合クラスを扱うクラスには公開したくないコンポーネントのメンバが公開されてしまうのは危険だとも思います。
    それでも公開範囲の設定のためだけに
    public void 命令() { コンポーネント.命令(); }
    などと書くのは冗長だなと感じます。
    言語仕様として宣言だけで
    public through コンポーネント.命令;
    などと書ければ便利だといつも思うのですが、これはメタプログラミングの分野でしょうか…。

    試行錯誤中のご意見もいただきありがとうございます。
    考える方向性が増えてたいへん参考になります。

    キャンセル

  • 2016/02/07 10:32 編集

    こんにちは。

    > 実際に統合しているのはたとえば、
    > ・座標を保持するクラス
    > ・座標の移動を非同期で行うクラス
    > ・座標の情報をビューに反映するクラス
    > などです。

    なるほど。汎用的なクラスとそれを特定の目的で操作するクラスなのですね。
    確かに、おっしゃるような構造になることがありますね。(0.1%よりは多そうです。統合クラス的なクラス1つに付き、従属するクラスは数10個あり、一部の統合クラスがそのような構造を持つので、たぶん全クラスの1%くらい?)

    その場合、私はComponentAのインスタンスは、統合クラス内のグローバル変数的な位置づけとなり、統合クラスが持つ各メンバへ配布します。なので、統合クラスのコンストラクタ付近に置いて、コンストラクタにて配布してます。

    なお、ComponentB, Cのメンバ関数を単純に中継して統合クラスから公開することは少ないです。結果として統合クラスが何らかの加工して公開することが多いですので。
    でも、ComponentB, Cの機能の一部をそのまま公開すれば良い時も稀にあり、手を抜いてComponentB, Cをインスタンス毎全部公開することもあります。
    ただし、内部に閉じたクラス限定です。統合クラスを多くの人が使う場合は、不用意に公開するのはやはり避けます。後で自分が痛い目にあいますから。

    キャンセル

0

私の場合は、どのようなクラスにすべきかの前にどのように利用したいかを考えます。

統合クラスを作る意味がわからないです。

統合クラスを利用するクラスー>統合クラスー>コンポーネントBCー>コンポーネントA

というのは、どのような利点があるのでしょうか?

私の場合は

呼び出し側->コンポーネントA


と簡潔にします。その上で下記の様なコードにします。

public class Component
{
    private int i;
    private string s;
    public Component(int i, string s){this.i = i; this.s = s;} 
    public int Int{
        get{return i;}
        set{i = value;}
    } 
    public int String{
        get{return s;}
        set{s = value;}
    } 
}

どうして1つのクラスにまとめた方が良いかというと、例えばComponentクラスと非同期処理に対応される場合、もともとのComponetCクラスで更新中にComponentBクラスでが呼び出された時変更前の値が表示されるか、変更後の値が表示されるかわかりません。

それを避けるためにはComponetCクラスで更新中はComponentAをロックする必要があります。ところがComponentAクラスの変数はpublicになっているため、スレッドセーフにはできません。そのため、ComponetAとComponentCは統合され、更新中の表示はロック出来るようにしておく必要があります。

これだけでなく実際の更新処理で様々な整合性を考える場合が多く、整合性が必要な範囲で、値を隠蔽化して更新する際への注意をクラス内に閉じ込めておく必要があると私は考えます。

値が隠蔽化されているため、直接変更はできなくなりましたが、現在の値を知る必要がある場合もあるでしょうしその利用のされ方はComponentAが管理すべきではありません。ということで、ConponentBは標準出力に値を返せるだけでなく、呼び出し元でどのような利用の仕方も許すべきと考え、統合されたComponentACに統合すべきだと考えました。

ただし、これはComponentAが扱う変数が2つであるためで、変数がもっと増えてくれば、統合されたConponetクラスを分割することを検討します。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.00%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る