1. 고루틴

-  고프로그램을 구성하는 가장 기본적인 단위 중 하나이다. GO 코드의 시작점인 main 함수 또한 고루틴 으로 할당되어 실행된다.
- 고루틴은 다른 코드와 함께 동시에 실행되는 함수라고 이해하면 쉽다.

사용방법은 Go Tour에서 확인 바란다. 아래 간단한 예제를 첨부한다.

func hello(){
	fmt.Println("안녕 잘지내?")
}
func main(){
	go hello()
	//익명함수 방식
    func(){
      fmt.Println("응 잘지내")
    }()
}

고루틴은 OS 스레드 인가? 그린 스레드 인가?라는 주제를 시작으로 이야기를 풀어간다. 

OS 스레드 란?

-OS 작업에 의해서 관리되는 스레드를 OS 스레드 라고 한다. 개별적인 스택, 레지스터 상태를 가지고 있으며, 커널이 스케쥴링을 하며 이는 CPU 코어 간의 부한 분산을 가능하게 합니다. 그러나 이로 인해 스레드 간의 교체에 따른 오버헤드를 컨택스트 스위치라고 일컬으며 이 비용이 높다는 단점이 존재한다.

 

그린 스레드란?

-그린 스레드란 프로그램 또는 런타임 시스템이 관리하는 가벼운 스레드로, 가상 스레드라고도 한다. OS 스레드 보다 생성과 관리비용이 훨씬 적지만 한 번에 하나의 OS 스레드에서만 실행될 수 있습니다.

 

고 루틴은 이 둘 중에 어디에도 분류할 수 없다. 그린스레드 보다 높은 수준의 추상화인 코루틴이다. 

코루틴 이란?

- 코루틴은 단순히 동시에 실행되는 서브루틴(함수, 클로져, 메서드) 로서 , 비선점적이다. 다시 말해 인터럽트 할 수 없다는 뜻이다. 이런 특성에 따라 코루틴은 2가지 특징이 있는데  중단과 재개 의 특징이 존재한다. 자신의 실행을 일시적으로 중단하고, 필요에 따라 다시 재개할 수 있다. 

 

아무리 이런 동시기능을 제공해 주는 기능이 있더라도 누군가는 이 동시 기능이 가능하게 임무분담을 해주어 햐는데 GO에서는 이 메커니즘을 M:N 스케쥴러라고 한다. 

가용한 그린스레드 보다, 많은 고 루틴이 존재한다면 스케쥴러는 사용가능한 스레드들로 고루틴을 재분배 하고 , 이고루틴이 대기상태가 되면 다른 고루틴이 실행될 수 있도록 한다.

M개의 그린스레드를 N 개의 OS 스레드에 맵핑한다는 의미이다. 프로세스 단위에서 스레드로 맵핑이 올라간다고 생각하면 생각보다 범위가 너무 넓어진다.그린스레드와 OS 스레드 중간 어딘가의 추상화 단계 에서 그린스레드 의 고 루틴 이 넘치면 이에 스레드가 할당되어 고루틴이 비선점적으로 실행된다 ? 스레드 와 고루틴 간의 통신 수단은 무엇이 될지 어떻게 진행될지 , 이들 간의 스위칭에 어떤 문제가 존재하는지 정말 많은 의문이 꼬리에 꼬리를 물지만 이에 대해 6장에서 보다 자세히 설명한다고 하니 그냥 이런 게 있나 보다 하고 넘어가자.

M 은 OS 스레드에 의해 할당되는 쓰레드의 모습을 표현 하였다. 이에 OS 쓰레드 하나에는 모든 P  가 붙게 되고 이는 하나의 메인 고 루틴 이 존재하고,
P 안에는 로컬큐 고루 틴들이 할당된다. 
M 은 단지 OS에 의해 할당받고 실행된다면 고 루틴은 이 M에 의해 실행이 된다고 이해하면된다.(https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html)


Go에서는 Folk-Join 모델을 따른다.

동시성 모델 중 하나로, 프로그램 어느 지점에서든 자식 분기를 만들어 미래 어느 시점에 다시 합쳐진다.

고에서는 단순히 go 키워드를 이용하면 위 표에서 포이는 포크의 한지점이 생성되는 것이다. 고 키워드 로직이 마무리가 된다면 join 지점에 의해 메인 고 루틴에 합류된다.

 

위에서 코루틴의 범위중 하나에 클로저라는 단어를 언급했다. 클로저 란 함수 의 실행 컨택스트 내에서 함수 실행환경을 캡처하여 함수호출시 동일한 환경을 제공해 준다. 아래 예제를 보자.

var wg sync.WaitGroup
greeting := "안녕 잘지내"
wg.Add(1)
go func(){
 defer wg.Done()
 greeting = "응 잘지내"
}()
wg.Wait()

fmt.Println(greeting)

이는 "응 잘 지내"를 반환한다. 고 루틴 은 자신이 생성된 곳과 동일한 주소 공간에서 실행되기 때문에, 가능한 일이다. 

 

var wg sync.WaitGroup
for _,a := range []string{"ㄱ","ㄴ","ㄷ","ㄹ"} {
	wg.Add(1)
    go func(){
    	defer wg.Done()
     	fmt.Printnl(a)
    }()
}
wg.Wait()

이 코드의 결괏값으로는 ㄹ,ㄹ,ㄹ,ㄹ 이 출력된다. 왜?

for 루프가 돌 때마다 a의 값을 가져가 사용하기 위해 클로저가 캡처된다고 생각해 보자. 그러나 스케쥴링된 고 루틴이 어느 시점에 실행될지 알 수가 없다. 미래의 어느 시점에든지 실행될 수 있기 때문에 어떤 값이 출력될지 정해져 있지 않다.

고 루틴이 실행되기 전에? for 루프가 종료될 확률이 높다는 의미이다. 

그렇게 된다면 루프의 범위를 벗어난다면 a는 어디서 참조를 해오는 것인가? 저 a의 변수가 고 루틴에서 참조가 가능하도록 힙 공간으로 옮겨진 것이다. 

함수의 인자값으로 던져줘서 복사를 시키면 원하는 결괏값을 반환하게 된다.

 

이는 다시 말해 고 루틴은 동일한 주소 공간에서 작동하며 단순 함수를 호스팅하는것 이것을 기억하고 넘어가자

(버려진 고루틴은 가비지 컬렉터에 의해 회수되지 않는다. 이에 대하여 4장에서 언급한다고 하니 인지만 하자)

 

고 루틴이 얼마나 가벼운지 에 대하여 설명하는데  이론상 8기가 램에 수백만개의 고루틴 생성이 가능하다.

 

