ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

PowerShellでクラス定義!

PowerShellの記事を結構、書きそうなので「PowerShell」カテゴリを作りました。

C#」カテゴリはまだ3件しか記事を書いてないんですね。

そういえば余談ですが、C# 6.0の新機能を調べてみたらあんまり面白いものがありませんでした。

C#はもう、これ以上いじったら最初のものと離れすぎるし、そろそろ保守的な時期に入ったのでしょうかね。

それに比べ、PowerShellのこの頃の進化はヤバイです。


Windows10ではBashが使えるようになるらしいですね。
Ubuntu互換のシステムを搭載して、その上でLinux用のBashをそのまま動かすみたいです。

私はBashがどんなものなのかよく知らないのですが、すごく喜ばしいことのようです。

Bashって便利なんですかね。
PowerShellもなかなか便利だと思うんですけどね。

というか、PowerShellのほうが全然新しいでしょうし、オブジェクトのパイプラインもあるし、

PowerShellは一行のコマンドを書くのも、大きなスクリプトを書くのも柔軟に対応できるし、PowerShellのほうが本当は凄いんだと思います。


「WindowsのCUI環境はクソだ」とよく言われますが、それはコマンドプロンプトのほうだけ見ての意見なんじゃないでしょうか。

私はコマンドプロンプトのほうは使えないので、コマンドプロンプトが如何ようにクソなのか存じ上げませんが…。


まあ、Linuxなど他社の技術との親和性を求める人はWindows上でもBashを使うようになると思うので、
PowerShellには、もうふっきれて、親和性度外視でオレ仕様を作りまくってほしいですね。

私はせっかくPowerShellを覚えたので、PowerShellについていきたいと思います。



さて、PowerShell v5.0からは、なんとクラス定義が可能になりました。
もはやシェルスクリプトではないですね。

初期のPowerShellの仕様は、わりと変数の型指定もしなくていいようなものだったんですが、
最近はこのクラスメソッドの戻り値とか、どんどん型指定を推奨する仕様が増えてきたと思います。

そういうところからも、PowerShellはだんだん本格的に書くという方面に走りだしているのかなと感じられますね。


ちなみに、PowerShellでクラス定義ができるようになったというのは、こないだの記事「PowerShellでWPFとかイベントとか ~ 枠組みをC#で書く ~」でも言っていて、

C#でやっているクラス定義をPowerShellのほうで済ませられるかもと言及していたのですが、実験してみたところ無理そうでした。

どういう仕組みになっているのかXamlReaderは、C#で書いてAdd-Typeした型は認識しても、PowerShellで定義した型は認識しないようです。

(もしかしたら、'\powershell'というアセンブリ名を指定したらいけるのかもしれません。実験してません。)



今回は、PowerShellでクラスを定義して使う方法を記事にします。

例として、犬を表すクラスDogクラスを作成してみましょう。

まずクラス定義ですが、

class Dog {
}


となります。

それでは、犬の名前を表すフィールドを定義します。
しかし、今のところ、PowerShellのクラスではフィールドを直接定義することは無理で、プロパティの定義になるようです。

class Dog {
[string] $Name
}


これでNameプロパティを定義しました。
最初の[string]は型指定で、無くてもいいんですが、すごくあったほうがいいものという雰囲気です。

プロパティは現状ではアクセシビリティは設定できず、publicしか無理なようで、
これはオブジェクト指向を根本から否定してしまっている点だとは思いますね。


続いて、年齢プロパティも定義しておきましょう。

class Dog {
[string] $Name
[int] $Age
}


これでフィールドはもういいので、メソッドの定義をします。
メソッドの定義は、クラス内に

Method() { }

と書きます。
メソッド名Methodの後ろの括弧の中には、引数リストを定義します。

引数リストの書き方はfunctionと同じです。
functionでは引数なしなら括弧も要らないですが、こちらでは必須なようです。

そして、上のような状態だと戻り値はありません。
明示的に書けば、

[void] Method() { }

となります。
voidを他の型にすれば戻り値ありの関数になって、returnを書かないとエラーになります。

functionのほうでは戻り値の型は明示できないんですが、クラスメソッドでは明示できるんですね。
逆に、クラスメソッドでは型を明示せずに戻り値を返すことはできません。


では、犬がワンと鳴くMakeSoundメソッドを定義します。

class Dog {
[string] $Name
[int] $Age

[string] MakeSound() {
return 'ワン'
}
}


ここでは、あえてワンと出力するのではなく、'ワン'という値を返すようにしました。

というのも、実験してみたところクラスメソッド内では、Write-HostとかConsole.WriteLineで出力したものならちゃんと出るのに対して、

