Go

[Design Pattern] 싱글톤(Singleton), 빌더(Builder), 팩토리 메소드(Factory Method) 패턴

구루싸 2022. 11. 21. 14:42
반응형
SMALL

싱글톤(Singleton)

싱글톤(Singleton) 패턴은 기억하기 쉬운 패턴입니다. 이름에서도 나타내듯이 한 객체 대한 중복 없이 하나의 인스턴스를 제공하는 디자인 패턴입니다. 이는 인스턴스를 사용하기 위한 첫번째 호출에서 인스턴스를 생성하고 애플리케이션 내에서 재사용되는 것을 의미합니다. 

이를 돕기 위해 간단하게 count를 증가시키는 코드를 살펴보겠습니다.

package singleton

type Singleton interface {
	AddOne() int
}

type singleton struct {
	count int
}

var instance *singleton

func GetInstance() Singleton {
	if instance == nil {
		instance = new(singleton)
	}
	return instance
}

func (s *singleton) AddOne() int {
	s.count++
	return s.count
}

Test Code

 

package singleton

import "testing"

func TestGetInstance(t *testing.T) {
	counter1 := GetInstance()

	if counter1 == nil {
		//Test of acceptance criteria 1 failed
		t.Error("expected pointer to Singleton after calling GetInstance(), not nil")
	}

	currentCount := counter1.AddOne()
	if currentCount != 1 {
		t.Errorf("after calling for the first time to count, the count must be 1 but it is %d\n", currentCount)
	}

	expectedCounter := counter1

	counter2 := GetInstance()
	if counter2 != expectedCounter {
		//Test 2 failed
		t.Error("expected same instance in counter2 but it got a different instance")
	}

	currentCount = counter2.AddOne()
	if currentCount != 2 {
		t.Errorf("after calling 'AddOne' using the second counter, the current count must be 2 but was %d\n", currentCount)
	}

}

Singleton을 사용하면 계산이 필요한 경우에 대비하여 개체를 만드는 복잡성과 모든 개체가 유사한 경우 개체의 인스턴스가 필요할 때마다 개체를 만드는 함정을 숨길 수 있습니다. 

빌더(Builder)

빌더(Builder) 패턴은 동시에 동일한 기술을 사용하여 여러 유형의 개체를 만들 수 있습니다. 예를 들어, 승용차는 크기와 좌석 수가 다르다는 것을 제외하고 버스를 만들 때와 거의 같은 기술을 사용할 것입니다. 이럴 때 빌더 패턴을 이용합니다. 빌더 디자인 패턴은 Director, 몇 개의 Builder, 제품 간에 관계로 묘사됩니다. 이해를 돕기 위해 코드를 작성해보겠습니다. 

package builder

type ManufacturingDirector struct {
	builder BuildProcess
}

func (f *ManufacturingDirector) Construct() {
	f.builder.SetSeats().SetStructure().SetWheels()
}

func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
	f.builder = b
}
package builder

type VehicleProduct struct {
	Wheels    int
	Seats     int
	Structure string
}
package builder

type BuildProcess interface {
	SetWheels() BuildProcess
	SetSeats() BuildProcess
	SetStructure() BuildProcess
	Build() VehicleProduct
}

type CarBuilder struct {
	v VehicleProduct
}

func (c *CarBuilder) SetWheels() BuildProcess {
	c.v.Wheels = 4
	return c
}

func (c *CarBuilder) SetSeats() BuildProcess {
	c.v.Seats = 5
	return c
}

func (c *CarBuilder) SetStructure() BuildProcess {
	c.v.Structure = "Car"
	return c
}

func (c *CarBuilder) Build() VehicleProduct {
	return c.v
}

type BusBuilder struct {
	v VehicleProduct
}

func (c *BusBuilder) SetWheels() BuildProcess {
	c.v.Wheels = 8
	return c
}

func (c *BusBuilder) SetSeats() BuildProcess {
	c.v.Seats = 30
	return c
}

func (c *BusBuilder) SetStructure() BuildProcess {
	c.v.Structure = "Bus"
	return c
}

func (c *BusBuilder) Build() VehicleProduct {
	return c.v
}

Test Code

 

package builder

import "testing"