여기서 이론상이라는 말이 붙은 이유는 콘텍스트 스위칭 이 라는 개념이 존재하기 때문이다. 

전환하기 위해 자신의 상태를 저장하고, 변경되는 프로세스의 상태를 불러오는 것을 말한다.

프로세스 가 너무 많아 프로세스 사이의 컨텍스트 스위칭에 모든 CPU 시간을 소모하느라 작업 수행이 불가능할 수 있으며,

OS 스레드를 사용하면 스위칭으로 인한 비용 발생에 따른 성능저하 가 존재한다. 

 

반면 소프트웨어 안에서의 스위칭의 비용은 상당히 저렴하다.

 

즉 위 절에서는 고 루틴으로 인한 성능문제 가 발생된다면? 명확하게 고 루틴으로 인한 성능 문제라는 사실이 밝혀졌을 때 그 비용을 논의해야 한다고 주장한다.

 

2. Sync 패키지

 go의 Sync 패키지 에는 저수준 메모리 동기화에 유용한 동시성 기본 요소들이 포함되어 있다. 

 

2-1  WaitGroup

동시에 수행될 연산의 집합을 기다릴 때 유용하다. 아래 예제를 보자.

var wg sync.WaitGroup

wg.Add(1)

go func(){
	defer wg.Done()
    fmt.Println("1st Go Routine is sleeping")
    time.Sleep(1)
}()

wg.Add(1)

go func(){
	defer wg.Done()
    fmt.Println("2nd Go Routine is sleeping")
    time.Sleep(1)
}()

wg.Wait()
fmt.Println("All go routines are done")

2-2 Mutex와 RWMutex

Mutex는 상호배제 의 약자로 , 프로그램 의 임계영역을 보호하는 방법이다. 

GO 답게 말한다면, 채널은 메모리를 통해 공유하는 반면, Mutex는 개발자가 메모리에 접근하는 방법을 통제하는 규칙을 만들어 메모리를 공유한다.

 

func Test_Mutex(t *testing.T) {
	var count int
	var lock sync.Mutex

	increment := func() {
		defer lock.Unlock()

		lock.Lock()
		count++
		fmt.Println("Added Count current is : ",count)
	}
	decrement := func() {
		defer lock.Unlock()
		
		lock.Lock()
		count--
		fmt.Println("Sub Count current is : ",count)
	}
	
	var proc sync.WaitGroup
	for i:=0;i<5;i++{
		proc.Add(1)
		go func(){
			defer proc.Done()
			increment()
		}()
	}
	
	for i:=0;i<5;i++{
		proc.Add(1)
		go func(){
			defer proc.Done()
			decrement()
		}()
	}
	
	proc.Wait()
	
	fmt.Println("All Tasks done")
}

mutex의 구조체를 이용해서 함수의 호출 전후로 lock과 unlock을 진행한다. (자바의 syncronized 키워드 가 어떻게 동작하는지 어림잡아 추측이 가지 않는가?)

단순하게 unlock을 하지 않는다면? 프로그램은 deadlock에 빠지고 panic 이 떨어져 프로세스는 떨어지게 된다.

 

이러한 임계영역의 진입 은 다소 비용이 많이 들기에 최소화하기 위한 노력을 많이 한다.  이 결과의 산출물이 

RwMutex이다. 임계영역의 범위를 줄이는 방법이다. read, write를 구분하는 것이다 항상 모든 스레드에서 쓰기를 동반한 모든 작업이 필요하지 않는 경우가 존재하지 않겠는가? 에서 출발한 아이디어이다.

 

읽기 잠금 요청을 할 수 있지만, 다른 프로세스에서 쓰기 권한을 가지지 않은 경우에만 가능한다 던 지, 아무도 쓰기 잠금을 보유하지 않고 있다면, 여러 개의 포르세스에서 읽기 잠금을 보유할 수 있다. 논리적으로 합당하다면 이런 경우에는 RWMutex를 사용하는 것이 합당하다.

 

2-3 Cond