Write-Outputしてもファイルなら出るんですが、標準出力には出てこないようです。


スクリプトブロックとかfunctionならWrite-Output出来るんですけどね。

これらがその場所へ制御を移すという意味合いが強いのに対して、クラスメソッドは外部のライブラリを呼び出すのと同じで、外部のものという扱いが強いんでしょうか。

つまりは、メソッド内で色々と出力してるにしても、それは外部の事情であって、戻り値だけを気にするということです。


まあ、メソッドが出力するのは、戻り値だけということもできますね。

戻り値を変数に入れたら表示されないですが、PowerShellの場合は戻り値をフリーにさせといたら標準出力に出ますしね。

メソッド呼び出しも、スクリプトブロックやfunctionの方式ではなく、外部ライブラリのメソッドを呼び出すのと同じ方式を使います。



さて、最後にコンストラクタも定義しておきます。
コンストラクタはクラス名と同じメソッド名を、型指定なしで作ってやります。

コンストラクタを明示的に定義しなければ引数なしのものが自動で作られますが、定義すれば自動のものは消えます。


class Dog {
[string] $Name
[int] $Age

[string] MakeSound() {
return 'ワン'
}

Dog([string]$name, [int]$age) {
$this.Name = $name
$this.Age = $age
}
}


コンストラクタでフィールドを初期化するようにしました。

$thisは自身のインスタンスを表す自動変数で、ここからフィールド(プロパティ)にもアクセスします。

$thisを使わずにアクセスは出来ないようです。



ではインスタンス化して使ってみましょう。

functionを定義したあと普通に処理のコードを書き始められるのと同じで、普通にクラスを書いた続きに処理を書くことができます。

$instance = New-Object Dog('ハチ公', 10)

となります。10歳のハチ公としました。

New-Objectでインスタンス化は無理で、[Dog]::newという静的メソッドの呼び出しを行わなければならないというのがググれば出ますが、今はNew-Objectでも可能になったようです。


ではメソッド呼び出しですがこれは前述したように、インスタンスが入ってる変数を書いたあとに、ピリオドとメソッド名です。

動的メンバにアクセスするならこの書き方になって、
静的メンバにアクセスするなら[クラス名]::メンバ名になります。
これも、外部ライブラリを使うときと同じ扱いですね。


$instance = New-Object Dog('ハチ公', 10)
$result = $instance.MakeSound()


$resultに戻り値を入れたので、これで'ワン'が入ります。
変数に入れなければ標準出力に'ワン'と表示されるはずです。


今はまだフィールドの意義がないので、Dogクラスに自分の名前を名乗るSayMyNameメソッドを追加してみましょう。

class Dog {
... (省略) ...
[string] SayMyName() {
return $this.Name + 'だワン'
}
}



これで呼び出します。

$instance = New-Object Dog('ハチ公', 10)
$result = $instance.SayMyName()


$resultには'ハチ公だワン'が入ります。
クラスっぽくなりましたね。



さて、基本は以上のようなものですが、継承も可能なので、ここからは継承をしたいと思います。
DogクラスをAnimalクラスの派生クラスに変更しましよう。

まずは、Animalクラスを定義します。

class Animal {
[string] $Name
[int] $Age

Animal([string]$name, [int]$age) {
$this.Name = $name
$this.Age = $age
}
}


Animalクラスは如何にも抽象クラスにするべきなのですが、PowerShellでは現状では抽象クラスは作れないようです。


今はまだクラスというのはすごいシンプルな機能しかなくて、継承もすごい意味を持つようなものではなく、単純に継承元の内容をこっちに持ってくる程度の意味しかないと思います。

記事「本「オブジェクト指向でなぜつくるのか」の感想、要約」で、継承することには、実装の継承とインターフェースの継承という二つの側面があるという話をしていましたが、

PowerShellでは実装の継承だけということですね。
まあ、根本的に型の概念が弱いので、あたりまえではありますが。


なので、ここからのシナリオは、Catクラスも定義して、Dogを鳴かせたらワンと鳴いて、Catならニャーと鳴くということなんですが、

MakeSoundメソッドをAnimalクラスで定義して共通のものにしておくということを、現状のPowerShellではしません。


どうするかというと、普通にDogクラスにもCatクラスにもMakeSoundメソッドを定義しておいて、呼び出すだけです。

C#などと違って、メソッドのディスパッチはダックタイピングになっているので、MakeSoundがあれば呼ばれるし、さもなくば何も起きません。


なのでAnimalクラスはポリモーフィズムのためにはなりませんし、

