Structural Design Patterns - Decorator and Facade

Publish date: 2024-07-18
Tags: System-Design, Software-Engineering

Decorator

Facade


Decorator

Problem

public class EmailService {
    public void sendEmail(String email, String message) {
        ...
    }
}
public interface Communicator {
    void send(String target, String message);
}

Decorator

Decorator

Solution (Java)

public interface Communicator {
    void send(String target, String message);
}
public class EmailService implements Communicator {
    @Override
    public void send(String email, String message) {
        ...
    }
}
public abstract class CommunicatorDecorator implements Communicator {
    protected Communicator communicator;

    public CommunicatorDecorator(Communicator communicator) {
        this.communicator = communicator;
    }
}
public class PhoneService extends CommunicatorDecorator {
    public PhoneService(Communicator communicator) {
        super(communicator);
    }

    @Override
    public void send(String phone, String message) {
        communicator.send(phone, message);
        sendPhoneNotification(phone, message);
    }

    private void sendPhoneNotification(String phone, String message) {
        ...
    }
}
public class Client {
    public static void main(String[] args) {
        Communicator communicator = new EmailService();
        Communicator phoneService = new PhoneService(communicator);
        Communicator slackService = new SlackService(phoneService);
        slackService.send("user", "Hello");
    }
}

Decorator Pattern in Python

from abc import ABC, abstractmethod

class Datasource(ABC):
    @abstractmethod
    def read(self):
        pass

    @abstractmethod
    def write(self, value):
        pass

class BaseDecorator(Datasource):
    def __init__(self, next_layer):
        self._next_layer = next_layer

class FileDatasource(Datasource):
    def read(self):
        return "Base"

    def write(self, value):
        print(value)

class CompressionDecorator(BaseDecorator):
    def __init__(self, datasource):
        super().__init__(datasource)

    def read(self):
        compressed = self._next_layer.read()
        return self._decompress(compressed)

    def _decompress(self, compressed):
        return f"{compressed} - Decompressed"

    def write(self, value):
        compressed = self._compress(value)
        self._next_layer.write(compressed)

    def _compress(self, value):
        return f"{value} - Compressed"

class EncryptionDecorator(BaseDecorator):
    def __init__(self, next_layer):
        super().__init__(next_layer)

    def read(self):
        value = self._next_layer.read()
        return self._decrypt(value)

    def _decrypt(self, value):
        return f"{value} - Decrypted"

    def write(self, value):
        encrypted = self._encrypt(value)
        self._next_layer.write(encrypted)

    def _encrypt(self, value):
        return f"{value} - Encrypted"

if __name__ == "__main__":
    datasource = FileDatasource()
    encrypted_datasource = EncryptionDecorator(datasource)
    compressed_encrypted_datasource = CompressionDecorator(encrypted_datasource)

    compressed_encrypted_datasource.write("Test Data")
    print(compressed_encrypted_datasource.read())

Decorator Pattern in Golang

package decorator

import "fmt"

type Datasource interface {
	Read() string
	Write(value string)
}

type BaseDecorator struct {
	nextLayer Datasource
}

func (b *BaseDecorator) Read() string {
	return b.nextLayer.Read()
}

func (b *BaseDecorator) Write(value string) {
	b.nextLayer.Write(value)
}

type FileDatasource struct{}

func (f *FileDatasource) Read() string {
	return "Base"
}

func (f *FileDatasource) Write(value string) {
	fmt.Println(value)
}

type CompressionDecorator struct {
	BaseDecorator
}

func NewCompressionDecorator(datasource Datasource) *CompressionDecorator {
	return &CompressionDecorator{
		BaseDecorator{nextLayer: datasource},
	}
}

func (c *CompressionDecorator) Read() string {
	compressed := c.nextLayer.Read()
	return c.decompress(compressed)
}

func (c *CompressionDecorator) decompress(compressed string) string {
	return compressed + " - Decompressed"
}

func (c *CompressionDecorator) Write(value string) {
	compressed := c.compress(value)
	c.nextLayer.Write(compressed)
}

func (c *CompressionDecorator) compress(value string) string {
	return value + " - Compressed"
}

type EncryptionDecorator struct {
	BaseDecorator
}

func NewEncryptionDecorator(datasource Datasource) *EncryptionDecorator {
	return &EncryptionDecorator{
		BaseDecorator{nextLayer: datasource},
	}
}

func (e *EncryptionDecorator) Read() string {
	value := e.nextLayer.Read()
	return e.decrypt(value)
}

func (e *EncryptionDecorator) decrypt(value string) string {
	return value + " - Decrypted"
}

func (e *EncryptionDecorator) Write(value string) {
	encrypted := e.encrypt(value)
	e.nextLayer.Write(encrypted)
}

func (e *EncryptionDecorator) encrypt(value string) string {
	return value + " - Encrypted"
}

Advantages


Facade

Problem

public class Order {
    private PaymentGateway paymentGateway;
    private Inventory inventory;
    private EmailService emailService;
    private ShippingService shippingService;
    private AnalyticsService analyticsService;