고 루틴들이 대기하거나 , 어떤 이벤트의 발생을 알리는 집결 지점 (https://pkg.go.dev/sync#Cond).

두 개이상의 고 루틴 사이에서, 어떤 것이 발생했다는 사실에 대한 임의의 신호를 이벤트라고 일컫는다. 고 루틴의 실행 전 이러한 신호들 중 하나를 기다리고 싶을 수도 있는데 cond 타입이 이를 도와준다. 

cond 타입 없이 나이브하게 구현을 해본다면

 

무한루프를 사용하는 것이다. 

이렇게 되면 코어의 모든 사이클을 소모하기에 매우 비효율적이다. 이를 개선하기 위해 time.sleep을 이용해 강제로 스위칭을 일으킬 수 있다. 이 방법이 무한루프 보다 조금 더 낫지만 여전히 비효율적이다. 어느 정도의 슬립 이 필요한지 모르며 또한 이 슬립이 길어진다면 성능의 저하로 직결되기 때문이다.
고 루틴이 신호를 받을 때까지 슬립하고 있으며, 자신의 상태를 확인할 수 있는 방법 = cond 타입이 해주는 일이다. 

cond의 L locker 를 이용해 L.Lock, L.Unlock 을 활용이 가능하며 cond 의 자체적인 메서드 인 wait을 이용해 고 루틴을 일시중지 시킬 수 있다.

wait 은 단지 고 루틴이 멈추는것이 아닌 다른 고루틴이 os 스레드에서 실행될 수 있도록 한다.

func cond2() {
	c := sync.NewCond(&sync.Mutex{})

	queue := make([]interface{}, 0, 10)

	removeFromQueue := func(delay time.Duration) {
		time.Sleep(delay)
		c.L.Lock()
		queue = queue[1:]
		fmt.Println("Removed from queue")
		c.L.Unlock()
		c.Signal()
	}

	for i := 0; i < 10; i++ {
		c.L.Lock()
		for len(queue) == 2 {
			c.Wait()
		}
		fmt.Println("Adding to queue")
		queue = append(queue, struct{}{})
		go removeFromQueue(1 * time.Second)
		c.L.Unlock()
	}
}

 

10개의 항목이 추가되었으며, 마지막 두 개의 항목을 큐에서 꺼내기 전에 종료된다. wait의 조건에 따라 큐의 사이즈가 2가 되는 순간 1초의 대기시간이 걸리면서 remove 함수가 진행된다. 

여기서 signal이라는 새로운 메서드가 사용되는데 이는 cond의 wait에 대기 중인 고 루틴에게 신호를 보낸다. 이외에도 Broadcast라고 하는 메서드도 있다. 

 

런타임은 신호가 오기를 기다리는 고 루틴의 목록을 fifo(선입선출)의 형태를 유지한다. Signal 신호는 이중 가장 오래 기다린 고 루틴을 찾아서 알려주는 반면 Broadcast는 모든 고루 틴들에게 신호를 보낸다. 

 

2-4 Once

지난번 싱글턴 디자인 패턴에서도 등장하던 친구다. 이름에서 알 수 있듯이 함수를 정확하게 한 번만 호출하는 기능을 한다.

func Test_Once(t *testing.T) {
	var count int
	increment := func() {
		count++
	}
	decrement := func() {
		count--
	}

	var once sync.Once
	once.Do(increment)
	once.Do(decrement)

	fmt.Println(count)
}

이것의 결과는 0이 아닌 1이 나오게 된다. Do에 전달되는 함수의 호출 횟수가 아닌 Do 가 실행하는 횟수만을 계산하기 때문에 1의 결괏값을 받게 된다. 

 

2-5 Pool

pool 은 동시에 실행해도 안전한 책체 풀 패턴의 구현이다. 

일반적으로 데이터베이스 의 연결과 같은 비용이 많이 드는 것의 생성을 제한해 고정된 수의 개체만 생성하도록 하지만, 이러한 요청 혹은 연산이 얼마나 될지 알 수가 없다. 이런 경우 pool 은 매우 효과적으로 고 루틴 안에서 사용할 수 있다.

Get 메서드를 호출해 리턴 가능한 인스턴스가 pool 내에 있는지 확인하고, 그렇지 않으면 새 인스턴스를 만드는 new 멤버 변수를 호출한다. 이후 사용이 끝나면 반환을 위해 put을 호출한다.

func Test_Pool1(t *testing.T) {
	myPool := &sync.Pool{
		New: func() interface{} {
			fmt.Println("Creating new instance")
			return struct{}{}
		},
	}

	myPool.Get()
	instance := myPool.Get()
	myPool.Put(instance)
	myPool.Get()
}

Go 에는 가비지컬렉터가 존재하며 인스턴스 된 객체는 자동으로 정리된다. 그렇기에  객체의 캐시를 준비해야 하는 경우에 유용하게 사용할 수 있다. 호스트의 메모리르 보호하던, 사전로딩을 통해 신속한 결괏값을 표현할 때 사용된다.

 

한 가지 의문점이 들 수도 있다. sync 패키지에는 map 구조체 도 지원을 하고 있다. 고 루틴 간의 안전하게 데이터를 공유할 수 있게 하기 위해서 존재한다. 구조체 안에는 mutex, reader 등등 private으로 선언되어 있어 자체적으로 메서드 안에서 안전한 데이터 공유를 지원하게 해 준다. 그렇다면 sync.Map을 이용해서 저 캐시 기능을 구현가능하지 않겠는가 라는 의문점이 생길 수도 있다. 

 

위에서 언급했듯이 gc에 의해 이 pool 된 객체들은 제거된다. map을 이용해서 구현하게 된다면 계속 메모리에 들고 있어야 한다는 의미가 되고 이는 메모리 또한 관리해야 하는 치명적인 번거로움을 유발할 수도 있다.

 

pool 은 고비용 객체를 여러 번 재사용할 수 있도록 해주며, GC의 사이클이 실행되면 임시저장소에서 모든 객체가 수거된다. 이는 메모리 누수 방지에도 효과적이다. 따라서 Map과 pool 은 다른 동장방식과 다른 목적을 가지고 사용된다는 의미이다. 이를 혼동해서 사용하지 말자.

1. 동시성과 병렬성의 차이

- 동시성 은 "코드"의 속성, 병렬성은 "프로세스"의 속성이다.

  ㄱ. 우리가 작성한 코드들은 병렬로 실행되기를 바라면서 작성한다.

  ㄴ. 동시성 코드를 작성했다 할지라도, 실제로 병렬로 실행되는지 의 여부조차 모를 수가 있다.

  ㄷ. 병렬처리인지 아닌지는 컨택스트에 의해 결정된다. 

 

2. 대부분의 범용성 있는 언어들은 OS 스레드와 1:1 맵핑된 수준의 추상화 수준을 제공한다. 이는 다시 말해 동시성이 어려운 이유를 

야기하는 문제 가 실질적으로도 일어나는 레이어 계층이기도 하다. 

 

3. CSP 란 무엇인가 

- 상호작용 하는 순차적 프로세스들의 약자이다. 호어 가 제시한 논문의 약자로 "프로그래밍에서 두 가지 기본요소인 입력 및 출력이 간과되고 있으며, 특히 동시에 실행되는 코드의 경우에는 더욱 그렇다고 말한다." 논문에 제시된 코드를 보면 Go의 채널과  상당히 유사함을 알 수 있다. 

 

4. Go와 다른 대중적인 어어의 차이점 은 무엇인가?

 - OS 스레드 및 메모리 접근 동기화 수준에서의 언어 추상화 체인 이 기본언어의 틀인 반면, Go에서는 고 루틴 및 채널의 개념으로 이를 대체한다. 이에 고에서는 다음과 같은 통상적인 방법으로 코딩한다. 고루틴은 가볍기 때문에 고루틴 생성을 걸정할 필요는 없다. 

 

*자바로 작성된 스프링 프레임워크 와 고 의 에코 프레임워크는 어떻게 작동하는가?

각 요청마다 스프링 프레임워크는 아파치 톰캣 웹서버로부터 스레드풀에서 스레드를 할당받아 처리하고 스레드를 반환한다.

각 요청마다 고에서는 고 루틴을 생성해 처리하고 종료한다.

이것은 어떤 것이 더 빠르다고 할 수 없지만 GO에서는 리소스를 효율적으로 사용한다고 말할 수 있다.

 

5. Go의 동시성에 대한 철학

- 통신을 통해 메모리를 공유하고, 메모리 공유를 통해 통신하지 말라 이다. Go에서는 Sync 패키지를 이용해 전통적인 잠금 메커니즘을 사용할 수도 있고 혹은 채널을 이용해서 해결할 수 도 있다. 이에 언제 어떤 방식을 써야 하는지에 대해 트리를 이용해 제공하고 있다.

- 데이터 소유권을 이전하려는가? => 채널을 사용하면 동시성 코드를 다른 동시성 코드와 함께 구성할 수 있다는 점이다.

- 구조체의 내부 상태를 보호하고자 하는가? => 메모리 접근 동기화 기본요소를 사용하수 있는 최적의 선택지이다.

- 여러 부분 논리를 조정해야 하는가? => 채널을 사용한다면 Select 문을 활용해 긴급한 복잡성을 훨씬 쉽게 제어할 수 있다.

- 성능상의 임계영역 인가? => 해당영역 이 프로그램의 나머지 부분보다 현저하게 느린 주요 병먹 지점이라면, 메모리 접근 동기화 기본요소를 사용하자 이다.

 

고 루틴을 사용해 문제 공간을 모델링하고 작업 중 동시에 수행되는 부분을 표현하기 위해 고루틴을 사용하자.

단순화를 목표로 하고, 가능하면 채널을 사용하며 고 루틴을 무한정 쓸 수 있는 자원처럼 다루어라.!

 

Go 언어 의 꽃이라고 할 수 있는 동시성에 대해 공부해보고자 한다. 

우선 1장에서는 용어에 대한 설명 과 정의를 하는데 보다 명확하게 이해하고 넘어가고자 작성한다.

 

1. 동시성 은 왜 컴퓨터 과학에서 중요한가?

-GUI에 기반한 프로그램은 사용자의 버튼 클릭 와 해당 버튼의 동작으로 이루어진다. 이러한 프로그램 파이프라인은 순차적으로 실행될 수밖에 없는 사용자와의 상호작용에 의해 성능이 제한된다. 다시 말해 몇 개의 코어를 가지던, 상관없이 사용자가 얼마나 빠르게 클릭하느냐 에 따라 이 프로그램의 성능이 결정된다는 의미이다. 

- 스피곳 알고리즘 (원주율의 수수점 의 각자릿수에 대해 병렬로 처리하는 것)을 과잉병렬이라고 한다. 이를 해결하기 위해 프로그램에 서 더 많은 코어를 사용할 수 있게 만든다면 보다 성능향상 이 가능하다 반면 이에 각 코어 간의 결과를 어떻게 결합하고 저장하는가에 대한 새로운 문제점 이 발생한다. 이에 암달법칙 (병렬로 작업할 수 있는 부분이 많을수록 효율적으로 문제를 해결하는 것) 이 병렬처리가 시스템 성능 문제를 해결하는데 도움을 주는지에 대해 파악할 수 있다.

이런 병렬처리를 쉽게 하기 위해서는 프로그램, 애플리케이션을 수평적으로 작성할 것을 권장한다.

- 클라우딩 컴퓨팅에 의해 배포 및 수평확장에 새로운 패러다임이 등장하게 된다. 비교적 저렴한 비용으로 대교모 문제를 해결할 수 있는 컴퓨팅 성능에 접근하게 되었다. 그러나 이러한 자원들을 프로비저닝 하고, 인스턴스 간의 통신, 결과집계 저장 등 의 문제를 동시적으로 해결하는데 상당한 어려움이 있었으며 상황을 악화시키는 결과도 초래하곤 했다.

- 웹스케일(클라우드 회사에서 서버, 스토리지, 네트워크, 보안 등 구성요소를 최적화해서 운용하는 것) 이 등장하며, 과잉병렬을 가능하게 해 주었다

 

무어의 법칙이 깨짐 에 따라 , 하드웨의 기술발전이 이전만큼 유지되지 않고 있고 소프트웨어의 성능향상에 의존하기 어려워지고 있는 지금 동시성(멀티프로세스) 통한 성능향상을 추구하는 경향이 점점 더 강해지고 있고, 무어의 법칙의 깨짐에 대한 대안적인 접근 방법이 되어, 현 컴퓨터 과학에서 매우 중요한 개념으로 인식되고 있다.

 

2. 동시성 이 어려운 이유

- 레이스컨디션 : 하나의 동시작업이 어떤 변수를 읽으려고 시도하는 동안 또 다른 동시작업이 특정할 수 없는 시점에 동일변수에 값을 쓰려고 하는 데이터 레이스, 동시성 버그 중 가장 은밀한 유형 중 하나.

- 원자성 : 동작하는 콘텍스트 내에서 나누어지거나 중단되지 않는 것을 의미한다. 다시 말해 동시 실행되는 컨택스트 내에서 원자적이라 하면 안전하다는 것을 의미한다.

- 메모리접근 동기화: 각 프로그램 언어에서는 임계영역(공유리소스에 독점적 접근을 해야 하는 영역)의 동기화하는 다양한 방법을 제공한다. 

-데드락 : 두 개의 작업이 서로의 작업이 끝나기만을 기다리는 상태

-라이브락:  동시에 연산을 수행하고 있지만, 이 연산들이 실제 프로그램의 상태를 진행하는데 아무런 영향을 주지 못하는 상태

-기아상태: 실행하는데 필요한 모든 리소스를 얻을 수 없는 상태

Go에서는 동시성의 문제를 돕기 위해 다양한 기본요소들은, 동시성 알고리즘을 보다 안전하고 명확하게  표현할 수 있다. 

더보기
func race() {

	var memoryAccess sync.Mutex

	var data int
	go func() {
		memoryAccess.Lock()
		data++
		memoryAccess.Unlock()
		fmt.Printf("in the go routine the value is %v.\n", data)
	}()
	memoryAccess.Lock()
	if data == 0 {
		fmt.Printf("the value is %v.\n", data)
	} else {
		fmt.Printf("the value is %v.\n", data)
	}
	memoryAccess.Unlock()
}

type value struct {
	mu    sync.Mutex
	value int
}

func deadLock() {
	var wg sync.WaitGroup
	printSum := func(v1, v2 *value) {
		defer wg.Done()
		v1.mu.Lock()
		defer v1.mu.Unlock()
		time.Sleep(2 * time.Second)
		v2.mu.Lock()
		defer v2.mu.Unlock()
		fmt.Printf("sum=%v\n", v1.value+v2.value)
	}
	var a, b value
	wg.Add(2)
	go printSum(&a, &b)
	go printSum(&b, &a)
	wg.Wait()
}

func liveLock() {
	cadence := sync.NewCond(&sync.Mutex{})
	go func() {
		for range time.Tick(1 * time.Millisecond) {
			cadence.Broadcast()
		}
	}()

	takeStep := func() {
		cadence.L.Lock()
		cadence.Wait()
		cadence.L.Unlock()
	}

	tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool {
		fmt.Fprintf(out, " %v", dirName)
		atomic.AddInt32(dir, 1)
		takeStep()
		if atomic.LoadInt32(dir) == 1 {
			fmt.Fprintf(out, ". Success!")
			return true
		}
		takeStep()
		atomic.AddInt32(dir, -1)
		return false
	}

	var left, right int32
	tryLeft := func(out *bytes.Buffer) bool { return tryDir("left", &left, out) }
	tryRight := func(out *bytes.Buffer) bool { return tryDir("right", &right, out) }

	walk := func(walking *sync.WaitGroup, name string) {
		var out bytes.Buffer
		defer func() { fmt.Println(out.String()) }()
		defer walking.Done()
		fmt.Fprintf(&out, "%v is trying to scoot:", name)
		for i := 0; i < 5; i++ {
			if tryLeft(&out) || tryRight(&out) {
				return
			}
		}
		fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name)
	}

	var peopleInHallway sync.WaitGroup
	peopleInHallway.Add(2)

	go walk(&peopleInHallway, "Alice")
	go walk(&peopleInHallway, "Barbara")

	peopleInHallway.Wait()
}

