Go

[Design Pattern] 플라이웨이트(Flyweight), 전략(Strategy), 역할 사슬(Chain of Responsibility) 패턴

구루싸 2022. 11. 25. 12:06
반응형
SMALL

플라이웨이트(Flyweight)

플라이웨이트 디자인 패턴은 특정 유형의 여러 인스턴스 간에 무거운 개체의 상태를 공유할 수 있는 패턴입니다.

package flyweight

import "time"

const (
	TEAM_A = iota
	TEAM_B
)

type Team struct {
	ID             uint64
	Name           int
	Shield         []byte
	Players        []Player
	HistoricalData []HistoricalData
}

type Player struct {
	Name         string
	Surname      string
	PreviousTeam uint64
	Photo        []byte
}

type HistoricalData struct {
	Year          uint8
	LeagueResults []Match
}

type Match struct {
	Date          time.Time
	VisitorID     uint64
	LocalID       uint64
	LocalScore    byte
	VisitorScore  byte
	LocalShoots   uint16
	VisitorShoots uint16
}

type teamFlyweightFactory struct {
	createdTeams map[int]*Team
}

func NewTeamFactory() teamFlyweightFactory {
	return teamFlyweightFactory{
		createdTeams: make(map[int]*Team),
	}
}

func (t *teamFlyweightFactory) GetTeam(teamID int) *Team {
	if t.createdTeams[teamID] != nil {
		return t.createdTeams[teamID]
	}

	team := getTeamFactory(teamID)
	t.createdTeams[teamID] = &team
	return t.createdTeams[teamID]
}

func (t *teamFlyweightFactory) GetNumberOfObjects() int {
	return len(t.createdTeams)
}

func getTeamFactory(team int) Team {
	switch team {
	case TEAM_B:
		return Team{
			ID:   2,
			Name: TEAM_B,
		}
	default:
		return Team{
			ID:   1,
			Name: TEAM_A,
		}
	}
}

Test Code

package flyweight

import (
	"fmt"
	"testing"
)

func TestTeamFlyweightFactory_GetTeam(t *testing.T) {
	factory := NewTeamFactory()

	teamA1 := factory.GetTeam(TEAM_A)
	if teamA1 == nil {
		t.Error("The pointer to the TEAM_A was nil")
	}

	teamA2 := factory.GetTeam(TEAM_A)
	if teamA2 == nil {
		t.Error("The pointer to the TEAM_A was nil")
	}

	if teamA1 != teamA2 {
		t.Error("TEAM_A pointers weren't the same")
	}

	if factory.GetNumberOfObjects() != 1 {
		t.Errorf("The number of objects created was not 1: %d\n", factory.GetNumberOfObjects())
	}
}

func Test_HighVolume(t *testing.T) {
	factory := NewTeamFactory()
	teams := make([]*Team, 500000*2)
	for i := 0; i < 500000; i++ {
		teams[i] = factory.GetTeam(TEAM_A)
	}
	for i := 500000; i < 2*500000; i++ {
		teams[i] = factory.GetTeam(TEAM_B)
	}
	if factory.GetNumberOfObjects() != 2 {
		t.Errorf("The number of objects created was not 2: %d\n", factory.GetNumberOfObjects())
	}
	for i := 0; i < 3; i++ {
		fmt.Printf("Pointer %d points to %p and is located in %p\n", i, teams[i], &teams[i])
	}
}

전략(Strategy)

전략 디자인 패턴은 특정 기능을 달성하기 위해 서로 다른 알고리즘을 사용합니다. 이러한 알고리즘은 인터페이스 뒤에 숨겨져 있으며, 물론 교환 가능해야 합니다. 모든 알고리즘이 다른 방식으로 동일한 기능을 달성합니다. 

package main

import (
	"flag"
	"log"
	"os"

	"github.com/leo-themedium/golang/designPatterns/strategy/shapes"
)

var output = flag.String("output", "console", "The output to use between 'console' and 'image' file")

func main() {
	flag.Parse()

	activeStrategy, err := shapes.Factory(*output)
	if err != nil {
		log.Fatal(err)
	}

	switch *output {
	case shapes.TEXT_STRATEGY:
		activeStrategy.SetWriter(os.Stdout)
	case shapes.IMAGE_STRATEGY:
		w, err := os.Create("/tmp/image.jpg")
		if err != nil {
			log.Fatal("Error opening image")
		}
		defer w.Close()

		activeStrategy.SetWriter(w)
	}

	err = activeStrategy.Draw()
	if err != nil {
		log.Fatal(err)
	}
}
package strategy

import "io"

type Output interface {
	Draw() error
	SetLog(io.Writer)
	SetWriter(io.Writer)
}

type DrawOutput struct {
	Writer    io.Writer
	LogWriter io.Writer
}

func (d *DrawOutput) SetLog(w io.Writer) {
	d.LogWriter = w
}

func (d *DrawOutput) SetWriter(w io.Writer) {
	d.Writer = w
}
package shapes

import (
	"fmt"
	"os"

	"github.com/leo-themedium/golang/designPatterns/strategy"
)

const (
	TEXT_STRATEGY  = "text"
	IMAGE_STRATEGY = "image"
)