    public void checkout() {
        paymentGateway.charge();
        inventory.update();
        emailService.send();
        shippingService.add();
        analyticsService.update();
    }
}

Solution (Java)

public class OrderProcessor {
    private PaymentGateway paymentGateway;
    private Inventory inventory;
    private EmailService emailService;
    private ShippingService shippingService;
    private AnalyticsService analyticsService;

    public void process() {
        paymentGateway.charge();
        inventory.update();
        emailService.send();
        shippingService.add();
        analyticsService.update();
    }
}
public class Order {
    private OrderProcessor orderProcessor;

    public void checkout() {
        orderProcessor.process();
    }
}

Facade Pattern in Python

from abc import ABC, abstractmethod

class OrderManager(ABC):
    @abstractmethod
    def create_order(self) -> None:
        pass

class OrderManagerImpl(OrderManager):
    def __init__(self, order_processor: 'OrderProcessor'):
        self.order_processor = order_processor

    def create_order(self) -> None:
        self.order_processor.process()

class AnalyticsService:
    def track(self) -> None:
        print("Analytics created")

class InventoryService:
    def check_inventory(self) -> None:
        print("Inventory checked")

class OrderProcessor:
    def __init__(self, recommendation_service: 'RecommendationService', 
                 payment_service: 'PaymentService', 
                 warehouse_processor: 'WarehouseProcessor'):
        self.recommendation_service = recommendation_service
        self.payment_service = payment_service
        self.warehouse_processor = warehouse_processor

    def process(self) -> None:
        self.warehouse_processor.process()
        self.recommendation_service.recommend()
        self.payment_service.pay()

class OrderFlowProcessor:
    def __init__(self):
        self.payment_service = PaymentService()
        self.inventory_service = InventoryService()
        self.recommendation_service = RecommendationService()
        self.analytics_service = AnalyticsService()

    def process(self) -> None:
        self.payment_service.pay()
        # update
        self.inventory_service.check_inventory()
        # analytics
        self.recommendation_service.recommend()
        self.analytics_service.track()

class PaymentService:
    def pay(self) -> None:
        print("Payment done")

class RecommendationService:
    def recommend(self) -> None:
        print("Recommendation created")

class WarehouseProcessor:
    def __init__(self, inventory_service: InventoryService, 
                 analytics_service: AnalyticsService):
        self.inventory_service = inventory_service
        self.analytics_service = analytics_service

    def process(self) -> None:
        self.inventory_service.check_inventory()
        self.analytics_service.track()

if __name__ == "__main__":
    order_flow_processor = OrderFlowProcessor()
    order_flow_processor.process()

Facade Pattern in Golang

package facade

import "fmt"

// OrderManager interface
type OrderManager interface {
	CreateOrder()
}

// OrderManagerImpl struct
type OrderManagerImpl struct {
	orderProcessor *OrderProcessor
}

func (o *OrderManagerImpl) CreateOrder() {
	o.orderProcessor.Process()
}

// OrderProcessor struct
type OrderProcessor struct {
	recommendationService *RecommendationService
	paymentService        *PaymentService
	warehouseProcessor    *WarehouseProcessor
}

func (o *OrderProcessor) Process() {
	o.warehouseProcessor.Process()
	o.recommendationService.Recommend()
	o.paymentService.Pay()
}

// OrderFlowProcessor struct
type OrderFlowProcessor struct {
	paymentService        *PaymentService
	inventoryService      *InventoryService
	recommendationService *RecommendationService
	analyticsService      *AnalyticsService
}

func NewOrderFlowProcessor() *OrderFlowProcessor {
	return &OrderFlowProcessor{
		paymentService:        &PaymentService{},
		inventoryService:      &InventoryService{},
		recommendationService: &RecommendationService{},
		analyticsService:      &AnalyticsService{},
	}
}

func (o *OrderFlowProcessor) Process() {
	o.paymentService.Pay()
	// update
	o.inventoryService.CheckInventory()
	// analytics
	o.recommendationService.Recommend()
	o.analyticsService.Track()
}

// PaymentService struct
type PaymentService struct{}

func (p *PaymentService) Pay() {
	fmt.Println("Payment done")
}

// RecommendationService struct
type RecommendationService struct{}

func (r *RecommendationService) Recommend() {
	fmt.Println("Recommendation created")
}

// WarehouseProcessor struct
type WarehouseProcessor struct {
	inventoryService *InventoryService
	analyticsService *AnalyticsService
}

func (w *WarehouseProcessor) Process() {
	w.inventoryService.CheckInventory()
	w.analyticsService.Track()
}

// AnalyticsService struct
type AnalyticsService struct{}

func (a *AnalyticsService) Track() {
	fmt.Println("Analytics created")
}

// InventoryService struct
type InventoryService struct{}

func (i *InventoryService) CheckInventory() {
	fmt.Println("Inventory checked")
}
Tags: System-Design, Software-Engineering