func starvation() {
	var wg sync.WaitGroup
	var sharedLock sync.Mutex
	const runtime = 1 * time.Second

	greedyWorkder := func() {
		defer wg.Done()
		var count int
		for begin := time.Now(); time.Since(begin) <= runtime; {
			sharedLock.Lock()
			time.Sleep(3 * time.Nanosecond)
			sharedLock.Unlock()
			count++
		}
		fmt.Printf("Greedy worker was able to execute %v work loops.\n", count)
	}

	polliteWorker := func() {
		defer wg.Done()

		var count int
		for begin := time.Now(); time.Since(begin) <= runtime; {
			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()
			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()
			sharedLock.Lock()
			time.Sleep(1 * time.Nanosecond)
			sharedLock.Unlock()
			count++
		}
		fmt.Printf("Polite worker was able to execute %v work loops.\n", count)
	}

	wg.Add(2)

	go greedyWorkder()
	go polliteWorker()

	wg.Wait()
}




소프트웨어 디자인 패턴에서 싱글턴 패턴
(Singleton pattern)을 따르는 클래스는, 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다. 이와 같은 디자인 유형을 싱글턴 패턴이라고 한다.
주로 공통된 객체를 여러 개 생성해서 사용하는 DBCP(DataBase Connection Pool)와 같은 상황에서 많이 사용된다. - wikipedia

 

