ユーザ入力の問題点

前回のプログラムでは、小さな問題点があるというか、扱いづらい印象の関数になっていましたね。 大きく分けて二つの問題があります。

関数getIntは整数0が扱えない

関数getIntは整数型の数値を入力させ、それを返すという本質以外に、 数値が0であってはならないという条件付です。 改良するにはどうすればよいのか考えてくださいと書きましたが、 皆さんはどのような手段を思いついたでしょうか?

今回は、関数では0を許可し、演算する変数側で対処する方法を提示します。 これで、整数を返す関数として使いまわせるものとなりそうです。

取得した値の利用に手間が生じる

例えば、次のように使っていますね。

var num1 = intInputs("数値1の入力",,);
num1 = ( num1 > 9999 ) ? 9999 : num1;
							

もし、二行目の処理を書き忘れたら、大きな数字が入力された場合に問題が生じます。 それを防ぐ為の処置なのですが、何度も変数同士で演算させる場合は非常に手間です。

これまで関数やクラスを使ってきた理由のひとつとして、 各機能を制限して、独立性を持たせる意味がありました。 これは変数にも言える事で、ただ四則演算がしたいだけなのに、 変数の値を考慮しなければならないのでは、スマートなプログラムとはいえません。

今回のプログラムのようにある変数があり、その変数に入れる値に色々な制限が加わる場合、 プロパティと呼ばれる機能(仕組み)を使うと便利です。次回はこの仕組みを説明します。

プロパティの概要

一般的な言語では、クラスのメンバー変数は保護されます。 保護…つまり、外部からは変数がどのような使われ方をするのかを知る事が出来ませんし、 知る必要もありません。ただ、そのクラスの役割と仕様を知っていれば良いのです。 この仕組みを難しい言葉でカプセル化と呼びます。

ただ、TJSのカプセル化は完全ではありません。 ですから、メンバー変数を外部から参照させないために、わざわざメンバー変数を操作する為の関数を二つ用意しましたよね。 この手間を省いて、より効率よく変数を利用できるようにしたものがプロパティです。

カプセル化は、メンバーの参照範囲(スコープ)を適切に制限し、 全てクラスに自己管理させようという仕組みを指します。 クラスの制御を外部から行っては、わざわざクラスを用意する意味が薄れるからです。

ご存知の通り、TJSではメンバーの参照範囲を制限する事は出来ませんし、 全メンバーを外部から参照する事が出来てしまいます。 C++では、この範囲を厳密に制御出来ます。

そういった意味で、TJSのクラスはカプセル化が不完全なのです。

プロパティを定義するには、次のようにします。

property プロパティ名
{

}
				

クラスの時と同様に、最後に()は書きません。 また、このプロパティは空っぽですので、順番に機能を追加していきます。

値のセット

プロパティは基本的にある変数一つを扱います。(ある変数の名前を変数Aとします。) 変数Aを参照する代わりに、プロパティを参照するという形を取ります。 つまり、この時変数Aは直接操作しないという事で、変数Aを保護できるのです。

まず、値を入れる事が出来ないと困るので、値を入れます。 書式は次のようになります。

property プロパティ名
{
  //プロパティに値を代入すると呼ばれる
  setter( x )
  {
    //処理内容
  }
}
				

このように、setterという単語を書き、普通の関数のように、処理内容を{}で囲みます。 よって、setterは普通の関数のように扱えます。

普通の関数と同じく、引数を受け取ることが出来ますが、 ここで受け取るのは、プロパティに代入された値です。使用例をご覧ください。

var A;

//変数Aを扱うプロパティProp1
property Prop1
{
  setter( x )
  {
    A = x;
  }
}

//プロパティProp1に100を代入
//Propのセッターであるsetter()の呼び出し
Prop1 = 100;

//setter()が呼ばれ、Aに100が代入されている
System.inform( A );
				

