- PR -

C# 計算フローを抽象化したい

1
投稿者投稿内容
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2007-08-01 16:07
例題のようなケースの場合、計算フローと型に依存した処理を分離する方法はありますか?例えばインターフェースあるいはジェネリクスを使うことで解決できますか?

例題
 配列の平均値を求めるプログラムがありますが、この記述方法では対象とする配列の型が増える度に計算フローを重複して記述しなければならず、メンテナンス上好ましくありません。つまり、平均値を求める場合、@初期化A積算ループB平均化の3ステップのフローを要しますが、string型、int型、float型に対応する各Averageメソッドに@ABが重複して記述されています。@ABの計算フローを抽象化し、型に依存する部分と分離することはできるでしょうか?
 一つのアイデアとして、object型で受けるAverageメソッドを1つ作成し、リフレクション(GetType())を使って1@ABの中で型ごとに分岐する方法があります。こうすれば@の処理、Aの処理、Bの処理が1カ所に集められ、若干見通しが改善されますが、@ABの3カ所に分岐命令が挿入されるため、根本的な解決ではありません。
 @ABの計算フローと型に依存する処理とを完全に分離する記述方法を探しています。

public abstract class Test
{
// 配列要素の平均値を求める

public static string Average(string[] a)
{
int num = a.Length;
// Step1 初期化
string sum = "";
// Step2 積算
for (int i = 0; i < num; i++)
{
sum += String.Format("+{0}", a[i]);
}
// Step3 除算
return String.Format("({0})/{1}", sum, num);
}

public static int Average(int[] a)
{
// Step1 初期化
int num = a.Length;
int sum = 0;
// Step2 積算
for (int i = 0; i < num; i++)
{
sum += a[i];
}
// Step3 除算
return sum / num;
}

public static float Average(float[] a)
{
// Step1 初期化
int num = a.Length;
float sum = 0;
// Step2 積算
for (int i = 0; i < num; i++)
{
sum += a[i];
}
// Step3 除算
return sum / num;
}

}
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2007-08-01 16:17
すみません、サンプルプログラムの一部に誤りが見つかりましたので訂正します。

string型の積算ループ部分
誤 sum += String.Format("+{0}", a[i]); // 左端に余分な+がついてしまう
正 sum += (i == 0 ? "" : "+") + a[i];
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-08-01 22:02
引用:

ひろしさんの書き込み (2007-08-01 16:07) より:
型に依存する部分と分離することはできるでしょうか?


どこが型に依存する部分かというのが問題になりますね。
「複数の項目を全て足し合わせて、それを項目数で割る
という処理が平均を求める処理ならば、「足す」と「割る」という処理がひろしさんの決める型独自の処理になると思います。
例えばstringであれば足すというのは文字列を連結し間に'+'を入れることですし、intの割るは数値を項目数で除算したのち小数点以下を切り捨てる処理ということになります。
 intは切捨てが当り前ではありません。VBでは整数変数に小数を入れると偶数丸めになります。
 ひろしさんは、intの時には切り捨てをするという特別な選択を(暗黙的にですが)しているわけです。


ですので足すと割る以外の部分は分離することで「重複して記述」せずに済みます。
色々試してみたところ、結局こういう形になりました。
コード:

//足す・割る以外の共通の部分
public static class AverageCalc{
public static T Average<T>(T[] items, T initialValue, AddDelegate<T> addDele, DivDelegate<T> divDele){
T sum = initialValue;

foreach (T item in items)
{
sum = addDele(sum, item);
}
return divDele(sum, items.Length);
}
}

//intの平均計算
public static class IntAverageCalc
{
public static int Average(int[] items)
{
return AverageCalc.Average<int>(items, 0, new AddDelegate<int>(Add), new DivDelegate<int>(Div));
}

private static int Add(int item1, int item2) { return item1 + item2; }
private static int Div(int item, int count) { return item / count; }
}

//floatの平均計算
public static class FloatAverageCalc
{
public static float Average(float[] items)
{
return AverageCalc.Average<float>(items, 0f, new AddDelegate<float>(Add), new DivDelegate<float>(Div));
}

private static float Add(float item1, float item2) { return item1 + item2; }
private static float Div(float item, int count) { return item / count; }
}

//stringの平均計算
public static class StringtAverageCalc
{
public static string Average(string[] items)
{
return AverageCalc.Average<string>(items, "", new AddDelegate<string>(Add), new DivDelegate<string>(Div));
}

private static string Add(string item1, string item2)
{
if (item1 == "") return item2;
return item1 + "+" + item2;
}
private static string Div(string item, int count)
{
return item + "/" + count.ToString();
}
}


対象の型ごとにクラスが分かれているのは試行錯誤の名残ですので特に意味はありません。メソッドは全てstaticですから、ひとつのクラスに全てまとめても同じです。

で、ひろしさんが抽出したかったのはAverageCalcのAverageメソッドなんですね。
アーティストとしてはこのようにソースで物事を表現するのは楽しいものですが、もし仕事でこのようなことをしたいならおとなしく「重複して記述」しておいた方がいいですよ。
一年後に「平均」という言葉の意味が変わってしまうなら話は別ですが。

[ メッセージ編集済み 編集者: 一郎 編集日時 2007-08-01 22:05 ]
ひろし
ぬし
会議室デビュー日: 2002/09/16
投稿数: 390
お住まい・勤務地: 兵庫県
投稿日時: 2007-08-01 23:52
ご回答ありがとうございます。
私が希望していた回答です。

私自身は、ジェネリクのインターフェース制約あるいは派生制約あたりを使って解決できないか考えてみましたが、うまくいきませんでした。(残念なことに、演算子を制約に含めることはできないのですね)
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-08-02 00:02
あっと、すいません。
AddDelegate<T>とDivDelegate<T>の定義を書き忘れていました。

public delegate T AddDelegate<T>(T item1, T item2);
public delegate T DivDelegate<T>(T item, int count);

って感じです。
1

スキルアップ/キャリアアップ(JOB@IT)