싱글턴 패턴의 등장 배경에는 아래와 같은 이유가 있다.

유일한 인스턴스 유지:
일부 시스템에서는 특정 클래스의 인스턴스가 하나만 존재해야 할 때가 있습니다. 예를 들어, 시스템의 설정 정보를 담당하는 클래스, 로그를 관리하는 클래스, DB 연결을 관리하는 클래스 등이 있습니다. 이런 경우, 싱글턴 패턴은 해당 클래스의 인스턴스가 하나만 존재하도록 보장합니다.

전역 접근: 또한 싱글턴 패턴은 이 유일한 인스턴스에 대한 전역적인 접근 방법을 제공합니다. 즉, 어디서든 이 인스턴스를 참조할 수 있게 됩니다. 이는 애플리케이션 전반에서 공유해야 하는 데이터나 리소스를 관리하는 데 유용합니다.

제어된 리소스 공유: 한정된 리소스를 공유해야하는 경우에도 싱글턴 패턴이 유용합니다. 예를 들어, 데이터베이스 연결풀, 파일 시스템, 네트워크 소켓 등의 리소스는 한정되어 있기 때문에 효율적으로 관리하고 사용할 필요가 있습니다.

메모리 및 성능 최적화: 싱글턴 패턴을 사용하면 메모리 사용을 최적화하고 성능을 향상할 수 있습니다. 인스턴스를 한 번만 생성하므로 메모리 낭비를 줄이고, 계속해서 같은 인스턴스를 재사용함으로써 성능을 향상할 수 있습니다.

 

예전 Java 에서 Singleton 패턴에 관련해 정리된 내용이 있어 Go에서는 어떻게 활용하는가? 에 초점을 두어 작성하겠다.

(https://guiwoo.tistory.com/24)

 

전통적인 방식

type db struct {
	/**
	db 에 해당하는 값 이 저장
	*/
}

var obj *db

func GetInstance() *db {
	if obj == nil {
		obj = &db{}
	}
	return obj
}

 

위와 같이 구현을 한다. 다만 이렇게 구현 되었을때 문제점이 발생된다. 바로 멀티스레드 환경인데 

(생성 과정 의 단순함 과 Go Routine의 최적화에 따라 문제점 발생이 너무 희박해서 강제로 상황을 만들었다 "runtime.Gosched()를 활용")

func main() {
	wg := &sync.WaitGroup{}

	for i := 0; i < 30; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			singleton.GetInstance()
		}()
	}
	wg.Wait()
}

DB의 인스턴스를 여러 번 생성하는 것을 확인할 수 있다. 

 

이를 방지하기 위해 Go 에서 제공해 주는 특별한 기능들이 있는데

 