func Factory(s string) (strategy.Output, error) {
	switch s {
	case TEXT_STRATEGY:
		return &TextSquare{
			DrawOutput: strategy.DrawOutput{
				LogWriter: os.Stdout,
			},
		}, nil
	case IMAGE_STRATEGY:
		return &ImageSquare{
			DrawOutput: strategy.DrawOutput{
				LogWriter: os.Stdout,
			},
		}, nil
	default:
		return nil, fmt.Errorf("Strategy '%s' not found\n", s)
	}
}
package shapes

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"

	"github.com/leo-themedium/golang/designPatterns/strategy"
)

type ImageSquare struct {
	strategy.DrawOutput
}

func (i *ImageSquare) Draw() error {
	width := 800
	height := 600
	bgColor := image.Uniform{color.RGBA{0, 0, 0, 1}}
	origin := image.Point{0, 0}
	quality := &jpeg.Options{Quality: 75}

	bgRectangle := image.NewRGBA(image.Rectangle{
		Min: origin,
		Max: image.Point{X: width, Y: height},
	})

	draw.Draw(bgRectangle, bgRectangle.Bounds(), &bgColor, origin, draw.Src)

	squareWidth := 200
	squareHeight := 200
	squareColor := image.Uniform{color.RGBA{R: 255, G: 0, B: 0, A: 1}}
	square := image.Rect(0, 0, squareWidth, squareHeight)
	square = square.Add(image.Point{
		X: (width / 2) - (squareWidth / 2),
		Y: (height / 2) - (squareHeight / 2),
	})
	squareImg := image.NewRGBA(square)

	draw.Draw(bgRectangle, squareImg.Bounds(), &squareColor, origin, draw.Src)

	if i.Writer == nil {
		return fmt.Errorf("No writer stored on ImageSquare")
	}
	if err := jpeg.Encode(i.Writer, bgRectangle, quality); err != nil {
		return fmt.Errorf("Error writing image to disk")
	}

	if i.LogWriter != nil {
		i.LogWriter.Write([]byte("Image written in provided writer\n"))
	}

	return nil

}
package shapes

import "github.com/leo-themedium/golang/designPatterns/strategy"

type TextSquare struct {
	strategy.DrawOutput
}

func (t *TextSquare) Draw() error {
	t.Writer.Write([]byte("Circle"))
	return nil
}

역할 사슬(Chain of Responsibility)

단일 책임 원칙은 유형, 기능, 방법 또는 이와 유사한 추상화가 하나의 단일 책임만 가지고 있어야 함을 의미하고, 이를 상당히 잘 수행해야 한다는 것을 의미합니다. 이러한 방식으로, 우리는 특정한 한 가지를 각각 달성하는 많은 기능을 일부 구조, 슬라이스, 맵 등에 적용할 수 있습니다. 이러한 추상화 중 많은 부분을 논리적인 방식으로 순서대로 실행되도록 체인화할 수 있습니다.

package chainofresponsibility

import (
	"fmt"
	"io"
	"strings"
)

type ChainLogger interface {
	Next(string)
}

type FirstLogger struct {
	NextChain ChainLogger
}

func (fl *FirstLogger) Next(s string) {
	fmt.Printf("First logger: %s\n", s)

	if fl.NextChain != nil {
		fl.NextChain.Next(s)
	}
}

type SecondLogger struct {
	NextChain ChainLogger
}

func (sl *SecondLogger) Next(s string) {
	if strings.Contains(strings.ToLower(s), "hello") {
		fmt.Printf("Second logger: %s\n", s)
		if sl.NextChain != nil {
			sl.NextChain.Next(s)
		}
		return
	}
	fmt.Printf("Finishing in second logging\n\n")
}

type WriterLogger struct {
	NextChain ChainLogger
	Writer    io.Writer
}

func (wl *WriterLogger) Next(s string) {
	if wl.Writer != nil {
		wl.Writer.Write([]byte("WriterLogger: " + s))
	}
	if wl.NextChain != nil {
		wl.NextChain.Next(s)
	}
}

Test Code

package chainofresponsibility

import (
	"fmt"
	"strings"
	"testing"
)

type myTestWriter struct {
	receivedMessage *string
}

func (m *myTestWriter) Write(p []byte) (int, error) {
	if m.receivedMessage == nil {
		m.receivedMessage = new(string)
	}
	tempMessage := fmt.Sprintf("%s%s", *m.receivedMessage, p)
	m.receivedMessage = &tempMessage
	return len(p), nil
}

func (m *myTestWriter) Next(s string) {
	m.Write([]byte(s))
}

func TestCreateDefaultChain(t *testing.T) {
	//Our test ChainLogger
	myWriter := myTestWriter{}
	writerLogger := WriterLogger{Writer: &myWriter}
	second := SecondLogger{NextChain: &writerLogger}
	chain := FirstLogger{NextChain: &second}
	t.Run("3 loggers, 2 of them writes to console, second only if it founds "+
		"the word 'hello', third writes to some variable if second found 'hello'",
		func(t *testing.T) {
			chain.Next("message that breaks the chain\n")
			if myWriter.receivedMessage != nil {
				t.Fatal("Last link should not receive any message")
			}
			chain.Next("Hello\n")
			if !strings.Contains(*myWriter.receivedMessage, "Hello") {
				t.Fatal("Last link didn't received expected message")
			}
		})
}
반응형
LIST