5.7 制御をオブジェクトで置換

(1/1)
パズルを組み合わせる人たちのイラスト
複数あるラジオボタンのどれかを選択すると、それに応じた機能が働くというプログラムがよくある。
これまで学んできたことから、選択されたラジオボタンの値を取得し、「3.1 if~else文」や「3.2 switch~case文」を使って対応する関数に制御を渡すというプログラムを作ることができるだろう。
だが、こうした制御は日常生活では馴染みがないし、機能追加をするとプログラムが複雑化するという問題を孕んでいる。
そこで今回は、制御をオブジェクトを使って置き換えることで、これらの問題を解決していくことにする。

目次

サンプル・プログラムの実行例

数字表記を正規化する.

サンプル・プログラム

数字の正規化

今回は、テキスト中にある半角、全角、漢数字混在の数字を、一定の手法に基づいて変換(正規化)するJavaScriptプログラムを作る。正規化として選べる手法は以下の通り。
  1. 半角数字       (例)12345678
  2. 半角数字カンマ区切り (例)12,345,678
  3. 全角数字       (例)12345678
  4. 全角数字カンマ区切り (例)12,345,678
  5. 漢数字        (例)一二三四五六七八
  6. 漢数字位取り     (例)千二百三十四万五千六百七十八
プログラムとしては、
  1. テキスト中から数字を抜き出す。
  2. 抜き出した数字を半角に統一する。
  3. 抜き出した数字に選択した正規化手法を適用する。
という流れにする。

今回お伝えしたいのは、数字の正規化ではなく、3番目の正規化手法を選ぶ部分にある。
数字の正規化について興味がある方は、「PHPで漢数字を半角数字に変換する(整数版)」「PHPで半角数字を漢数字にする」を参考にしていただきたい。

読みやすいプログラム:制御をオブジェクトで置き換える

正規化手法を選ぶのにラジオボタンを用いることにする。
Webアプリケーションで、選択したラジオボタンによって作用や機能が変わるプログラムはよくある。今回紹介する方法を使うと、こうしたプログラムを作ったり拡張するのが容易になるだろう。
制御をオブジェクトで置き換える
ラジオボタン AC を選択したら、各々に対応する関数 funcAfuncC を実行したいとする。「3.1 if~else文」や「3.2 switch~case文」を使って、AC の場合に応じて条件分岐させ、funcAfuncC を実行させる――これは正しい。
しかし、機能追加でDやEの場合が増えると、その分、条件分岐を増やさなければならず、テストケースが膨れあがる。かといって、すべてのテストケースを網羅しないと、バグを取りこぼす恐れがある。

ここで、「forEachループ - 5.1 配列とオブジェクト、for...in、forEachループ」で、配列やオブジェクト使って forループを読みやすくする方法を紹介したのを思い出して欲しい。これを応用することで、明示的な条件分岐を書かなくて済むようになる。

上図のように、ラジオボタン A と対応する関数 funcAが1組で1つのオブジェクトに、ラジオボタン B と対応する関数 funcB が1組で1つのオブジェクトに‥‥というオブジェクト配列を用意し、ラジオボタンの選択と関数がセットで動くようにしてやるのだ。
つまり、条件分岐をオブジェクトで置き換えるのが読みやすいプログラムということになる。

オブジェクトを使った関数定義

実際に書いたJavaScriptプログラムを見ていくことにしよう。

  26: //正規化手法の一覧
  27: let NormalizedFunctions = [];
  28: /** 変数の構造
  29:  *  [関数名]
  30:  *      .label = ラベル(ラジオボタンに表示)
  31:  *      .func  = 関数本体
  32: */

 177: /**
 178:  * 半角英数記号文字を全角英数字に変換する.
 179:  * @param   String str  半角文字列
 180:  * @return  String 全角数字
 181: */
 182: NormalizedFunctions['toFullWidth'] = {
 183: label : '全角',
 184: func : function (str) {
 185:     const REG_TARGET = /[!-~]/g;
 186: 
 187:     return str.replace(REG_TARGET, function(ss) {
 188:         return String.fromCharCode(ss.charCodeAt(0+ 0xFEE0);
 189:     });
 190: }};

ラジオボタンと関数のセット(=正規化手法のセット)を配列 NormalizedFunctions に代入する。この配列は、添字に関数名を、オブジェクト label にラジオボタンに表示するテキストを、オブジェクト func に関数の本体を持たせた連想配列になっている。
配列 NormalizedFunctions への代入はやや複雑だが、こうすることで、ラジオボタンの選択と関数をパッケージング化することができる。複雑といっても定型化されており、他のラジオボタンと関数のセットも同様に定義・代入すればよい。

 306: /**
 307:  * 元のテキストの中の数字を正規化して変換後テキストに表示する.
 308:  * @param   なし
 309:  * @return  なし
 310: */
 311: function dispNormalizeNumbers() {
 312:     func = getSelectedValue('func');
 313: 
 314:     let sour = document.getElementById('sour').value;
 315:     document.getElementById('dest').value = normalizeNumbers(sour, func);
 316: }

ユーザー定義関数 dispNormalizeNumbers は、選択されたラジオボタンに応じた関数を呼び出すのであるが、条件分岐を使っていないことは一目瞭然であろう。
このように制御をオブジェクトに置き換えたことで、正規化手法(=オブジェクト)を追加したときは、追加したオブジェクトの単体テストだけ行えば十分である。

コラム:オブジェクト指向とダイナブック

制御をオブジェクトで置き換える
もう一度、先ほどの図をご覧いただこう。ラジオボタンと関数がセットになったオブジェクトである。
1.2 画面にメッセージを表示する」のコラムで触れたように、本連載の構成は『プログラミング言語C』を参考にしている関係で、条件分岐などの制御構造を先に説明した。だが、ここでつまずいた方が何人かいるのではないだろうか。また、関数型プログラミングに慣れている方は、まだ初心者だった時代を思い出してほしい。
じつは、日常生活で制御構造を頭に思い浮かべるシーンは少ない。むしろ、上図のようなオブジェクトで行動を決めることが多いのではないだろうか。このため、制御構造でつまずいてしまう人が出るのは致し方ないと言えよう。
Alto
Alto
こうしたオブジェクトを使ってプログラムの流れを設計することをオブジェクト指向と呼ぶ。
1970年代後半、ゼロックス社のパロアルト研究所(PARC)でアラン・ケイらが中心となってオブジェクト指向の研究が進められた。アラン・ケイは、紙の上で鉛筆を持って作業する作業を、そのままコンピュータ上で実現できるダイナブックを目指し、写真のミニコンピュータ Alto と、その上で動作するオブジェクト指向型プログラミング言語 SmallTalk を開発した。マウスやウィンドウシステムを使った先進的なGUIは、Macintosh に搭載され成功を収めた。また、紙のように持ち運んで作業ができるという部分は、東芝のノートPC「ダイナブック」に受け継がれた。
ここまでラジオボタンと関数のセットをオブジェクトにすると書いてきたが、サンプル・プログラムでは、じつはラジオボタンそのものではなく、表示ラベルをオブジェクトに代入しただけであった。
ラジオボタンの構造を取り込み、関数をメソッドにしたクラスを設計することで、さらにオブジェクト指向の度合いが高まる。
さらに、ラジオボタンだけでなく、「何かを選んだら、何かのメソッドを実行する」という基本構造を抽象クラスで定義し、それを継承して具体的なUIとメソッドを備えた具象クラスを書いてみよう。JavaScriptでは明示的に抽象クラスを宣言することはできないが(TypeScriptにはabstract修飾子が用意されている)、一般的なクラスで定義することになる。
(この項おわり)
header