Featured image of post [PowerShell] 77. PowerShell 클래스(class 키워드)

[PowerShell] 77. PowerShell 클래스(class 키워드)

PowerShell 5.0+ class 키워드로 사용자 정의 타입을 만드는 법과 프로퍼티·생성자·메서드 문법, 정적(static) 멤버, 27장 PSCustomObject와의 차이, ForEach-Object -Parallel에서 클래스를 안전하게 쓰는 법을 정리한 챕터다.

개요

PowerShell 5.0부터 class 키워드로 사용자 정의 타입을 직접 만들 수 있다. 27장에서 [PSCustomObject]로 임시 객체를 만들었다면, class는 그 객체가 항상 같은 속성 구조와 동작(메서드)을 갖도록 청사진을 정의한다는 점이 다르다 — [PSCustomObject]가 즉석에서 찍어낸 객체 하나라면, 클래스는 같은 모양의 객체를 몇 개든 찍어낼 수 있는 틀이다.

정신 모델은 “클래스는 속성(데이터)과 메서드(그 데이터로 할 수 있는 동작)를 하나로 묶은 새로운 타입 정의"라는 것이다. 인스턴스를 만들면 [타입]::new(...) 문법으로 그 청사진에서 실제 객체가 찍혀 나온다.

사용법

1
2
3
4
5
6
class <클래스이름> {
    [타입]$속성
    <클래스이름>([타입]$매개변수) { <생성자 로직> }
    [반환타입] 메서드이름([타입]$매개변수) { <메서드 로직> }
}
$인스턴스 = [<클래스이름>]::new(<인자>)

종류

요소설명
프로퍼티클래스 스코프에 선언된 변수, 인스턴스마다 별개의 값을 가짐
생성자클래스와 이름이 같은 메서드, [클래스]::new(...)로 호출됨
메서드항상 반환 타입을 명시해야 함(값이 없으면 [void])
static 키워드인스턴스 없이 클래스 자체에 속하는 프로퍼티·메서드([클래스]::멤버로 접근)
hidden 키워드Get-Member(11장)와 자동완성에서 숨기되, 여전히 공개 멤버로 접근은 가능
상속(class A : B)단일 상속만 지원(다중 상속 불가)

예시

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Book {
    [string]$Title
    [string]$Author
    [int]$PageCount

    Book([string]$Title, [string]$Author) {
        $this.Title = $Title
        $this.Author = $Author
    }

    [timespan] GetReadingTime() {
        if ($this.PageCount -le 0) { throw '페이지 수를 알 수 없습니다.' }
        return [timespan]::new(0, $this.PageCount * 2, 0)   # 페이지당 2분으로 가정
    }

    [string] ToString() {
        return "$($this.Title) by $($this.Author)"
    }
}

$book = [Book]::new("The Hobbit", "J.R.R. Tolkien")   # 생성자 호출
$book.PageCount = 310
$book.GetReadingTime()                                    # 메서드 호출
"$book"                                                     # ToString() 오버라이드가 자동 적용됨

$book2 = [Book] @{ Title = "1984"; Author = "George Orwell" }  # 해시테이블로 기본 생성자 활용(44장)

class Counter {
    static [int]$Total = 0            # 모든 인스턴스가 공유하는 static 프로퍼티
    Counter() { [Counter]::Total++ }
}
[Counter]::new(); [Counter]::new()
[Counter]::Total                       # 2 — 인스턴스와 무관하게 공유됨

주의사항·함정

클래스는 기본적으로 ForEach-Object -Parallel(79장)에서 안전하지 않다: PowerShell 클래스는 자신이 정의된 런스페이스(Runspace)에 결속돼 있어, 병렬로 실행되는 다른 런스페이스에서 메서드를 호출하면 상태가 손상되거나 교착 상태(deadlock)에 빠질 수 있다. 병렬 처리에서 안전하게 쓰려면 클래스 선언에 [NoRunspaceAffinity()] 특성(PowerShell 7.4+)을 추가해야 한다.

모듈에 정의한 클래스는 Import-Module만으로는 자동으로 딸려 오지 않는다: 75장에서 배운 Import-Module은 함수·별칭·변수는 가져오지만 클래스·열거형 정의는 별도로 취급한다 — 클래스를 쓰려면 스크립트 맨 위에 using module <모듈이름> 문을 추가해야 한다. 이 차이를 모르면 “모듈을 임포트했는데 왜 타입을 찾을 수 없다고 나오지?“라는 혼란에 빠지기 쉽다.

세션 도중에 클래스를 다시 로드하거나 언로드할 수 없다: 함수는 다시 정의하면 즉시 갱신되지만, 클래스는 한 번 정의되면 세션이 끝날 때까지 그 정의가 고정된다. 개발 중 클래스 코드를 수정했다면 새 세션을 시작해야 변경 사항이 반영된다 — Import-Module -Force로도 해결되지 않는다는 점이 다른 모듈 멤버와 다르다.

생성자·메서드의 매개변수에는 검증 특성(ValidateSet 등)을 쓸 수 없다: 59장에서 배운 [ValidateNotNullOrEmpty()] 같은 매개변수 검증 속성은 클래스의 생성자·메서드 매개변수에는 적용되지 않는다. 검증이 필요하다면 메서드 본문 안에서 직접 확인 로직을 작성해야 한다.

이식성: C#의 class 문법을 상당 부분 그대로 계승해, 접근 지정자(hiddenprivate)와 static 키워드 사용법이 비슷하다. Python의 class와도 개념적으로 유사하지만, Python은 다중 상속을 지원하는 반면 PowerShell 클래스는 단일 상속만 허용한다는 차이가 있다. Bash·CMD에는 객체지향 타입 시스템 자체가 없다.

Reference