この例では、プロパティProp1に100を代入しています。 この時、setter()が100という数値を引数xとして受け取り、これを変数Aに代入しています。 変数Aの内容がちゃんと表示されている事に注目してください。

このsetter()関数をセッターと呼びます。 (値をセット-Setするからセッターなんですね。タバコとは関係ありません)

ここでは、単純に代入された値を変数Aに代入していますが、 変数Aの値を調整したい場合、このセッター内に処理を書けば良いのです。

このプログラムでは、あたかも変数AとプロパティProp1が等しいかのように振舞っています。 逆に言えばプロパティProp1が、まるで変数のように扱えています。 これがプロパティを利用する最大の利点です。

値の参照

もう一度書きますが、プロパティは基本的に一つの変数を扱います。 この時扱う変数とプロパティが同じ変数のように振舞わせるので、 扱う変数自体はは直接参照しません。

System.inform( A );

前回の、この指定は誤りであるという事ですね。 値を代入出来るという事は、当然入れた値を読めないと意味がありません。

そこで、値が読み出された時に呼び出される関数を指定します。

property プロパティ名
{
  //プロパティの値が読み出されると呼ばれる
  getter( )
  {
    //処理内容(普通は値を返すだけ)
  }
}
				

このように、内部にgetter()という関数を書くと、 プロパティが参照された時に、自動的に呼び出されます。 これをゲッターと呼びます。 (値をゲット-Getするからゲッターなんですね。ロボットとは関係ありません。)

先ほどのプロパティProp1に、値を読み出す関数を追加したのが以下です。

var A;

//変数Aを扱うプロパティProp1
property Prop1
{
  setter( x )
  {
    //代入された時の処理(1)
    A = x;
  }

  getter( )
  {
    //読み出された時の処理(2)
    //値を返すだけ
    return A;
  }
}

//プロパティProp1に100を代入(1)
Prop1 = 100;

//プロパティの値を読み出す(2)
System.inform( Prop1 );
				

値が読み出された時に行う処理をゲッターに書きますが、 通常は扱っている変数の値をそのまま返すだけで良い場合がほとんどです。

ゲッター(getter)は値を読むだけなので、引数は受け取れません。 よって最後の()は省略する事も出来ます。 が、やはり読みやすさを重視する当入門では、括弧()まで書く事を推奨します。

処理の省略

ゲッターやセッターは省略可能です。 例えばセッターを省略すると、プロパティに値を代入出来なくなります。 (読み取り専用プロパティです。)

//読み取り専用
property Prop1
{
  getter( )
  {
    return;
  }
}
				

逆に、ゲッターを省略すると、プロパティから値を読み込み出来なくなります。 (書き込み専用プロパティです。)

//書き込み専用
property Prop1
{
  setter( x )
  {
    A = x;
  }
}
				

読み込み専用プロパティは、例えばウィンドウサイズはプログラム側では操作するけど、 外部側からは変えられない…というような機能を実現します。

書き込み専用プロパティは、例えばウィンドウの透明度を変える事は出来るけど、 その値を知る必要がない、というような場合に利用します。

ユーザ入力の完成

駆け足で説明してきましたが、プロパティの仕組みについて理解できたでしょうか? ここで仕組みを簡単にまとめます。

  • プロパティはある変数と同じように振舞わせる
  • プロパティに値が代入されたら、好みの値に補正してある変数に代入できる
  • プロパティが参照されたら、ある変数の値を返す

この三点を考えると、前回やったデータ入力と四則演算の問題点を解決出来ますね? 演算に使用する変数をプロパティに置き換え、 0を拒否したり、9999を超えた値を補正する部分を、プロパティのセッターに任せればよいのです。

プログラムの完成版が以下です。

// 整数入力関数 userInput.tjs
function getInt(cap="整数入力", prom, str)
{
    var num = int System.inputString(cap , prom , str);
    return num;
}
				
