AWK++によるオブジェクト指向入門
AWK++ を用いてオブジェクト指向について学んでみましょう。AWKは本来オブジェクト指向プログラミングに対応していません。しかし、AWKでもオブジェクト指向風の記述ができるようにAWK++が開発されました。実を言えば、AWK 自身によって開発された AWK へのトランスレーターにすぎず、一般的なオブジェクト指向言語よりも機能が少なく不満な点も多くあります。しかし、シンプルに出来ていて覚えるべきことが少なく、難しい言語と違ってオブジェクト指向の初学者をいたずらに苦しめ挫折させることはありません。
処理系に触れてみる
[編集]あなたが使用している環境にはAWKの処理系がインストールされていますか? UNIX系のOSを使用している方はほとんどの場合、すでにインストールされていることと思います。Windowsをご利用の方でAWK処理系がない方はGawk for Windowsやgawk 3.1.5 for Windowsをご利用ください。 インストールされているかどうか確認するには以下のコマンドを実行してください。
awk 'BEGIN { print "hello, world"}'
hello, world と表示されればインストールされています。ただし、処理系の種類やその他の原因で awk ではなく gawk 、 jgawk 、 mawk などとしなければ動作しないこともありえます。
次にAWK++を入手します。 lawker - awk++から最新バージョンのAWK++をダウンロードしてください。
それではまず、下記のプログラムを sample.awkpp というファイル名で保存し実行してみましょう。
class c1 { method show() { return "hello, world" } } BEGIN { obj1 = c1.new() print obj1.show() obj1.delete }
AWK++を使って早速実行してみましょう。次のように入力してください。
awk -f awkpp sample.awkpp > tmp; awk -f tmp
hello, world と表示されれば成功です。
クラス
[編集]世の中には犬という動物がいて、猫という動物がいて、それらは動物と呼ばれます。犬は「ワン」と鳴き、猫は「ニャー」と鳴きます。 要するに、おおよそほとんどの物事はカテゴリーに分類でき、そのカテゴリーに属するものにはそれ相応の性質を持っています。さらには、それらは特有の振る舞いをします。 オブジェクト指向におけるクラスというのはカテゴリーのようなものだといえます。
次のプログラムを見てください。
class dog { method naku() { print "ワン" } } class cat { method naku() { print "ニャー" } } BEGIN { pochi = dog.new() mike = cat.new() print "ポチが鳴くよ。" pochi.naku() print "こんどはミケが鳴くよ。" mike.naku() pochi.delete mike.delete }
すると、次のように表示されるはずです。
ポチが鳴くよ。 ワン こんどはミケが鳴くよ。 ニャー
クラスとは、難しく言えばデータとその操作手順であるメソッドをまとめたもので、オブジェクト(上の例で言えばポチとかミケ)の雛形となるものです。よくわからなければ、今は物事を分類するための定義・カテゴリーと思っておいてください。
プロパティ
[編集]犬や猫には名前、性別、年齢などがあります。このようなオブジェクトの持つ性質をプロパティといいます。 早速実例を見てみましょう。
class dog { property name property sex property age method new(x, y, z) { name = x sex = y age = z } method get(x) { if (x == "name") { return name } else if (x == "sex") { return sex } else if (x == "age") { return age } else { return "値が不正です。" } } } BEGIN { pochi = dog.new("ポチ","オス",8) printf ("名前は%s。性別は%s。年齢は%d歳です。\n",pochi.get("name"), pochi.get("sex"), pochi.get("age")) pochi.delete }
実行結果は次のようになります。
名前はポチ。性別はオス。年齢は8歳です。
クラスの最初で name 、 sex 、 age の3つのプロパティを定義しています。 method new() というのは、オブジェクトを作成した際に自動的に実行されるメソッド(メソッドって何?と思われる方もいらっしゃるでしょうが、今は動作・振る舞いのような物だと思ってください)です。
pochi = dog.new("ポチ","オス",8) と記述することで、プロパティを持つオブジェクトを作成しています。 method get(x) にreturn文が出てきますが、これは通常の AWK の関数における return と同じ働きです。printf文で表示させると、確かにpochiオブジェクトはプロパティを保持しています。ちなみにプロパティはクラス内でしか書き換えることはできないようになっています。BEGINブロック内で name という変数に適当な数や文字列を代入してもクラス内の name の値に変化はありません。
ところで、他のオブジェクト指向プログラミング言語を学習したことのある方の中には、 method get(x) を定義してプロパティにアクセスしているのを不思議に思われるかもしれません。実を言うと、 AWK++ ではプロパティにダイレクトにアクセスできません。もし、クラスの外からプロパティの値を利用したければ、今回のようにプロパティの値を返すメソッドを定義しておく必要があります。
また、GNU Awk を使っている方や C言語を学んだことのある方の中には、長い if else 文のところでは switch を用いたほうが良いと思われる方もいらっしゃるでしょう。確かにその通りで、 GNU Awk で実行するなら switch を用いてわかりやすく書くほうが望ましいでしょう。ただし、他の処理系では switch が使えませんので今回は if で書いています。
メソッド
[編集]メソッドとは、今までの例でもすでに何度も出てきた
method メソッド名()
という文で定義された一種の関数のことです。 普通の関数と違うのは、各オブジェクトが持っている自分自身に対する操作であるという点です。 すでに簡単に説明してありますが、改めて例を示します。
class gal { property name property age property flag method new(x, y) { name = x age = y flag = 0 } method aisatsu() { print "俺「おはよう!」" if (flag == 0) { printf ("%s「あ、おはよう。」\n",name) flag = 1 } else { printf("%s「あれー? さっきもあいさつしたよね?」\n",name) } } } BEGIN { yumiko = gal.new("由美子", 16) mina = gal.new("美奈", 14) yumiko.aisatsu() mina.aisatsu() yumiko.aisatsu() mina.aisatsu() yumiko.delete mina.delete }
実行結果は以下のようになります。
俺「おはよう!」 由美子「あ、おはよう。」 俺「おはよう!」 美奈「あ、おはよう。」 俺「おはよう!」 由美子「あれー? さっきもあいさつしたよね?」 俺「おはよう!」 美奈「あれー? さっきもあいさつしたよね?」
オブジェクト作成時、new(x, y) メソッドで flag プロパティを 0 に設定しています。その後、 yumiko.aisatsu() や mina.aisatsu() を実行すると、 flag が 0 なら「あ、おはよう。」とあいさつを返すようになっていて、実際にそのように動作します。その時、 flag は 1 に変わります。再び yumiko.aisatsu() や mina.aisatsu() を実行すると、flag は1になっています。その場合は「あれー? さっきもあいさつしたよね?」と表示されます。
なお、 new(x, y) メソッドのようにオブジェクト作成時に自動的に実行され、初期化などの役割を果たすメソッドのことをコンストラクタ(構築子)と呼ぶことがあります。
インヘリタンス(継承)
[編集]既に定義されているクラスをもとにして、拡張や変更を加えた新しいクラスをつくることをインヘリタンス(継承)といいます。元になるクラスは「スーパークラス」とか「基本クラス」などと呼ばれます。それに対し、新たに作ったクラスは「サブクラス」とか「派生クラス」などと呼ばれます。早速実例を見てみましょう。
class gal { property name property age property flag method new(x, y) { name = x age = y flag = 0 } method jiko_shokai() { printf("%s「%sといいます。%d歳です。」\n",name, name, age) } method aisatsu() { print "俺「おはよう!」" if (flag == 0) { printf ("%s「あ、おはよう。」\n",name) flag = 1 } else { printf("%s「あれー? さっきもあいさつしたよね?」\n",name) } } } class tsundere : gal { property name property flag method aisatsu() { print "俺「や、やあ。おはよう!(苦手なんだよなぁ・・・この娘。)」" if (flag == 0) { printf("%s「な、なんなの? 気安く話しかけないでちょうだい。」\n",name) flag = 1 } else { printf("%s「何度もしつこいのよ。あっち行ってくれる?」\n",name) } } } BEGIN { yumiko = gal.new("由美子", 16) mina = gal.new("美奈", 14) futaba = tsundere.new("双葉",17) yumiko.jiko_shokai() mina.jiko_shokai() futaba.jiko_shokai() yumiko.aisatsu() mina.aisatsu() yumiko.aisatsu() mina.aisatsu() futaba.aisatsu() futaba.aisatsu() yumiko.delete mina.delete futaba.delete }
実行結果は以下のようになります。
由美子「由美子といいます。16歳です。」 美奈「美奈といいます。14歳です。」 双葉「双葉といいます。17歳です。」 俺「おはよう!」 由美子「あ、おはよう。」 俺「おはよう!」 美奈「あ、おはよう。」 俺「おはよう!」 由美子「あれー? さっきもあいさつしたよね?」 俺「おはよう!」 美奈「あれー? さっきもあいさつしたよね?」 俺「や、やあ。おはよう!(苦手なんだよなぁ・・・この娘。)」 双葉「な、なんなの? 気安く話しかけないでちょうだい。」 俺「や、やあ。おはよう!(苦手なんだよなぁ・・・この娘。)」 双葉「何度もしつこいのよ。あっち行ってくれる?」
tsundere クラスの宣言が class tsundere : gal と書かれていますが、これは tsundere クラスが gal クラスを継承していることを示しています。簡単に言うと、tsundere クラスは基本的には gal クラスと同じものになっているということです。事実、由美子・美奈・双葉の自己紹介は名前と年齢が入れ替わるだけで全て同じ形式になっていますね。これはgal クラスの jiko_shokai() メソッドが実行されているからです。
でも、tsundere クラスのオブジェクトの双葉は gal クラスのオブジェクトの由美子や美奈とはあいさつの仕方が違いますね。それは、tsundere クラスで改めて aisatsu() というメソッドを定義し直しているからです。つまり、スーパークラスの性質はすべてサブクラスに受け継がれますので、サブクラスではスーパークラスとの違いを定義するだけで済むのです。このように、スーパークラスで定義されたメソッドをサブクラスで定義しなおし、動作を上書きすることをオーバーライド (override)と言います。