패키지 수준의 캡슐화
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 48번째.
이전 레슨에서 필드 수준의 가시성을 다루었지만, Go의 캡슐화는 실제로는 패키지 수준에서 작동합니다. 동일한 패키지 내의 모든 것은—어느 파일에 있든 상관없이—내보내지 않은(unexported) 필드와 함수에 접근할 수 있습니다.
여러 개의 파일로 구성된 패키지를 고려해 봅시다:
// account/account.go
package account
type Account struct {
balance int // 외부로 노출되지 않음 (unexported)
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // 가능함 - 같은 패키지
}
두 파일 모두 account 패키지에 속하므로, helper.go는 내보내지 않은(unexported) balance 필드에 직접 접근할 수 있습니다. 이는 의도된 것으로, 내부 접근 권한을 유지하면서 패키지 로직을 여러 파일로 나눌 수 있게 해줍니다.
하지만 패키지 외부의 코드는 내보내지 않은(unexported) 멤버에 접근할 수 없습니다:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // 작동함 - 내보낸(exported) 메서드
// acc.balance = 50 // 에러 - 내보내지 않은(unexported) 필드
// account.ResetAccount // ResetAccount가 내보내졌다면 작동함
}
이 패키지 수준의 경계는 Go의 주요 캡슐화 메커니즘입니다. 내부 코드는 자유롭게 협력하고 외부 코드는 내보낸(exported) API를 통해서만 상호 작용하는 응집력 있는 단위로 패키지를 설계합니다. 각 패키지를 명확한 공개 인터페이스를 가진 독립적인 모듈로 생각하십시오.
챌린지
쉬움동일한 패키지 내의 여러 파일이 비공개(unexported) 필드에 직접 접근하여 협업하는 동시에, 외부 코드로부터는 해당 데이터를 숨기는 방법을 보여주는 재고 관리 시스템을 구축해 보겠습니다.
main 패키지 내에서 함께 작동하는 세 개의 파일을 생성하게 됩니다:
product.go: 공개(exported) 필드인Name과 비공개(unexported) 필드인quantity(int)를 가진Product구조체를 정의합니다.[Name]: [quantity] in stock형식을 반환하는 공개 메서드Info() string를 추가합니다.inventory.go: 비공개 필드인quantity에 직접 접근하여 제품을 다루는 헬퍼 함수들을 생성합니다 (동일한 패키지에 있기 때문에 가능합니다). 다음을 구현하세요:Restock(p *Product, amount int)- 제품의 수량에 해당 금액을 더합니다.Sell(p *Product, amount int) bool- 재고가 충분하면 수량에서 해당 금액을 뺍니다. 성공하면true를, 재고가 부족하면false를 반환합니다.NewProduct(name string, initialQty int) *Product- 주어진 이름과 초기 수량으로 제품을 생성하는 생성자입니다.
main.go: 입력에서 제품 상세 정보를 읽고, 생성자를 사용하여 제품을 만든 다음, 재고 작업을 수행하고 결과를 표시합니다. 각 작업 후에 제품 정보를 출력하여 업데이트된 상태를 보여줍니다.
다음 입력이 제공됩니다:
- 1행: 제품 이름
- 2행: 초기 수량 (정수)
- 3행: 재입고 수량 (정수)
- 4행: 판매 수량 (정수)
제품을 생성한 후, 초기 정보를 출력합니다. 그런 다음 재입고를 하고 정보를 다시 출력합니다. 마지막으로 판매를 시도하고 업데이트된 정보(성공 시) 또는 Sale failed: insufficient stock(수량이 부족한 경우)을 출력합니다.
예를 들어, Laptop, 10, 5, 8이 주어지면 출력은 다음과 같아야 합니다:
Laptop: 10 in stock
Laptop: 15 in stock
Laptop: 7 in stock그리고 Phone, 3, 2, 10이 주어지면 출력은 다음과 같아야 합니다:
Phone: 3 in stock
Phone: 5 in stock
Sale failed: insufficient stock여기서 핵심은 inventory.go가 product.go와 동일한 패키지에 있기 때문에 비공개 필드인 quantity를 직접 읽고 수정할 수 있다는 점입니다. 이것이 바로 패키지 수준의 캡슐화가 작동하는 방식입니다. 내부 협업은 자유롭게 이루어지면서 데이터는 외부 패키지로부터 보호됩니다.
치트 시트
Go의 캡슐화는 파일 수준이 아니라 패키지 수준에서 작동합니다. 같은 패키지 내의 모든 파일은 정의된 파일에 관계없이 내보내지 않은(unexported) 필드와 함수에 접근할 수 있습니다.
패키지 수준 접근
내보내지 않은 멤버는 동일한 패키지의 모든 파일에서 접근할 수 있습니다:
// account/account.go
package account
type Account struct {
balance int // 내보내지 않음
}
func (a *Account) Deposit(amount int) {
a.balance += amount
}
// account/helper.go
package account
func ResetAccount(a *Account) {
a.balance = 0 // 작동함 - 동일한 패키지
}
외부 패키지 제한
패키지 외부의 코드는 내보내지 않은 멤버에 접근할 수 없습니다:
// main.go
package main
import "account"
func main() {
acc := &account.Account{}
acc.Deposit(100) // 작동함 - 내보낸 메서드
// acc.balance = 50 // 에러 - 내보내지 않은 필드
}
이러한 설계를 통해 내부 접근 권한을 유지하면서 패키지 로직을 여러 파일로 분할할 수 있으며, 명확한 공개 인터페이스를 가진 응집력 있는 단위를 만들 수 있습니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 제품 이름 읽기
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
// 초기 수량 읽기
initialQtyStr, _ := reader.ReadString('\n')
initialQty, _ := strconv.Atoi(strings.TrimSpace(initialQtyStr))
// 재입고 수량 읽기
restockStr, _ := reader.ReadString('\n')
restockAmount, _ := strconv.Atoi(strings.TrimSpace(restockStr))
// 판매 수량 읽기
sellStr, _ := reader.ReadString('\n')
sellAmount, _ := strconv.Atoi(strings.TrimSpace(sellStr))
// TODO: NewProduct 생성자를 사용하여 새 제품 생성
// TODO: 초기 제품 정보 출력
// TODO: 제품 재입고 및 정보 출력
// TODO: 판매 시도 및 결과 출력
// 판매에 성공하면 업데이트된 정보를 출력
// 판매에 실패하면 "Sale failed: insufficient stock" 출력
fmt.Println() // 실제 출력으로 대체하세요
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서