- Published on
Strategy Pattern dengan Golang
- Authors
- Name
- Aulia' Illahi
- @alfath_go
- Name
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:
- Context: merepresentasikan entitas yang menggunakan berbagai macam strategi.
- Strategy interface: interface yang mendefinisikan method yang harus diimplementasi oleh semua concrete strategi.
- Concrete strategi: sekumpulan struct/class yang mengimplementasi Strategy interface
Diagram
berikut komponen dalam bentuk diagram UML:
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
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)
}
}