func TestBuilderPattern(t *testing.T) {
	manufacturingComplex := ManufacturingDirector{}

	carBuilder := &CarBuilder{}
	manufacturingComplex.SetBuilder(carBuilder)
	manufacturingComplex.Construct()

	car := carBuilder.Build()

	if car.Wheels != 4 {
		t.Errorf("wheels on a car must be 4 and they were %d\n", car.Wheels)
	}

	if car.Structure != "Car" {
		t.Errorf("Structure on a car must be 'Car' and was %s\n", car.Structure)
	}

	if car.Seats != 5 {
		t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats)
	}

	busBuilder := &BusBuilder{}
	manufacturingComplex.SetBuilder(busBuilder)
	manufacturingComplex.Construct()

	bus := busBuilder.Build()

	if bus.Wheels != 8 {
		t.Errorf("wheels on a bus must be 4 and they were %d\n", bus.Wheels)
	}

	if bus.Structure != "Bus" {
		t.Errorf("Structure on a bus must be 'Bus' and was %s\n", bus.Structure)
	}

	if bus.Seats != 30 {
		t.Errorf("Seats on a bus must be 20 and they were %d\n", bus.Seats)
	}
}

 

빌더(Builder) 패턴은 Director가 사용하는 공통 구성 알고리즘을 사용하여 예측할 수 없는 수의 제품을 유지하는 데 도움이 될 수 있고, 건설 과정은 항상 제품의 사용자로부터 추상화됩니다. 동시에 정의된 구성 패턴을 갖는 것은 소스 코드를 처음 접하는 사람이 파이프라인에 새로운 제품을 추가해야 할 때 도움이 됩니다. 빌드 프로세스 인터페이스는 가능한 빌더의 일부가 되기 위해 준수해야 하는 사항을 지정합니다. 그러나 알고리즘이 어느 정도 안정적인지 확실하지 않을 때는 Builder 패턴을 피하십시오. 왜냐하면 이 인터페이스의 작은 변경이 모든 Builder에 영향을 미치므로 일부 Builder가 필요하고 다른 Builder가 필요하지 않은 새로운 방법을 추가하면 어색할 수 있습니다. 

팩토리 메소드(Factory Method)

팩토리 메소드(Factory Method)은 아마도 두번째로 잘 알려지고 산업에서 사용되는 디자인 패턴입니다. 이 패턴은 사용자가 웹 서비스 또는 데이터베이스에서 일부 가치를 검색하는 것과 같은 특정 목적을 위해 달성해야 하는 구조에 대한 지식을 추상화하는 것이 목적입니다. 사용자는 이 값을 제공하는 인터페이스만 필요합니다. 이 결정을 공장에 위임함으로써 이 공장은 사용자의 요구에 맞는 인터페이스를 제공할 수 있습니다. 또한 필요한 경우 기본 유형의 구현을 다운그레이드하거나 업그레이드하는 프로세스를 쉽게 수행할 수 있습니다.

package factorymethod

import (
	"fmt"
)

const (
	Cash      = 1
	DebitCard = 2
)

type PaymentMethod interface {
	Pay(amount float32) string
}

func GetPaymentMethod(m int) (PaymentMethod, error) {
	switch m {
	case Cash:
		return new(CashPM), nil
	case DebitCard:
		return new(DebitCardPM), nil
	default:
		return nil, fmt.Errorf("payment method %d not recognized", m)
	}
}

type CashPM struct{}

func (c *CashPM) Pay(amount float32) string {
	return "paid using cash"
}

type DebitCardPM struct{}

func (c *DebitCardPM) Pay(amount float32) string {
	return "paid using debit card"
}

Test Code

 

package factorymethod

import (
	"strings"
	"testing"
)

func TestGetPaymentMethodCash(t *testing.T) {
	payment, err := GetPaymentMethod(Cash)
	if err != nil {
		t.Fatal("a payment method of type 'Cash' must exist")
	}

	msg := payment.Pay(10.30)
	if !strings.Contains(msg, "paid using cash") {
		t.Errorf("the cash payment method message wasn't correct")
	}
	t.Log("LOG:", msg)
}

func TestGetPaymentMethodDebitCard(t *testing.T) {
	payment, err := GetPaymentMethod(DebitCard)
	if err != nil {
		t.Error("a payment method of type 'DebitCard' must exist")
	}

	msg := payment.Pay(22.30)
	if !strings.Contains(msg, "paid using debit card") {
		t.Errorf("the debit card payment method message wasn't correct")
	}
	t.Log("LOG:", msg)
}

func TestGetPaymentMethodNonExistent(t *testing.T) {
	_, err := GetPaymentMethod(20)
	if err == nil {
		t.Error("a payment method with ID 20 must return an error")
	}
	t.Log("LOG:", err)
}
반응형
LIST