じゃあDogクラスもAnimalクラスも入る共通型の定義としては役立つのかといえば、
そもそもPowerShellでは変数の型を設定しなくてもいいので、役立ちません。

もちろん、変数の型を明示するということであれば、Animal型を設定することになるので、Animalクラスを作っておくことは意味があります。


というわけで、ようやくですがDogクラスにAnimalクラスを継承させます。
クラスの継承は、クラス名のあとにコロン、そして継承するクラス名、となります。


class Dog : Animal {
[string] MakeSound() {
return 'ワン'
}

[string] SayMyName() {
return $this.Name + 'だワン'
}
}

SayMyNameで、Animalクラスにて定義したNameにアクセスしていますが、
C#ならthisかbaseの二種類でアクセスできるものを、PowerShellにはbaseに相当するものはありません。

なので、親クラスと子クラスで競合する名前のものがあってもエラーにはならないのですが、そのとき子クラスから親クラスのものにアクセスすることはできません。


じゃあ子クラスから親クラスの別の名前のものを呼んで、そこから$thisで親クラスの競合しているものにアクセスしたらどうなるのかというと、

C#と違って、親クラスに書いてある$thisなのに、子クラスのほうが呼ばれます。

こういう挙動を見るところ、本当にPowerShellでは単純に親クラスに書かれてあるものを、子クラスにもコピーしてくるというのが継承ということなのかもしれません。

そして同じ名前のものは単純に上書きするということかもしれません。


つまりは、同じ名前のメソッドが親と子で競合しているときに、親か子のどちらかのインスタンスを変数にいれて、そのメソッドを呼び出すとき、
変数の型指定によらず、インスタンスが親なら親、子なら子のほうのメソッドが呼ばれます。

これが実質的にオーバーライドに相当していると思います。
C#と違って親の競合しているメソッドを隠すという選択肢はないです。



さて、本題に戻しますが、実は上のDogクラスの定義だとエラーになります。

Animalクラスのコンストラクタは引数ありなのに、Dogクラスのほうでその対処をしていないからです。

ということで、Dogクラスのコンストラクタで、Animalクラスのコンストラクタを明示的に呼び出します。

それにはC#と同じで、コンストラクタ名を書いたあと、コロンを書いて、baseと書いて、引数リストを括弧で囲んで書きます。


class Dog : Animal {
Dog([string]$name, [int]$age) : base($name, $age) { }
... (略) ...
}


となります。
これで、Dogクラスは定義完了です。

次は、同じようにCatクラスを定義します。


class Cat : Animal {
Cat([string]$name, [int]$age) : base($name, $age) { }

[string] MakeSound() {
return 'ニャー'
}

[string] SayMyName() {
return '吾輩は猫である。名前はまだない。'
}
}



これで、定義はすべて完了です。
挙動を確認してみます。

[Animal] $instance = New-Object Dog('ハチ公', 10)
$result1 = $instance.MakeSound()
$result2 = $instance.SayMyName()

[Animal] $instance = New-Object Cat('キティ', 3)
$result3 = $instance.MakeSound()
$result4 = $instance.SayMyName()



これは、$result1は'ワン'、$result2は'ハチ公だワン'となります。
そして、$result3は'ニャー'、$result4は'吾輩は猫である。名前はまだない。'となります。

というわけで、一応はポリモーフィズムっぽいことができるというわけです。


ただし、これはまったく異なる型の二つのインスタンスを作成して、それぞれメソッドを呼び出しているというコードでしかないと思います。

一応、Animal型で共通化しているので、ここでは恩恵はないですが、
まあ場合によっては共通化による恩恵を受けられることもある、ということです。



というわけで、PowerShellのクラス定義はこんなものとなります。

だいぶシンプルですね。
今後、機能追加されると思います。

現状では、継承がどういう仕組みになっているのかは非常に疑問ですね。


C#とかでは、はっきりとクラスが継承関係になっているという情報の上でやっていると思うんですが、

PowerShellでは単純に内容を持ってきて、一応は子クラスは親クラスに変換できるようにする、ということなのか、
それともちゃんと本格的なものになっているのか、どうなんでしょうか。


まあ今後、機能が追加されていくにつれて、そんなシンプルな仕組みでは対応しきれない機能もあると思うので、それがどうなるかで判断できると思います。


ちなみに、クラスだけじゃなくて列挙体も定義できるようになったそうです。

これも場合によっては使う機能ですね。

tag: PowerShell スクリプト CUI クラス メソッド 継承 オブジェクト指向 ダックタイピング

コメント

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/454-d311177f
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。