개요
Go 언어는 간결하고 효율적인 설계를 중시하며, 객체 지향 프로그래밍 패러다임을 직접적으로 지원하지 않습니다. 그러나 캡슐화(encapsulation)와 임베딩(embedding) 같은 개념을 통해 객체 지향 프로그래밍의 핵심 개념들을 구현할 수 있습니다.
1. 캡슐화 (Encapsulation)
캡슐화는 객체의 데이터를 외부로부터 숨기고, 그 데이터에 접근하거나 조작하는 방법을 제공하는 개념입니다. Go에서는 캡슐화를 구조체와 메서드를 통해 구현할 수 있습니다.
1.1 접근 제어
Golang에서는 대소문자를 사용하여 접근 제어를 수행합니다:
- 대문자로 시작하는 필드나 메서드: 외부에서 접근 가능 (public)
- 소문자로 시작하는 필드나 메서드: 같은 패키지 내에서만 접근 가능 (private)
1.2 구조체 (Struct)
Go에서는 struct를 사용하여 데이터를 캡슐화합니다. 구조체는 관련된 데이터를 그룹화하여 하나의 단위로 만들 수 있게 해줍니다.
// 생략...
type Person struct {
Name string // 외부에서 접근 가능
age int // 같은 패키지 내에서만 접근 가능
}
func (p *Person) SetAge(age int) {
if age > 0 {
p.age = age
}
}
func (p Person) GetAge() int {
return p.age
}
func main() {
person := Person{Name: "Alice"}
person.SetAge(30)
fmt.Printf("%s is %d years old\n", person.Name, person.GetAge())
}
이 예제에서 age 필드는 private이므로 직접 접근할 수 없고, SetAge와 GetAge 메서드를 통해서만 접근 및 수정이 가능합니다.
1.3 완전한 캡슐화
앞서 설명한 예제에서 Name 필드가 public으로 선언되어 있어 외부에서 직접 접근 및 수정이 가능했습니다. 이는 완전한 캡슐화를 위반하는 것으로, 데이터의 무결성을 해칠 수 있는 잠재적 위험이 있습니다.
문제점
type Person struct {
Name string // 외부에서 접근 가능
age int // 같은 패키지 내에서만 접근 가능
}
func main() {
person := Person{Name: "Alice"}
person.Name = "" // 유효하지 않은 이름으로 직접 수정 가능
}
이 코드에서 Name 필드는 외부에서 직접 수정이 가능하므로, 빈 문자열이나 유효하지 않은 이름으로 설정될 수 있습니다.
3.2 캡슐화 개선
더 나은 캡슐화를 위해 모든 필드를 private으로 만들고, getter와 setter 메서드를 통해 접근하도록 개선할 수 있습니다.
type Person struct {
name string
age int
}
func NewPerson(name string) Person {
return Person{name: name}
}
func (p *Person) SetName(name string) error {
if name == "" {
return errors.New("name cannot be empty")
}
p.name = name
return nil
}
func (p Person) GetName() string {
return p.name
}
func (p *Person) SetAge(age int) error {
if age < 0 {
return errors.New("age cannot be negative")
}
p.age = age
return nil
}
func (p Person) GetAge() int {
return p.age
}
func main() {
person := NewPerson("Alice")
err := person.SetName("")
if err != nil {
fmt.Println("Error:", err)
}
fmt.Println("Name:", person.GetName())
}
캡슐화 정리
- 데이터 무결성: 유효하지 않은 데이터가 설정되는 것을 방지합니다.
- 유연성: 내부 구현을 변경하더라도 외부 인터페이스는 그대로 유지할 수 있습니다.
- 디버깅 용이성: 데이터 변경 지점을 명확히 알 수 있어 디버깅이 쉬워집니다.
2. 임베딩 (Embedding)
Golang Embedding(임베딩)은 다른 구조체를 현재의 구조체 내에 포함시키는 것을 의미합니다. 임베딩된 구조체는 필드와 메서드를 현재 구조체에서 직접 사용할 수 있으며, 이는 마치 상속처럼 동작하지만, Go에서는 명시적인 상속 개념이 없기 때문에 임베딩으로 이 기능을 구현합니다.
2.1 기본 임베딩
type Address struct {
Street string
City string
Country string
}
type Employee struct {
Name string
Address // 임베딩된 구조체
}
func main() {
emp := Employee{
Name: "Bob",
Address: Address{
Street: "123 Main St",
City: "Anytown",
Country: "USA",
},
}
fmt.Println(emp.Name) // 직접 접근
fmt.Println(emp.Street) // 임베딩된 필드에 직접 접근
}
이 예제에서 Employee 구조체는 Address 구조체를 임베딩하고 있습니다. 이를 통해 Employee의 인스턴스에서 Address의 필드에 직접 접근할 수 있습니다.
2.2 메서드 임베딩
임베딩된 구조체의 메서드도 외부 구조체에서 사용할 수 있습니다.
type Printer struct{}
func (p Printer) Print() {
fmt.Println("Printing...")
}
type Scanner struct{}
func (s Scanner) Scan() {
fmt.Println("Scanning...")
}
type PrinterScanner struct {
Printer
Scanner
}
func main() {
ps := PrinterScanner{}
ps.Print() // Printer의 메서드 호출
ps.Scan() // Scanner의 메서드 호출
}
이 예제에서 PrinterScanner 구조체는 Printer와 Scanner를 임베딩하여 두 구조체의 메서드를 모두 사용할 수 있습니다.
2.3 주의할 점
필드 충돌
임베딩된 구조체와 임베딩을 받는 구조체가 동일한 이름의 필드를 가지는 경우, 필드 충돌이 발생할 수 있습니다. 이 경우, Go는 가장 바깥 구조체의 필드를 우선적으로 사용합니다. 필요한 경우, 명시적으로 필드명을 지정해야 합니다.
type Address struct {
City string
}
type Person struct {
City string
Address
}
func main() {
p := Person{City: "Los Angeles", Address: Address{City: "San Francisco"}}
fmt.Println(p.City) // Los Angeles 출력
fmt.Println(p.Address.City) // San Francisco 출력
}
'프로그래밍 > Golang' 카테고리의 다른 글
Golang Date(날짜, 시간) 계산, 포맷팅 (0) | 2024.09.10 |
---|---|
Golang 함수 []any(슬라이스 파라미터) 와 ...any(가변인자) (0) | 2024.08.08 |
Golang Error 인터페이스 (0) | 2024.08.07 |
Golang에서 문자열 자르기 (0) | 2024.08.07 |
Golang 정수 타입과 아키텍처 의존성 (0) | 2024.08.06 |