Published on

Strategy Pattern dengan Golang

Authors

Definisi

Strategy design pattern adalah turunan dari behavioral design pattern. Design pattern ini dapat merubah prilaku dari sebuah objek di waktu runtime tanpa perlu melakukan perubahan pada objek kelas tersebut. Ini sangat berguna ketika kita memiliki opsi solusi yg beragam terhadap suatu masalah dan dapat berganti secara cepat sesuai dari masalah yang ada. Saya akan mengambil contoh teknik pisau memotong, mengiris dan mencincang dapat dipilih berdasarkan jenis dan bahan masakan yang diolah menyesuaikan kebutuhan masakan.

Komponen

Strategy Pattern memiliki 3 komponen:

  1. Context: merepresentasikan entitas yang menggunakan berbagai macam strategi.
  2. Strategy interface: interface yang mendefinisikan method yang harus diimplementasi oleh semua concrete strategi.
  3. Concrete strategi: sekumpulan struct/class yang mengimplementasi Strategy interface

Diagram

berikut komponen dalam bentuk diagram UML:

UML-strategy-pattern

Implementasi kode program

Problem 1

Mendesain sistem pembayaran yang support untuk multi metode pembayaran seperti credit card, paypal, dan cryptocurrency

1. Strategy interface (payment_method.go)
package main

type PaymentMethod interface {
 Pay(amount float64) string
}
2. Concrete Strategies: CreditCard, PayPal, and Cryptocurrency (payment_type.go)
package main

type CreditCard struct {
 name, cardNumber string
}

func (c *CreditCard) Pay(amount float64) string {
 return fmt.Sprintf("Paid %.2f using Credit Card (%s)", amount, c.cardNumber)
}

type PayPal struct {
 email string
}

func (p *PayPal) Pay(amount float64) string {
 return fmt.Sprintf("Paid %.2f using PayPal (%s)", amount, p.email)
}

type Cryptocurrency struct {
 walletAddress string
}

func (c *Cryptocurrency) Pay(amount float64) string {
 return fmt.Sprintf("Paid %.2f using Cryptocurrency (%s)", amount, c.walletAddress)
}
3. Context: (Shopping Cart)
package main

type ShoppingCart struct {
 items       []Item
 paymentMethod PaymentMethod
}

func (s *ShoppingCart) SetPaymentMethod(paymentMethod PaymentMethod) {
 s.paymentMethod = paymentMethod
}

func (s *ShoppingCart) Checkout() string {
 var total float64
 for _, item := range s.items {
  total += item.price
 }
 return s.paymentMethod.Pay(total)
}
4. client (main.go)
package main
func main() {
 shoppingCart := &ShoppingCart{
  items: []Item{
   {"Laptop", 1500},
   {"Smartphone", 1000},
  },
 }

 creditCard := &CreditCard{"Chidozie C. Okafor", "4111-1111-1111-1111"}
 paypal := &PayPal{"chidosiky2015@gmail.com"}
 cryptocurrency := &Cryptocurrency{"0xAbcDe1234FghIjKlMnOp"}

 shoppingCart.SetPaymentMethod(creditCard)
 fmt.Println(shoppingCart.Checkout())

 shoppingCart.SetPaymentMethod(paypal)
 fmt.Println(shoppingCart.Checkout())

 shoppingCart.SetPaymentMethod(cryptocurrency)
 fmt.Println(shoppingCart.Checkout())
}

Output

output-strategy-pattern

Problem 2

Mendesain sistem ratelimiter menggunakan tipe ratelimiter yang berbeda seperti FixedWindowRateLimiter dan SlidingWindowRateLimiter.

1. Strategy interface (rate_limiter.go)
package main

type RateLimiter interface {
	Allow() bool
}
2. Concrete Strategies: FixedWindowRateLimiter dan SlidingWindowRateLimiter (rate_limiter_type.go)
// FixedWindowRateLimiter is a concrete implementation of RateLimiter using fixed window algorithm.
type FixedWindowRateLimiter struct {
	window   time.Duration
	limit    int
	requests map[string]int64
}

// NewFixedWindowRateLimiter creates a new FixedWindowRateLimiter instance.
func NewFixedWindowRateLimiter(window time.Duration, limit int) *FixedWindowRateLimiter {
	return &FixedWindowRateLimiter{
		window:   window,
		limit:    limit,
		requests: make(map[string]int64),
	}
}

// Allow checks if a request is allowed using fixed window rate limiting strategy.
func (limiter *FixedWindowRateLimiter) Allow() bool {
	now := time.Now()
	for k, v := range limiter.requests {
		if now.Sub(time.Unix(0, v)) > limiter.window {
			delete(limiter.requests, k)
		}
	}

	if len(limiter.requests) >= limiter.limit {
		return false
	}

	limiter.requests[now.String()] = int64(now.UnixNano())
	return true
}

// SlidingWindowRateLimiter is a concrete implementation of RateLimiter using sliding window algorithm.
type SlidingWindowRateLimiter struct {
	window   time.Duration
	limit    int
	requests []int64
}

// NewSlidingWindowRateLimiter creates a new SlidingWindowRateLimiter instance.
func NewSlidingWindowRateLimiter(window time.Duration, limit int) *SlidingWindowRateLimiter {
	return &SlidingWindowRateLimiter{
		window:   window,
		limit:    limit,
		requests: make([]int64, 0, limit),
	}
}

// Allow checks if a request is allowed using sliding window rate limiting strategy.
func (limiter *SlidingWindowRateLimiter) Allow() bool {
	now := time.Now().UnixNano()

	// Remove expired requests
	for len(limiter.requests) > 0 && now-limiter.requests[0] > int64(limiter.window) {
		limiter.requests = limiter.requests[1:]
	}

	if len(limiter.requests) >= limiter.limit {
		return false
	}

	limiter.requests = append(limiter.requests, now)
	return true
}


3. Context: (rate_limiter_context.go)
package main

// RateLimiterContext holds the reference to the rate limiter strategy.
type RateLimiterContext struct {
	strategy RateLimiter
}

// NewRateLimiterContext creates a new RateLimiterContext instance with the specified strategy.
func NewRateLimiterContext(strategy RateLimiter) *RateLimiterContext {
	return &RateLimiterContext{
		strategy: strategy,
	}
}
4. client (main.go)
package main
import (
"time"
"fmt"
)
func main() {
	// Create a new rate limiter context with the fixed window rate limiting strategy.
	fixedWindowLimiter := NewRateLimiterContext(NewFixedWindowRateLimiter(1*time.Second, 5))

	// Simulate requests using the fixed window strategy
	fmt.Println("Fixed Window Rate Limiting:")
	for i := 0; i < 10; i++ {
		if fixedWindowLimiter.Allow() {
			fmt.Println("Request allowed")
		} else {
			fmt.Println("Request rate-limited")
		}
		time.Sleep(200 * time.Millisecond)
	}

	// Create a new rate limiter context with the sliding window rate limiting strategy.
	slidingWindowLimiter := NewRateLimiterContext(NewSlidingWindowRateLimiter(1*time.Second, 5))

	// Simulate requests using the sliding window strategy
	fmt.Println("\nSliding Window Rate Limiting:")
	for i := 0; i < 10; i++ {
		if slidingWindowLimiter.Allow() {
			fmt.Println("Request allowed")
		} else {
			fmt.Println("Request rate-limited")
		}
		time.Sleep(200 * time.Millisecond)
	}
}

Output

output-strategy-pattern-2