서론
객체지향 프로그래밍(OOP) 패러다임으로 개발하다보면 class를 잘 사용하는 것이 잘하는 개발자일 것이다. 이번에 공부해본 GO 언어는 class를 사용하지 않는 객체지향 언어이다.
어떻게 class 없이 객체지향 프로그래밍이 가능한지 살펴보자.
OOP의 3요소
- 캡슐화: 정보 은닉
- 상속: 재사용 + 확장
- 다형성: 사용편의
클래스스럽게만 사고하고 개발했다면 GO를 만났을 때 충격받을 것이다. GO는 코드의 간결성과 성능을 위하여 클래스 문법을 직접적으로 제공하지 않는다.
GO의 객체지향
- 인터페이스 (Interfaces):
- 인터페이스는 함수의 집합으로 정의되며, 해당 인터페이스를 구현하는 구조체는 자동으로 인터페이스를 따르게 .
- 인터페이스를 사용하여 다형성을 지원하고, 코드의 유연성과 재사용성을 향상시킴.
- 구조체 (Structs):
- 구조체는 데이터를 그룹화하는 데 사용되며, 필드의 집합으로 정의함.
- 구조체 내에 메서드를 연결할 수 있습니다. 이러한 메서드는 특정 구조체 인스턴스에 바인딩되며, 해당 구조체의 동작을 구현함.
- 함수 (Methods):
- 메서드는 특정 타입에 연결된 함수로서, 해당 타입에 대한 동작을 정의.
- 메서드는 구조체에 연결되어 해당 구조체의 데이터와 동작을 함께 캡슐화합니다.
- 구조체의 메서드를 정의하고 호출함으로써 객체의 동작을 구현합니다.
GO의 객체지향 코드
인터페이스
package main
import "fmt"
// 도형 인터페이스 구현(추상화)
type Shape interface {
Area() float64
Perimeter() float64
}
// Circle 구조체 구현
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14 * c.Radius
}
// Rectangle 구조체 구현
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 실행
func main() {
// Circle, Rectangle 선언
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 3, Height: 4}
//
shapes := []Shape{circle, rectangle}
for _, shape := range shapes {
fmt.Println("Area:", shape.Area())
fmt.Println("Perimeter:", shape.Perimeter())
fmt.Println()
}
}
// 실행 결과:
// Area: 78.5
// Perimeter: 31.400000000000002
//
// Area: 12
// Perimeter: 14
구조체
package main
import "fmt"
// 구조체 선언.
type Person struct {
Name string
Age int
}
// 구조체를 함수 붙이기.
func (p Person) SayHello() {
fmt.Printf("Hello, my name is %s and I'm %d years old.\n", p.Name, p.Age)
}
func main() {
person := Person{Name: "John", Age: 30}
person.SayHello()
}
// 실행 결과:
// Hello, my name is John and I'm 30 years old.
메서드
package main
import "fmt"
type Counter struct {
count int
}
func (c *Counter) Increment() {
c.count++
}
func (c *Counter) Decrement() {
c.count--
}
func (c *Counter) GetCount() int {
return c.count
}
func main() {
counter := Counter{}
counter.Increment()
fmt.Println("Count:", counter.GetCount())
counter.Increment()
fmt.Println("Count:", counter.GetCount())
counter.Decrement()
fmt.Println("Count:", counter.GetCount())
}
// 실행결과:
// Count: 1
// Count: 2
// Count: 1
GO 객체지향에 대한 나의 생각.
단점:
- 평소 TS를 사용하여 개발하였기 때문에, 연관관계를 맺어줄 수 없으니 타입 체크가 불안정해보이긴 했다.
- 다형성이 없다. 인터페이스로 비슷하게 구현하는 방법은 있음.
장점:
- c언어로 개발하는 느낌이라 포인터가 존재하여 확실히 편했다.
- 경량화가 굉장히 잘되어 있다. 현재 참여하고 있는 팀의 서버 API 로그와 메모리 사용량을 보고 느꼈다.
위 장단점이 GO언어의 철학에 맞기 때문에 합리적이긴 하지만, 다형성만은 좀 제대로 제공해줬으면 한다.. 다형성이 없어 ORM이나 스웨거같은 라이브러리가 굉장히 약하다는걸 느꼈다.