長い文章で申し訳ありませんが、
設計(クラス分担の考え方)について教えていただければ幸いです。
ファイルA
ファイルB
ファイルC
を読み込み、それぞれに対して、
処理1
処理2
処理3
をした結果をクロス集計のように出力したいと思っています。
具体的な出力結果としては、
・ファイルAに対して処理1,2,3を行った結果を一覧出力。
(ファイルB,ファイルCも同じく)
・処理1をファイルA,B,Cへ行った結果を比較一覧出力。
(処理2,処理3も同じく)
と両方向から集計する感じです。
このとき、
・データの読み書きを担当するクラス
・データに処理を加えるクラス
・結果を出力するクラス
を作って進めようと思ったのですが、考え方は合っていますでしょうか?
悩んでいるのは、
「データの読み書きするクラス」でデータを配列に準備した場合、
その配列を「データに処理を加えるクラス」で使うには
引数で渡すことになると思うのですが、それが正しいのかどうかです。
特に、渡すべきデータ(配列)が多岐に渡っている場合は
引数で渡すのが正しいのか・・・
(引数は1~2つが妥当という意見とはかけ離れるので・・・)
もちろんクラスごと引数で渡してしまえば記述上は1つで良いのかもしれませんが、
こういう思考になること自体がクラスの役割分担などで
何か間違っているような気がして質問させていただきました。
オブジェクト指向(?)に慣れていない思考だと、
どのクラスからもアクセスできるグローバルなデータ用の配列を準備しておいて、
それぞれのクラスからアクセスすれば簡単なようにも思えます。
ただ、その方法だと仕様を拡張する時、
グローバルな配列をどんどん増やさなければいけない気もするので、
それはそれで何か違うような気がしています。
以前プログラムしていた言語が関数のスコープが緩く、
(下位の)呼ばれた関数が(上位の)呼んだ関数の変数はそのまま使えていたので、
どうして良いか分かっていないのもあります。
抽象的な質問で申し訳ありませんが、
アドバイスいただければ幸いです。
ご不明な点がありましたら補足しますので、
遠慮無くご指摘いただければ幸いです。
よろしくお願い致します。
※最後にプログラム的な表記も併記させてください。
c#
1Class データ読み書き 2{ 3 public string d_data = new string [1000]; //データ用配列 (実際は十数個) 4 5 public void データ読み込み (string ファイル名) 6 { 7 StreamReader sr = new StreamReader(ファイル名); 8 9 while (sr.Peek() > -1) 10 { 11 d_data[i] = sr.Readline(); // データ用配列にファイル内容を読み込む 12 i++; 13 } 14 } 15} 16 17 18Class データ処理 19{ 20 public void 処理1 (データ元) 21 { 22 処理1; 23 return; 24 } 25 26 public void 処理2 (データ元) 27 { 28 処理2; 29 return; 30 } 31 32 public void 処理3 (データ元) 33 { 34 処理3; 35 return; 36 } 37} 38 39void main 40{ 41 42 データ読み書き d1 = new データ読み書き; 43 44 d1.データ読み込み(ファイルA); // データ用配列に読み込む 45 46 データ処理 d2 = new データ処理; 47 48 d2.処理1(d1); // ←ここでd1の配列データを渡すために、 49 d2.処理2(d1); // d1(クラス)を引数で渡すことになる設計は正しいのか? 50 d2.処理3(d1); // データ処理のクラス内では、d1.d_data とアクセスしています。 51 52 ... 53 54} 55
ベストアンサーを付ける形のようなので、
tkow様に付けさせていただきましたが、
どの方の回答も非常に参考になります。
概略を書かせていただきますと、
unau様は 大きな枠組みでのクラス設計や実装
tkow様は クラス設計に加え、コンテキスト処理を含めたスマートな記述法
tkturbo様は クラスのインスタンスにおける static の利用やメモリの節約法
などが勉強できますので、後からこの質問を参照する方は、
概要を参考に回答を見ていただけると良いかもしれません。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答3件
0
いくつか書きますが、C# は使ったこともないし、実行環境もないので、その点、ご了承くださいませ。
「クラスごと(インスタンスごと、のことかと思います)引数で渡してしまえば~役割分担などで何か間違っているような」とありますが、たとえ情報量の大きなインスタンスだとしても、それが論理的に考えて一つの情報のまとまりとなっているのであれば、そのインスタンスごと引数でやりとりするのはオブジェクト指向的に間違った考えだとは思いません。
クラス設計についてはいろいろな考え方があり、規模感や処理速度などの要求から一概には言えないのですが、他に制約がないのであれば、データを中心に考え、それにしかるべき処理をメソッドとしてつけたものをクラスとしてまとめるのがいいかな、と個人的には思っています。tkow さんが回答の最後に書かれているような設計方針ですね。
「データ読み書き」クラスがデータを保持しているのは、私的にはちょっと気持ちが悪いです。
名前の問題だけかもしれませんが、「データ」クラスがあって、それに「読み書き」メソッドがついている方が自然かな、と。あと、これは occhan さんが teratail に質問としてあげる際に元々の名前をわざと抽象的にしたのかもしれませんが、抽象的な「データ」クラスという名前よりは、たとえば、ファイル A が店舗 A の売り上げデータ、ファイル B が店舗 B の売り上げデータ、で日付ごとの売り上げデータが入っている、というようなものならば「売り上げ」クラスみたいな名前の方がいいのかな、と。
「読み書き」については、ストレージをファイルシステム (CSV) から RDBMS に替えるご予定があるとのことなので、メソッドにするのではなく、「ストレージ」クラスを基底クラスとして、「CSV」クラスと「SQL Server」クラスを派生クラスにするのはありだろうな、と思います(規模的にそこまでやらなくていいケースも多いですが)。「売り上げ」データを読み込む部分では「ストレージ」クラスのインスタンスを使うようにすれば、CSV から SQL Server に替えたときもその部分のコードは変更する必要がなくなりますので。
あと、処理1, 処理2, 処理3 については、これらの「処理機」or「タスク」or「コマンド」をクラス化するクラス設計もあります(これも、ここまでやらなくていいケースが少なくないです)。
つまり、「処理」基底クラスにたとえば exec
というメソッドを定義しておき、「処理1」クラス、「処理2」クラス、「処理3」クラスをそれぞれ「処理」クラスの派生クラスにして、実際の処理を exec
メソッドをオーバライドする形で作ります。
あとは、具体的なストレージの種類や設定 (SQL Server であれば接続パラメータとか) を渡すとか設定ファイルや環境変数を読み込んで具体的なストレージインスタンスを生成する関数 (下の例では getStorage
) や、各売り上げデータに施すべき処理の「タスク」をリストとして返す関数 (下の例では getTasks
) を作ったりして、次のようにするとか。
c#
1static void main() { 2 ストレージ storage = getStorage( /* ここになんか設定とか */ ); 3 List<処理> tasks = getTasks( /* ここになんか設定とか */ ); 4 List<売り上げ> saleList = getSaleList( /* なんか */ ); 5 for (saleList のすべての要素 sale に対して) { 6 sale.load(storage); 7 for (tasks のすべての要素 task に対して) { 8 task.exec(sale); 9 } 10 } 11}
済みません、ちょっと力尽きて最後雑になりました。
おそらくやりたい規模からしたらリッチすぎるやりすぎなクラス構成で、パフォーマンス面ではよくないものですが、クラス設計の考え方をいろいろお知りになりたいご様子でしたので口出しいたしました。
投稿2016/04/17 10:14
総合スコア2468
0
ベストアンサー
class設計の一つの基準はそのインスタンスを複数生成する必要があるかないかです。
一つ定義されていればデータの内容を変える必要がないものはクラスにする必要性はほとんどありません。
例外としては,staticメソッドのみをグループ化するライブラリなどを作る場合,static Classで実装することがあります。このとき,インスタンスを作成したり,定数以外の内部変数を処理するような実装にしてはいけません。なので,定数は,readonly属性を作るようになると思います。
質問者様がデータ処理のクラスを他のコードで使い回す可能性があるのなら,処理のクラス化はokです。
C#では,Listを使えるので,いくらデータが入るかわからないものは配列ではなくListにしましょう。
arrayとListは相互に変換できるので,Listの方が無難です。
また,コレクションの要素一つ一つにやる処理に対して,引数を配列にするのはやめましょう。なぜなら,Listやarrayには,関数を引数に取ると,要素の一つ一つにその関数を適用して要素を返す関数が存在したり,処理を並列かするのが簡単に出来るからです。
個人的にはこういう流れで書きます。実行環境なしで書いているので,文法のミスなどがあったら調べて直してください。また最適とまではいかない可能性があるので参考までにしてください。namespaceなどは省略しています。
C#
1Class データ読み書き 2{ 3 public List<string> d_data {get;,set;}; //データ用list 4 public データ読み書き(string[] input){ 5 d_data = new List<string>(); 6 foreach(var file_name in input){ 7 データ読み込み(file_name) 8 } 9 } 10 public void データ読み込み (string ファイル名) 11 { 12 (using StreamReader sr = new StreamReader(ファイル名)){ 13 string temp=""; 14//仕様だとファイルごとの処理と書いてあったのでファイルごとにしました。行ごとにしたければ二次元Listになおしてください。 15 while (!sr.EndOfStream) 16 { 17 temp += sr.Readline(); // データ用配列にファイル内容を読み込 18 } 19 this.d_data.push(temp); 20 }; 21 } 22} 23 24 25Class データ処理 26{ 27 public void 処理1 (データ元) 28 { 29 処理1; 30 return データ元; 31 } 32 33 public void 処理2 (データ元) 34 { 35 処理2; 36 return データ元; 37 } 38 39 public void 処理3 (データ元) 40 { 41 処理3; 42 return データ元; 43 } 44} 45 46 static void main(string args[])//argsでファイル名指定 47{ 48 using static データ処理; 49 データ読み書き d1 = new データ読み書き(args); 50 51 var data_list = d1.d_data.Select(element=>{ 52 //書き直しました 53 element=処理1(element); 54 element=処理2(element); 55 return 処理3(element); 56 }).ToList(); 57 58 59 ... 60 61} 62
また,処理の対象がデータが一種類しかしないのであれば処理クラスの処理をデータ読み書きのクラスに移してデータクラスとしたほうがオブジェクト指向っぽくなります。個別のデータが自分自身のデータを直接操作できるほうがいい場合オブジェクト指向に向いた設計になります。
C#
1Class データ 2{ 3 public string d_data {get;,set;}; //データ用list 4 public データ(string input_file){ 5 d_data =""; 6 (using StreamReader sr = new StreamReader(ファイル名)){ 7 string temp=""; 8//仕様だとファイルごとの処理と書いてあったのでファイルごとにしました。行ごとにしたければListになおしてください。 9 while (!sr.EndOfStream) 10 { 11 d_data += sr.Readline(); // データ用配列にファイル内容を読み込 12 } 13 処理1(); 14 処理2(); 15 処理3(); 16 17 }; 18 } 19 20 21 public void 処理1 () 22 { 23 d_dataの処理 24 } 25 26 public void 処理2 () 27 { 28 d_dataの処理 29 } 30 31 public void 処理3 (データ元) 32 { 33 d_dataの処理 34 } 35} 36 37 38 static void main(string args[])//argsでファイル名指定 39{ 40 List<データ> data_list=args.Select(element=>new データ(element)).ToList(); 41} 42
投稿2016/04/15 08:39
編集2016/04/17 10:35
退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

退会済みユーザー
2016/04/17 11:12

退会済みユーザー
2016/04/17 11:21

0
C#
1public void データ読み込み (string ファイル名)
細かいことを言うと、こういう処理はstaticでやったほうが処理が早かったりします。
また、この構成だと「Class データ読み書き」がデータの保持もしてしまっているので、
C#
1// インスタンス変数 2public string d_data = new string [1000]; //データ用配列 (実際は十数個) 3public long d_size = 0; // 実際に読み込んだファイルの行数 4// インスタンスを生成するstaticメソッド 5public static データ読み書き データ読み込み結果取得(string ファイル名){ 6 データ読み書き d = new データ読み書き(); 7 // 省略 8 while (sr.Peek() > -1) 9 { 10 d.d_data[d.size] = sr.Readline(); // データ用配列にファイル内容を読み込む 11 d.size++; // 読み込んだ行数をインクリメント - 処理のほうで最終行を知るのに使用 12 } 13 return d; 14}
のように取り扱うとよいかも。
あとは呼び出し側で
C#
1データ読み書き d1 = データ読み書き.データ読み込み結果取得(ファイルA); // データ用配列に読み込む 2データ読み書き d2 = データ読み書き.データ読み込み結果取得(ファイルB); // データ用配列に読み込む 3データ読み書き d3 = データ読み書き.データ読み込み結果取得(ファイルC); // データ用配列に読み込む
みたいにしてやるとd1,d2,d3に読み込み済みデータとデータ行数が設定された状態で取得できますね。
これを対象の処理に引数渡しする分には何も問題ないと思いますが。
C#
1// データ処理クラス側の定義 2Class データ処理 3{ 4 public void 処理1 (データ読み書き d) 5 { 6 for(int i = 0; i < d.size; i++){ 7 // 各行に対する処理1; 8 } 9 return; 10 } 11// 略:以下、使う側の記述 12 処理1(d1); // ファイルAの読み込み結果に対して処理1を実行 13
投稿2016/04/15 07:43
編集2016/04/15 08:25総合スコア5572
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/04/20 13:13
2016/04/20 14:05
2016/04/21 12:47