この記事は、「
マルチパラダイム時代におけるデータ構造 第15回 ー 続可変オブジェクトと命令型プログラミング」の続きです。前回は、可変オブジェクトと命令型プログラミングについて解説しました。今回は、不変オブジェクトと宣言型プログラミングについて解説します。
この連載で何度も、不変オブジェクトと宣言型プログラミングに言及してきましたが、実際にプログラミングするときについて解説したいので書きます。不変オブジェクトと宣言型プログラミングでは、基本的に
常に新しいインスタンスを使用する事が大事です。命令型プログラミングに慣れている人は、その点で間違いをしやすいので注意しましょう。
実例として不変コンセンスセルのテストを実装します。これにより、不変オブジェクトと宣言型プログラミングを楽しむための準備は万全だと思います。
using System;
using DataStructure;
namespace Test
{
//IImmutablDataManager<T>を実装するオブジェクトに対するテスト。
abstract class IImmutablDataManagerTest<T>
{
//要素数が正しいかチェックする。
protected virtual void CountCheck(
string testName,
IImmutablDataManager<T> target,
string methodName,
int before,
int rightValue )
{
if ( target.Count != rightValue )
throw new TestException(
testName,
methodName +
"メソッド実行後のCountプロパティの値が正しくありません。"
+ Environment.NewLine +
"予想値:" + rightValue +
" 実際の値:" + target.Count
+ Environment.NewLine +
methodName +
"メソッドとCountプロパティを確認してください。" );
}
//指定したデータが選択可能か否か判定する。
protected abstract bool CanSelect(
IImmutablDataManager<T> target,
int count,
T value );
//発生したデータが選択できないときに行うエラー処理。
protected void InsertSelectError(
string testName,
IImmutablDataManager<T> target,
string methodName, //オーバーロードがあるから
bool success,
T value )
{
if ( !success ) {
throw new TestException(
testName,
methodName +
"メソッド実行後に発生したデータを選択できませんでした。"
+ Environment.NewLine +
"選択できない値:" + value
+ Environment.NewLine +
methodName +
"メソッドとSelectメソッドを確認してください。" );
}
}
//発生したデータが選択できないときに行うエラー処理。
protected void DeleteSelectError(
string testName,
IImmutablDataManager<T> target,
string methodName, //オーバーロードがあるから
bool success,
T value )
{
if ( success ) {
throw new TestException(
testName,
methodName +
"メソッド実行後に消滅したデータを選択できてしまいました。" +
"選択値:" + value
+ Environment.NewLine +
methodName +
"メソッドとSelectメソッドを確認してください。" );
}
}
//1つのデータを生成・消滅できる事を確認するテスト。
protected void OneDataLifeCycleTest(
IImmutablDataManager<T> target,
T value )
{
string testName = "OneDataLifeCycleTest";
int count = target.Count;
var oneTarget = OneDataInsertSubTest( target, value, testName, count );
OneDataDeleteSubTest( oneTarget, value, testName, count );
}
//1つのデータを発生させるサブテスト。
private IImmutablDataManager<T> OneDataInsertSubTest(
IImmutablDataManager<T> target, T value,
string testName, int count )
{
var newTarget = target.Insert( value );
this.CountCheck(
testName: testName,
target: newTarget,
methodName: "Insert",
before: count,
rightValue: ( count + 1 ) );
bool success = this.CanSelect(
target: newTarget,
count: 1,
value: value );
if ( !success ) {
this.InsertSelectError(
testName: testName,
target: target,
methodName: "Insert",
success: success,
value: value );
}
return newTarget;
}
//1つのデーターを消滅させるサブテスト。
private void OneDataDeleteSubTest(
IImmutablDataManager<T> target, T value,
string testName, int count )
{
var newTarget = target.Delete();
this.CountCheck(
testName: testName,
target: newTarget,
methodName: "Delete",
before: count,
rightValue: count );
bool error = this.CanSelect(
target: newTarget,
count: 1,
value: value );
if ( error ) {
this.DeleteSelectError(
testName: testName,
target: newTarget,
methodName: "Delete",
success: error,
value: value );
}
}
//2つのデータが生成・消滅できる事を確認するテスト。
protected virtual void TwoDataLifeCycleTest(
IImmutablDataManager<T> target,
T one,
T two )
{
string testName = "TwoDataLifeCycleTest";
int count = target.Count;
var twoTarget = TwoDataInsertSubTest( target, one, two, testName, count );
TwoDataDeleteSubTest( twoTarget, testName, count );
}
//2つのデータを発生させるサブテスト。
private IImmutablDataManager<T> TwoDataInsertSubTest(
IImmutablDataManager<T> target, T one, T two,
string testName, int count )
{
var oneTarget = target.Insert( one );
var twoTarget = oneTarget.Insert( two );
this.CountCheck(
testName: testName,
target: twoTarget,
methodName: "Insert",
before: count,
rightValue: ( count + 2 ) );
return twoTarget;
}
//2つのデーターを消滅させるサブテスト。
private void TwoDataDeleteSubTest(
IImmutablDataManager<T> target, string testName, int count )
{
var newTarget = target.Delete();
this.CountCheck(
testName: testName,
target: newTarget,
methodName: "Delete",
before: count,
rightValue: ( count + 1 ) );
var zeroTarget = newTarget.Delete();
this.CountCheck(
testName: testName,
target: zeroTarget,
methodName: "Delete",
before: count,
rightValue: count );
}
}
}
using System;
using DataStructure;
namespace Test
{
abstract class IImmutablDataFreeManagerTest<T> : IImmutablDataManagerTest<T>
{
//1つのデータを任意の場所で、生成・更新・消滅できる事を確認するテスト。
protected void OneDataLifeCycleToIndexTest(
IImmutablDataFreeManager<T> target,
T value )
{
string testName = "OneDataLifeCycleTest";
int count = target.Count;
int index = 0;
var oneTarget = this.OneDataInsertSubTest(
target, value, testName, count, index );
this.OneDataDeleteSubTest(
oneTarget, value, testName, count, index );
}
//1つのデータを生成するサブテスト。
protected IImmutablDataFreeManager<T> OneDataInsertSubTest(
IImmutablDataFreeManager<T> target,
T value,
string testName,
int before,
int index )
{
var newTarget = target.Insert( value, index );
this.CountCheck(
testName: testName,
target: newTarget,
methodName: "Insert(int)",
before: before,
rightValue: ( before + 1 ) );
if ( !newTarget.Select( index ).Equals( value ) )
throw new TestException(
testName,
"挿入した位置に正しいデータがありません。"
+ Environment.NewLine +
"予想値:" + value +
", 実際の値:" + newTarget.Select( index ) +
"Insert(index)メソッドと" +
"Select(index)メソッドを確認してください。" );
return newTarget;
}
//1つのデータを消滅するサブテスト。
protected IImmutablDataFreeManager<T> OneDataDeleteSubTest(
IImmutablDataFreeManager<T> target,
T value,
string testName,
int before,
int index )
{
var newTarget = target.Delete( index );
this.CountCheck(
testName: testName,
target: newTarget,
methodName: "Delete(int)",
before: before,
rightValue: before );
if ( newTarget.Select( index ).Equals( value ) )
throw new TestException(
testName,
"任意の位置にあるデータを正常に消滅できていません。"
+ Environment.NewLine +
"Select(int)メソッドと" +
"Delete(int)メソッドを確認してください。" );
return newTarget;
}
//2つのデータが生成・更新・消滅できる事を確認するテスト。
protected void TwoDataLifeCycleToIndexTest(
IImmutablDataFreeManager<T> target,
T one,
T two )
{
string testName = "TwoDataLifeCycleTest";
int initCount = target.Count;
var result = this.TwoDataInsertSubTest(
target, one, two, testName, initCount );
this.TwoDataDeleteSubTest(
result.First,
one,
two,
testName,
initCount,
result.Second );
}
//2つのデータを生成するサブテスト。
protected DataStructure.ImmutabTuple<IImmutablDataFreeManager<T>, int>
TwoDataInsertSubTest(
IImmutablDataFreeManager<T> target,
T one,
T two,
string testName,
int before )
{
int oneindex = 0;
int twoIndex = oneindex + 1;
var oneTarget = target.Insert( one, oneindex );
var twoTarget = oneTarget.Insert( two, twoIndex );
this.CountCheck(
testName: testName,
target: twoTarget,
methodName: "Insert(int)",
before: before,
rightValue: ( before + 2 ) );
if ( !twoTarget.Select( oneindex ).Equals( one ) )
throw new TestException(
testName,
"挿入した位置に正しいデータがありません。"
+ Environment.NewLine +
"予想値:" + one +
", 実際の値:" + twoTarget.Select( oneindex ) +
"Insert(index)メソッドと" +
"Select(index)メソッドを確認してください。" );
if ( !twoTarget.Select( twoIndex ).Equals( two ) )
throw new TestException(
testName,
"挿入した位置に正しいデータがありません。"
+ Environment.NewLine +
"予想値:" + two +
", 実際の値:" + twoTarget.Select( twoIndex ) +
"Insert(index)メソッドと" +
"Select(index)メソッドを確認してください。" );
return new DataStructure.ImmutabTuple<IImmutablDataFreeManager<T>, int>(
twoTarget,
oneindex );
}
//2つのデータを消滅するサブテスト。
protected virtual void TwoDataDeleteSubTest(
IImmutablDataFreeManager<T> target,
T one,
T two,
string testName,
int before,
int oneindex )
{
var oneTarget = target.Delete( oneindex );
this.CountCheck(
testName: testName,
target: oneTarget,
methodName: "Delete(int)",
before: before,
rightValue: ( before + 1 ) );
if ( oneTarget.Select( oneindex ).Equals( one ) )
throw new TestException(
testName,
"任意の位置にあるデータを正常に消滅できていません。"
+ Environment.NewLine +
"Delete(int)メソッドを確認してください。" );
var zeroTarget = oneTarget.Delete( oneindex );
this.CountCheck(
testName: testName,
target: zeroTarget,
methodName: "Delete(int)",
before: before,
rightValue: before );
if ( zeroTarget.Select( oneindex ).Equals( two ) )
throw new TestException(
testName,
"任意の位置にあるデータを正常に消滅できていません。"
+ Environment.NewLine +
"Delete(int)メソッドを確認してください。" );
}
}
}
using System;
using DataStructure;
namespace Test
{
//不変コンセンスセルのテスト
class ImmutabConsCellTest<T> : IImmutablDataFreeManagerTest<T>
{
//指定したデータが選択可能か否か判定する。
protected override bool CanSelect(
IImmutablDataManager<T> target,
int count,
T value )
{
bool result = false;
var temp = ( ImmutabConsCell<T> ) target;
try {
T returnValue = temp.Select( count );
result = returnValue.Equals( value );
} catch ( IndexOutOfRangeException ) {
}
return result;
}
//全てのテストを実行する。
public void AllTestExecute(
Func<ImmutabConsCell<T>> initFunc,
T one,
T two,
T selectRightValue )
{
this.OneDataLifeCycleTest( initFunc(), one );
this.TwoDataLifeCycleTest( initFunc(), one, two );
this.OneDataLifeCycleToIndexTest( initFunc(), one );
this.TwoDataLifeCycleToIndexTest( initFunc(), one, two );
}
}
}
一目でわかると思いますが、基本的にインスタンスを処理→新しいインスタンスを使って処理...というふうに、
連鎖的にインスタンスを使用しています。利点は、処理前の状態がわかることです。以前テストがやり易くなると述べましたが、その言葉には、この利点があるから処理結果を追いやすく、デバッグ作業がやり易いという意味も含まれています。
今回はこれで終わりです。次回は新しいデータ構造を解説しますが、読者の方は不変配列と、不変キューを作ってみてください。そうすれば、不変オブジェクトと宣言型プログラミングの基本を押さえられるでしょう。続く...
テーマ : プログラミング
ジャンル : コンピュータ