PowerShellのコンストラクタまとめ 

この記事は、[PowerShell Advent Calendar 2017](https://qiita.com/advent-calendar/2017/powershell)の14日目の記事です。

PowerShellのコンストラクタについてまとめました。

# 動作環境
Windows 10
PowerShell 5.1
Visual Studio Code (拡張機能として ms-vscode.powershell を使用)

```powershell
$PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.15063.726
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.15063.726
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
```

# コンストラクタの役割
この記事はあくまで文法について焦点を当てた記事なので、簡潔に述べます。

　**1. 初期化と不可分一体の処理を定義する**
　　→ インスタンス生成する上で前提となる処理を実装する。
　**2. インスタンスにメモリを割り当てる**
　　→ 基本、PowerShellプログラマーは気にする必要はない。

# 基本的な定義方法

まずは、基本となる使い方から記載しておきます。

## 宣言

他のプログラム言語と同じように書けます。

```powershell
class Employee {
    [int] $Id
    [String] $Name
    [String] $Department

    Employee([int] $Id, [String] $Name) {
        $this.Id = $Id
        $this.Name = $Name
        $this.Department = "未配属"
    }

    [String] ToString() {
        return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
    }
}
```

## 呼び出し

コンストラクタを呼び出す方法は2通り存在します。
基本は前者を使用し、動的に生成するクラス名を変更したい場合だけ後者を用いる感じでいいと思います。

### [クラス名]::new()

new [クラス名] でないことに注意してください。

```powershell
$emp = [Employee]::new(1, "Smith")
$emp.ToString()
# Id=1, Name=Smith, Department=未配属
```

### New-Objectコマンドレット

-TypeName は省略可能です。

```powershell
$className = "Employee"
$emp = New-Object -TypeName $className -ArgumentList 1, "Smith"
$emp.ToString()
# Id=1, Name=Smith, Department=未配属
```

## デフォルトコンストラクタ

引数なしのコンストラクタを定義することができます。
特に処理を書く必要がない場合は、宣言する必要はありません。

```powershell
class Employee {
    [int] $Id
    [String] $Name
    [String] $Department

    Employee() {
        $this.Id = -1
        $this.Name = "不明"
        $this.Department = "未配属"
    }

    [String] ToString() {
        return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
    }
}

> [Employee]::new().ToString()
# Id=-1, Name=不明, Department=未配属
```

## オーバーロード

コンストラクタをオーバーロードで定義することはできますが、
他のオーバーロードをthisで呼ぶことはできません。

[Class-definition syntax should support calling the same class's other constructors #3820](https://github.com/PowerShell/PowerShell/issues/3820)

> PowerShell is not C# and it was not a goal to replicate all of C#'s capabilities.
> 
> For this specific area - we added the : base() syntax because some syntax is required to call a base class constructor.
> 
> We did not add : this() syntax because there is a reasonable alternative that is also somewhat more intuitive syntax wise - introduce a shared initialization method and just call it,

上のIssueの要点をまとめると、PowerShell開発者の一人であるShirk氏曰く、
親クラスのコンストラクタ呼び出しをサポートしたのは文法上必要であったため。
オーバーロードの場合、初期化する際の共通処理をメソッドで定義しておけば、
可読性をそこまで落とすことなく代用できるため意図してサポートしていない、とのことです

提示されているサンプルコードでは `hidden Init()` と定義し、
そこに各コンストラクタ共通の処理を実装していけばいいと書いているので、
慣例として使用する程度の心づもりでいいのではと思います。

```powershell
class Employee {
    [int] $Id
    [String] $Name
    [String] $Department

    # ------ Constructors ------------------

    Employee([int] $Id, [String] $Name, [String] $Department) {
        $this.Init($Id, $Name, $Department)
    }

    Employee([int] $Id, [String] $Name) {
        $this.Init($Id, $Name)
    }

    Employee() {
        $this.Init()
    }

    # ------ Initializing for constructors ------------------

    hidden Init([int] $Id, [String] $Name, [String] $Department) {
        $this.Id = $Id
        $this.Name = $Name
        $this.Department = $Department
    }

    hidden Init([int] $Id, [String] $Name) {
        $this.Init($Id, $Name, "未配属")
    }

    hidden Init() {
        $this.Init(-1, "不明")
    }

    [String] ToString() {
        return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
    }
}

> [Employee]::new().ToString()
# Id=-1, Name=不明, Department=未配属
> [Employee]::new(2, "Allen").ToString()
# Id=2, Name=Allen, Department=未配属
> [Employee]::new(3, "Ward", "朝読書推進部").ToString()
# Id=3, Name=Ward, Department=朝読書推進部
```

## デストラクタ（不可能）

結構ググりましたが、どうやらないみたいです。
ただ、断言している情報源がなかったので、
この記事内では**ドキュメント内で言及されていない**、としておきます。

## 継承

PowerShellは継承もサポートしていますが、
同じファイル内に親クラスと子クラスの両方を定義しておく必要があります。

```powershell
class SalesMan : Employee {
    [String[]] $Clients

    SalesMan([int] $Id, [String] $Name, [String] $Department, [String[]] $Clients) : base($Id, $Name, $Department) {
        $this.Clients = $Clients
    }

    [String] ToString() {
        return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department + ", Clients=" + $this.Clients
    }
}

> [SalesMan]::new(4, "Martin", "シエスタ推進部", @("Miller", "Ford", "James")).ToString()
# Id=4, Name=Martin, Department=シエスタ推進部, Clients=Miller Ford James
```

ちなみに継承をしなくてもbaseキーワード自体は指定可能です。
これは全てのクラスは基本クラスであるSystem.Objectを継承するように動作するためです。
なので、正確に言うと、継承をしてないクラスなんてない、ということです。
System.Objectにはデフォルトコンストラクタが定義されているので、以下のように書けます。
（書いたところで意味はないです。）

```powershell
class Employee {
    [int] $Id
    [String] $Name

    Emploee() : base() {
        $this.Id = -1
        $this.Name = "不明"
    }
}
```

### 外部ファイル（不可能）

サブクラスを定義する場合、親クラスと同じファイル内に書く必要があることから、
親クラスのコンストラクタの呼び出しも外部ファイルからはできません。
コンストラクタというよりかは、クラス構文上の制約です。

ちなみに、以下検証結果。

```powershell
. .\Employee.class.ps1

class Clerk : Employee {
    Clerk([int] $Id, [String] $Name): base($Id, $Name) {
    }
}

> [Clerk]::new(5, "Blake")
: + class Clerk : Employee {
: +                 ~~~~~~
: 型 [Employee] が見つかりません。
```

# 初期化される順番

PowerShellでインスタンス生成時に初期化される順番はいたって他のオブジェクト指向言語と
同じ順番となります。

```powershell
[int] $Script:count = 0

class Parent {
    [int] $second = ++$Script:count
    [int] $third

    Parent() {
        $this.third = ++$Script:count
    }
}

class Child : Parent {
    [int] $first = ++$Script:count
    [int] $forth
    
    Child() : base() {
        $this.forth = ++$Script:count
    }
}
$child = [Child]::new()
$child.first    # 1
$child.second   # 2
$child.third    # 3
$child.forth    # 4
```

# デザインパターン

コンストラクタに限った話題ではないですが、いくつかまとめておきます。

## 抽象クラス

インスタンス生成することを禁止し、継承したサブクラスでインスタンス生成させるようにしたい場合、
以下のようなやり方があります。
ただ、実際に実行してみないとエラーにならないため、使い勝手は微妙です。

```powershell
class Foo {
    Foo () {
        if ([Foo] -eq $this.GetType()) {
            throw("Class [Foo] must be inherited")
        }
    }
}
```

## Singleton

同一プロセス内に生成されるインスタンス数を一つだけにする方法です。
他の実装方法もあるかと思いますので、細かいやり方は実装者のお好みで。

```powershell
class Singleton {
    static [Singleton] $Instance
    [Object] $SingletonTarget

    hidden Singleton() {
    }

    static [Singleton] GetInstance() {
        if ([Singleton]::instance -eq $null) {
            [Singleton]::instance = [Singleton]::new()
        }
        return [Singleton]::instance
    }
}
```

# 参考資料
[公式ドキュメント｜about_Classes](https://technet.microsoft.com/ja-jp/library/dn820211.aspx)
[Powershell v5 Classes & Concepts](https://xainey.github.io/2016/powershell-classes-and-concepts/)
[Class-definition syntax should support calling the same class's other constructors #3820](https://github.com/PowerShell/PowerShell/issues/3820)