Sync.Once(https://pkg.go.dev/sync)

- 싱크 패키지 에서 제공하는 구조체 중에 하나로 Once는 한 번만 수행되도록 도와주는 구조체이다. 제공되는 메서드 중 하나로 Do 메서드가 있으며 Do에서 실행되는 콜백 function 은 오직 한 번의 실행만을 수행한다.

type db struct {
	/**
	db 에 해당하는 값 이 저장
	*/
}

var obj *db
var o sync.Once

func GetInstance() *db {
	o.Do(func() {
		runtime.Gosched()
		obj = &db{}
		fmt.Println("Crated DB Instance")
	})
	return obj
}

동일한 main func 을 수행한다면?

위와 같은 결과를 얻을 수 있다. 

 

init 함수 활용 하기

- go에서는 패키지 import 간 최초 실행 해주는 함수가 있다. 바로 init() 함수인데 제일 좋아하는 방법이다. 바로 활용해 보자.

type db struct {
	/**
	db 에 해당하는 값 이 저장
	*/
}

var obj *db

func init() {
	obj = &db{}
	fmt.Println("Db created")
}

func GetInstance() *db {
	if obj == nil {
		fmt.Println("Three is no db instance")
	}
	return obj
}

개인적으로는 init 함수를 선호한다. 

 

이러한 싱글턴 디자인 패턴 에는 테스트의 어려움 이 있다. 변수를 아무래도 전역적으로 관리하다 보니 목킹 이 생각보다 어렵다. 

이런 경우 인터페이스를 활용해서 추상화 작업을 통해 작성하는 것도 하나의 방법이 될 수 있다.

 

type MyDB interface {
	DoSomething() // DB 작업을 위한 메서드
}

type db struct {
	// db에 해당하는 값이 저장
}

var obj *db

func init() {
	obj = &db{}
	fmt.Println("DB created")
}

func (d *db) DoSomething() {
	// DB 작업을 수행하는 메서드
}

func GetInstance() MyDB {
	if obj == nil {
		fmt.Println("There is no DB instance")
	}
	return obj
}

 

[프로타입 패턴]

인스턴스는 새 객체를 만들기 위해 자신을 복제(clone)하게 된다.

원칙은 "런타임"에 또 다른 "객체"를 생성한다는 것이다. 다시 말해 이 시점에 가서 클로닝(cloning)을 하는 객체의 "실제 복사본"이 만들어지는 것이다.

 

실행 시간 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. -->

ko.wikipedia.org

 

기존 자바를 사용했던 사람들에게 는 매우 익숙한 패턴처럼 보일 것이다.

모든 자바의 클래스는 Object를 상속하고, 이는 Clone()의 메서드를 제공하게 된다. 즉 객체의 복사를 제공한다.

 

왜 필요할까? 

객체의 정확한 복사본을 만들고자 할 때 필요하다고 정의되어있다. 특히나 Go에서는 slice, pointer에서 예외 상황이 발생한다.

아래의 예제를 보자.

결과 값의 3,4번째 줄을 확인해 보자. 동일한 주소값을 가지고 있다. 분명 새로운 키보드를 만들기 위해서 작성했지만 아래와 같이 동일한 주소값을 가지고 있다.

고에서 슬라이스는 포인터의 래퍼이다. 즉 메모리 주소의 포인터 값을 랩핑 한 배열이다. 당연히 이를 복사하면 우리가 가져오는 값들은 저 포인터 값을 복사한 값들을 들고 오게 된다. 이는 반드시 주의해야 한다.

 

	leopold := &Keyboard{
		Layout: vamilo.Layout,
		Switch: []string{"Gateron Mx Red", "Gateron Mx Blue"},
		KeyCap: vamilo.KeyCap,
	}

위와 같은 새로운 슬라이스를 선언하고 값을 넣어줘야 한다. 

 

아래와 같은 방법을 이용해서 복사하는 방법도 하나의 방법 중 하나이다.

gob를 이용해 스트림화시켜 직렬화 역직렬화시키는 방법을 이용해서 복사를 하는 것도 하나의 방법이 될 수도 있다.

 

Interface의 clone을 선언해서 이용하는 방법을 사용해 보자.

 

이번에는 인터페이스의 클론을 활용해서 인터페이스 자신을 반환하게 설정하였다.
이렇게 설정되면 클라이언트는 복사하고자 하는 대상으로 타입어설션이 가능해지며 유연성 있고, 확장가능한 코드구성이 가능해진다.

func Run() {
	sixty := &Keyboard{"60%", []string{"Cherry MX Blue", "Cherry MX Brown", "Cherry MX Red"}, "DSA"}

	keyboardClone := sixty.Clone().(*Keyboard)

	fmt.Println(keyboardClone)
}

 

[빌더 패턴]

빌더 패턴이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 패턴이다.

생성패턴 의 근간이 되는 기본 구조에 대해서 다시 한번 상기하고 넘어가자.

생성패턴 은 구현하는 구현체가 있고 구현체 의 추상화 클래스가 존재한다.

 

빌더패턴 은 다음 과 같은 문제점을 해결하고자 나왔다.

1. 하나의 객체에서 한 개 이상의 생성자 필드를 받게 된다면 , 생성함에 있어 깔끔하고 유지가능한 코드를 작성하는 것이 생각보다 어려울 수 있다.

2. 객체의 생성이후 필드값의 불변성이 요구될 때, 한 개 이상의 다양한 많은 속성을 불변으로 만드는 것은 생각보다 어려울 수 있다.

3. 객체의 복합 생성 로직은 메인 클래스 의 유지 및 확장성에 어려움을 야기할수 있다. 

 

빌더 패턴 을 만들어 보자.

type House struct {
	windows            int
	door               int
	floor              string
	rooms              int
	bathroom           int
	swimmingPoolSize   int
	swimmingPoolHeight int
	gardenSize         int
	gardenFlower       string
	gardenTree         string
	garageSize         int
	garageDoor         string
}

물론 작은 필드 들이지만 이것보다 많아질 수 있고 다양해질 수 있다고 가정해 보자. House를 생성하고자 한다면?

house := &House{4, 1, "wooden", 2, 1, 0, 0, 0, "", "", 0, ""}

이런 식의 코드가 생기게 되며 읽기 매우 어렵고 난해하다.

우선 구현하고자 하는 기본 골격 구조를 다음과 같이 잡고자 한다.

 

정통적인 빌더 패턴에서는 다음과 같은 구조를 가지게 된다. 

생성패턴의 기본 골격인 인터페이스와 구현체를 빌더패턴에서도 동일하게 가져간다. 

이제는 생각보다 많이 익숙해진 구조이고 , 다만 디렉터라는 구현체가 빌더의 총괄적인 핸들링을 담당하고 있다.

구체적인 클래스 설계도를 확인해 보자.

House 빌더라는 인터페이스가 있으며, 그거를 구현하는 각각의 빌더 구조체들을 구현하고 이 각각 의 구조체는 House라는 거대한 

구조체를 상속받아서 사용할 것이다.

이렇게 생성된 HouseBuilder는 BuildDirector라는 구현체에 의해 핸들링되고 관리될 것이다. 

바로 코드로 가보자.

인터페이스는 위와 같이 설정해 주었으며, 아래 있는 의문에 대해 생각해 보자. 최종 구현체에서 코드를 사용할 때 이유를 확인할 수 있다.

인터페이스의 구현을 위해 위와 같이 작성했으며, 특히나 go에서 ide를 사용하고 있다면, 

var _ HouseBuilder = (*HouseNormalBuilder)(nil)

위 코드를 이용해 자동완성을 애용하자.

핵심이 되는 디렉터 구조체를 확인해 보자. 

디렉터 구조체는 하우스 빌더를 생성간의 주입받게 된다. 이렇게 주입된 하우스 빌더를 통해 원하는 집을 디렉터가 정의한 순서에 맞춰

생산할 수 있다. 마치 프레임워크가 작동하듯이 말이다.

 

중간 함수를 확인하게 되면. 을 이용해 죄다 체이닝 해버렸다. 보다 직관적인 코드의 방식 구현을 위해 인터페이스에서 다음과 같이 정의를 했다. 본인 자신을 반환하게 된다면 위와 같은 체이닝 방식이 가능해진다.

 

그래서 클라이언트는 어떻게 이거를 사용할까?

빌더를 각각 주입해 주면서 우리는 대형 객체를 보다 손쉽게 선언하고 사용할 수 있다. 

SwimmingPool House is {windows:40 door:10 floor:wooden rooms:0 bathroom:1 swimmingPoolSize:200 swimmingPoolHeight:180 gardenSize:0 gardenFlower: gardenTree: garageSize:0 garageDoor:}
Normal House is {windows:20 door:20 floor:wooden rooms:0 bathroom:3 swimmingPoolSize:0 swimmingPoolHeight:0 gardenSize:0 gardenFlower: gardenTree: garageSize:0 garageDoor:}

이렇게 코드가 작성되고 클라이언트가 사용한다고 가정할 때 빌더패턴 이 나오게 된 이유를 모두 충족하는가? 

 

1. 하나의 객체에서 한 개 이상의 생성자 필드를 받게 된다면 , 생성함에 있어 깔끔하고 유지가능한 코드를 작성하는 것이 생각보다 어려울 수 있다.

- 각각의 빌더를 주입받아서 정해진 순서에 따라 하우스를 생산한다. 순서의 관리는 디렉터 가 담당하고, 각각의 구현체끼리는 느슨한 결합 형태를 가지게 된다.

 

2. 객체의 생성 이후 필드값의 불변성이 요구될 때, 한 개 이상의 다양한 많은 속성을 불변으로 만드는 것은 생각보다 어려울 수 있다.

- 하우스 구조체 의 필드값은 소문자로 임포트 되지 않는다. 즉 특정 메서드가 제공되지 않는다면? 불변하는 필드들이다.

 

3. 객체의 복합 생성 로직은 메인 클래스 의 유지 및 확장성에 어려움을 야기할 수 있다. 

- 하우스 새로운 건축물 혹은 수영장 집의 업그레이드 함에 있어 어디를 수정하고 어디를 고쳐야 하는지 명확한가?

 

빌더패턴 의 특징을 다시 한번 알고 넘어가자.

- Builder Interface 가 모든 구현체에서 구현 함수를 정의한다.- Concrete Builder에서 Builder Interface를 구현하고 실제 로직을 캡슐화한다.- Director는 옵션적인 부분이며, Builder Interface의 함수를 호출해 Build의 대상을 제공한다.- Product에서는 Build pattern을 사용해 실질적인 인스턴스를 담당한다.

 

어느 정도 패턴을 공부하고, 자바 사람이라면 이 빌더패턴 은 너무나도 친숙한 패턴이 아닐 수 없다.Lombok 이  이 빌더패턴을 정말 잘 활용해서  객체의 생성을 도와준다. 고 와는 달리 자바에서는 필드의 생성자 값들을 하나씩만 넣으면서 초기화하기 위해서? 그에 해당하는 모든 생성자 클래스를 선언해주어야 한다. 창문 만 있는 생성자 클래스, 창문 욕조 가 있는 생성자 클래스 등등

갑자기 이 이야기를 왜 하냐고 의문이 들 수 있다.

House.builder().window().door().build();

롬복에서 는 builder라는 어노테이션을 제공해 주는데 이런 체이닝 방식을 이용해서 제공한다. 왜 이렇게 유명한 라이브러리(github star 11.9k)에서 우리가 지금 까지 배운 인터페이스, 함수 호출 외순서를 정하는 방식이 아닌 위와 같은 체이닝 방법을 활용해서 제공하고 빌더패턴을 적용했다고 할까? 우리가 배운 생성패턴의 기본 골격 구조는 인터페이스에 의존하는 구현체인데 방식이 많이 다르지 않은가?

 

이런 부분에 있어 어느 정도 유연성을 둔다고 한다. 그래서 글 초창기에 전통적인 빌더패턴이라는 표현을 사용했다.

빌더패턴의 목적  "유연하고, 효과적인 방법으로 객체 생성을 제공해야한다." 이다. 

처음 제공된 코드처럼 마치 프레임워크 같이 작성하는 전통적인 빌더패턴 이 있고, 롬복 과 같이 다양한 라이브러리 에서는 사용자에게 보다 많은 유연성을 주기 위해 최근에는 이런 인터페이스 가  생략된 간단한 빌더패턴을 제공한다고 한다.

아래 코드를 보자.

 

더보기
package simpleBuilder

type Person struct {
	StreetAddress, Postcode, City string

	CompanyName, Position string
	AnnualIncome          int
}

type PersonBuilder struct {
	person *Person
}

type PersonAddressBuilder struct {
	PersonBuilder
}

func NewPersonBuilder() *PersonBuilder {
	return &PersonBuilder{&Person{}}
}

func (b *PersonBuilder) Lives() *PersonAddressBuilder {
	return &PersonAddressBuilder{*b}
}

func (b *PersonBuilder) Works() *PersonJobBuilder {
	return &PersonJobBuilder{*b}
}

func (p *PersonBuilder) Builder() *Person {
	return p.person
}

func (p *PersonAddressBuilder) At(street string) *PersonAddressBuilder {
	p.person.StreetAddress = street
	return p
}
func (p *PersonAddressBuilder) In(city string) *PersonAddressBuilder {
	p.person.City = city
	return p
}
func (p *PersonAddressBuilder) PostCode(code string) *PersonAddressBuilder {
	p.person.Postcode = code
	return p
}

type PersonJobBuilder struct {
	PersonBuilder
}

func (p *PersonJobBuilder) At(comapny string) *PersonJobBuilder {
	p.person.CompanyName = comapny
	return p
}
func (p *PersonJobBuilder) Asa(job string) *PersonJobBuilder {
	p.person.Position = job
	return p
}
func (p *PersonJobBuilder) Earn(income int) *PersonJobBuilder {
	p.person.AnnualIncome = income
	return p
}

 

 

단순하게 큰구조체 안에 빌더2개를 더만들어서 나아가는 방식으로 작성했다.

그러면 다음과 같은 클라이언트 호출을 가져갈수 있다.

 

다시말해 생성패턴 의 기본 골격 구조가 항상 지켜져야 만 패턴이 성립한다는 생각을 이번기회에 버리자. 

이렇게 됨에 우리는 2가지 선택지가 생긴다.

 

1. 각단계의 순서를 강제해 객체 의 생성 로직을 명확히 하는 전통적인 빌더패턴의 방법

2. 사용자 에게 자유로운 방식의 유연성을 주는 방식의 빌더패턴 방법 

 

상황에 맞는 유연한 코드를 작성하자.

 

코드전문

더보기
프로젝트 구조
package houseImpl

type House struct {
	windows            int
	door               int
	floor              string
	rooms              int
	bathroom           int
	swimmingPoolSize   int
	swimmingPoolHeight int
	gardenSize         int
	gardenFlower       string
	gardenTree         string
	garageSize         int
	garageDoor         string
}


package houseImpl

type HouseBuilder interface {
	Window() HouseBuilder
	Door() HouseBuilder
	Floor() HouseBuilder
	Rooms() HouseBuilder
	BathRoom() HouseBuilder
	GetHouse() House
}


package houseImpl

type HouseGarageBuilder struct {
	House
}

func NewHouseGarageBuilder() *HouseGarageBuilder {
	return &HouseGarageBuilder{}
}

func (h *HouseGarageBuilder) GetHouse() House {
	h.setGarage()
	return House{
		windows:    h.windows,
		door:       h.door,
		floor:      h.floor,
		rooms:      h.rooms,
		bathroom:   h.bathroom,
		garageDoor: h.garageDoor,
		garageSize: h.garageSize,
	}
}
func (h *HouseGarageBuilder) setGarage() {
	h.garageSize = 20000
	h.garageDoor = "Marble"
}

func (h *HouseGarageBuilder) Window() HouseBuilder {
	h.windows = 40
	return h
}

func (h *HouseGarageBuilder) Door() HouseBuilder {
	h.door = 10
	return h
}

func (h *HouseGarageBuilder) Floor() HouseBuilder {
	h.floor = "Marble"
	return h
}

func (h *HouseGarageBuilder) Rooms() HouseBuilder {
	h.rooms = 7
	return h
}

func (h *HouseGarageBuilder) BathRoom() HouseBuilder {
	h.bathroom = 5
	return h
}


package houseImpl

type HouseGardenBuilder struct {
	House
}

func NewHouseGardenBuilder() *HouseGardenBuilder {
	return &HouseGardenBuilder{}
}

func (h *HouseGardenBuilder) GetHouse() House {
	h.setGarden()
	return House{
		windows:      h.windows,
		door:         h.door,
		floor:        h.floor,
		rooms:        h.rooms,
		bathroom:     h.bathroom,
		gardenSize:   h.gardenSize,
		gardenFlower: h.gardenFlower,
		gardenTree:   h.gardenTree,
	}
}
func (h *HouseGardenBuilder) setGarden() {
	h.gardenSize = 400
	h.gardenFlower = "Rose and Sunflower"
	h.gardenTree = "White Oak Tree"
}

func (h *HouseGardenBuilder) Window() HouseBuilder {
	h.windows = 40
	return h
}

func (h *HouseGardenBuilder) Door() HouseBuilder {
	h.door = 10
	return h
}

func (h *HouseGardenBuilder) Floor() HouseBuilder {
	h.floor = "Marble"
	return h
}

func (h *HouseGardenBuilder) Rooms() HouseBuilder {
	h.rooms = 7
	return h
}

func (h *HouseGardenBuilder) BathRoom() HouseBuilder {
	h.bathroom = 5
	return h
}

package houseImpl

type HouseNormalBuilder struct {
	House
}

func NewHouseNormalBuilder() *HouseNormalBuilder {
	return &HouseNormalBuilder{}
}

func (h *HouseNormalBuilder) GetHouse() House {
	return House{
		windows:  h.windows,
		door:     h.door,
		floor:    h.floor,
		rooms:    h.rooms,
		bathroom: h.bathroom,
	}
}

func (h *HouseNormalBuilder) Window() HouseBuilder {
	h.windows = 20
	return h
}

func (h *HouseNormalBuilder) Door() HouseBuilder {
	h.door = 20
	return h
}

func (h *HouseNormalBuilder) Floor() HouseBuilder {
	h.floor = "wooden"
	return h
}

func (h *HouseNormalBuilder) Rooms() HouseBuilder {
	h.rooms = 5
	return h
}

func (h *HouseNormalBuilder) BathRoom() HouseBuilder {
	h.bathroom = 3
	return h
}

package houseImpl

type HouseSwimmingPoolBuilder struct {
	House
}

func NewHouseSwimmingPoolBuilder() *HouseSwimmingPoolBuilder {
	return &HouseSwimmingPoolBuilder{}
}

func (h *HouseSwimmingPoolBuilder) GetHouse() House {
	h.setSwimmingPool()
	return House{
		windows:            h.windows,
		door:               h.door,
		floor:              h.floor,
		rooms:              h.rooms,
		bathroom:           h.bathroom,
		swimmingPoolHeight: h.swimmingPoolHeight,
		swimmingPoolSize:   h.swimmingPoolSize,
	}
}
func (h *HouseSwimmingPoolBuilder) setSwimmingPool() {
	h.swimmingPoolSize = 200
	h.swimmingPoolHeight = 180
}

func (h *HouseSwimmingPoolBuilder) Window() HouseBuilder {
	h.windows = 40
	return h
}

func (h *HouseSwimmingPoolBuilder) Door() HouseBuilder {
	h.door = 10
	return h
}

func (h *HouseSwimmingPoolBuilder) Floor() HouseBuilder {
	h.floor = "wooden"
	return h
}

func (h *HouseSwimmingPoolBuilder) Rooms() HouseBuilder {
	h.rooms = 2
	return h
}

func (h *HouseSwimmingPoolBuilder) BathRoom() HouseBuilder {
	h.bathroom = 1
	return h
}

package main

import (
	"builder/houseImpl"
	"builder/simpleBuilder"
	"fmt"
)

type BuildDirector struct {
	builder houseImpl.HouseBuilder
}

func NewBuildDirector(builder houseImpl.HouseBuilder) *BuildDirector {
	return &BuildDirector{builder: builder}
}

func (b *BuildDirector) BuildHouse() houseImpl.House {
	return b.builder.Window().Door().Floor().BathRoom().GetHouse()
}
func (b *BuildDirector) changeBuilder(builder houseImpl.HouseBuilder) {
	b.builder = builder
}

func main() {
	builder := houseImpl.NewHouseSwimmingPoolBuilder()
	director := NewBuildDirector(builder)

	a := director.BuildHouse()
	fmt.Printf("SwimmingPool House is %+v\n", a)
	director.changeBuilder(houseImpl.NewHouseNormalBuilder())
	b := director.BuildHouse()
	fmt.Printf("Normal House is %+v\n", b)

	pb := simpleBuilder.NewPersonBuilder()
	pb.
		Lives().
		At("123 London").
		In("London").
		PostCode("Mortgatan6").
		Works().
		At("Plea").
		Asa("Programmer").
		Earn(12300)

	person := pb.Builder()
	fmt.Println(person)

}

 

 

+ Recent posts