// getInt()を使用するため
Scripts.execStorage("userInput.tjs");

// 演算に使用する変数
var number1,number2;

// number1に対応する
property num1
{
  setter( x )
  {
    number1 = x > 9999 ? 9999 : x;
  }

  getter()
  {
    return number1;
  }
}

// number2に対応する
property num2
{
  setter( x )
  {
      if ( x == 0)
      {
          System.inform("0は入力出来ません。\n1として処理を続けます");
          x = 1;
      }
      number2 = x > 9999 ? 9999 : x;
  }

  getter()
  {
      return number2;
  }
}

System.inform(
  "四則演算を行います。"+
  "\n数値を二つ入力してください。");
num1 = getInt("数値入力1","整数を入力してください");
num2 = getInt("数値入力2","整数を入力してください");

System.inform(
  num1+" + "+num2+"="+(num1+num2)+"\n"+
  num1+" - "+num2+"="+(num1-num2)+"\n"+
  num1+" × "+num2+"="+(num1*num2)+"\n"+
  num1+" ÷ "+num2+"="+(num1/num2),
  "計算結果"
);
				

今回はあえて解説は省略します。 他人のプログラムを自力で読み取る事はとても大切な事。 他人が作ったプログラムを読み取り、自分なりにアレンジして新たなプログラムとして作る。 これが上達の一番の早道だからです。

それほど難しい処理はしていませんので、これまで覚えた知識を使って、是非自力で読み解いてください。 また、改良点をあえて残していますので、見つけてアレンジしてみてください。

  • number1、number2はnum1、num2にそれぞれ対応します。num1は演算する値、num2は演算される値です。
  • プロパティnum1は代入最大値を9999に設定します。
  • プロパティnum2は0であってはならないので、1をセットする処理をくわえています。
    ※ここでwhile文を使ってgetInt()による再入力を行わせても良いですね。

メンバープロパティ

唐突で申し訳ないのですが、実は今回のようにプロパティ単体で使う例は少ないです。 説明の為に半ば強引にプロパティを使ってみましたが、 四則演算程度の処理ならば、プロパティを定義する手間の方がデメリットになりえるからです。

プロパティが最も有効に機能するのは、クラスのメンバーとして使った時です。 プロパティをメンバーとした時、メンバー変数を保護する事が出来ますので、 TJSにおけるカプセル化が完全なものとなります。

クラスのメンバーであるプロパティはメンバープロパティと呼びます。

これまで、クラスのメンバー変数を扱う場合、次のように書く必要がありました。

class myclass
{
  var num;

  function setNum( num )
  {
  this.num = num;
  }
  
  function getNum( )
  {
  return num;
  }
}
				

このように、変数を読み出すメンバー関数と、値をセットするメンバー関数を別々に用意しました。 これは、プロパティで言うセッターとゲッターの役割と同じ事が分かりますね。

しかしプロパティとは違い、値のセット用の関数名と、読み出し用の関数名を覚えておかなくてはなりませんよね。 おまけに、扱うメンバー変数が二つ、三つと増えれば、操作用の関数も二倍、三倍と増えてしまいます。。

そこでプロパティの出番です。 これをプロパティで代替するなら次のようになります。

class myclass
{
  var num;

  property number
  {
      setter( num )
      {
          this.num = num;
      }
  }

  getter ()
  {
    return num;
  }
}
				

これで、変数を使う側は、メンバー関数の呼び出しという手順を踏まなくても、 変数に値を自由に出し入れ出来るようになります。例えば次のように。

var a = new myclass();
a.number = 10;
System.inform( a.number );
				

この時、オブジェクト(実物)aのメンバー変数numを扱っている様に見えますが、 numberはプロパティである事に注意してください。 よって、メンバー関数と同じく、一度名前を付けたらもう変えてはなりません。

逆に、メンバー変数numは変えても構いません。 その場合はプロパティnumberの中だけを改良すれば済むからです。