4 minute read

디자인 패턴

싱글톤 패턴

  • 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴이다.
  • 하나의 인스턴스만을 생성하며, 모든 클라이언트에게 동일한 인스턴스를 반환한다.
  • private 생성자를 가지는 특징을 가지며, 생성된 싱글톤 오브젝트는 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.

문제점

  • 의존 관계상 클라이언트가 구체 클래스에 의존한다.
  • private 생성자 때문에 테스트가 어렵다.
  • 객체 인스턴스를 하나만 생성해서 공유하는 방식 때문에, 싱글톤 객체를 stateful하게 설계 했을 경우 장애 발생요인이 된다.
  • 싱글톤의 단점을 해결하기 위해 무상태(stateless)로 설계해야 한다.
  • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
  • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기 전용으로 만들고, 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
public class Singleton {
    private static Singleton instance;
    
    private Singleton() { }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

가교 패턴 (= Bridge pattern)

  • 추상부와 구현부를 분리하는 디자인 패턴
  • 기능은 인터페이스를 통해 정의 & 이용되고, 해당 인터페이스를 따르는 클래스를 통해 구현된다.
  • 클라이언트에서 추상부와 구현부를 독립적으로 수정 및 확장할 수 있다.
  • 객체지향 설계의 SOLID 원칙 중 단일 책임 원칙(SRP)과 개방 폐쇄 원칙(OCP)에 부합한 패턴이다.

// Abstraction. 추상부
public abstract class Shape {
    protected Color color;

    public Shape(Color color) {
        this.color = color;
    }

    public abstract void draw();
}

// Implementor. 구현 인터페이스
public interface Color {
    void applyColor();
}

// Concrete Implementors 구현부
public class RedColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying red color");
    }
}

public class BlueColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("Applying blue color");
    }
}

// Refined Abstractions
public class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("Drawing a Circle with ");
        color.applyColor();
    }
}

public class Square extends Shape {
    public Square(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("Drawing a Square with ");
        color.applyColor();
    }
}

전략 패턴 (Strategy Pattern)

  • 알고리즘을 객체 단위로 캡슐화하는 디자인 패턴
  • 알고리즘은 인터페이스를 통해 정의 및 이용되고 , 해당 인터페이스를 따르는 클래스를 통해 구현된다.
  • 해당 패턴을 통해서 사용자는 알고리즘을 필요에 따라 바꿔서 사용할 수 있다.
  • 객체지향 설계의 SOLID 원칙 중 개방 폐쇄 원칙(OCP)에 부합한 패턴

  • 브릿지 패턴과 구조가 비슷하지만 목적에 차이가 있다.
    • 브릿지 패턴이 추상과 구현의 분리를 통한 독립적 개발의 용이성에 중점을 둔다면, 전략 패턴은 알고리즘의 캡슐화를 통한 알고리즘 변경의 유연성에 중점을 둔다.

// Strategy Interface
public interface PaymentStrategy {
    void pay(int amount);
}

// Concrete Strategies : 객체에 따라 알고리즘을 달리 구현
public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    private String name;

    public CreditCardPayment(String cardNumber, String name) {
        this.cardNumber = cardNumber;
        this.name = name;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card.");
    }
}

public class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal.");
    }
}

// Context
public class ShoppingCart {
    private List<Item> items;

    public ShoppingCart() {
        this.items = new ArrayList<>();
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public void checkout(PaymentStrategy paymentMethod) {
        int totalAmount = calculateTotal();
        paymentMethod.pay(totalAmount);
    }

    private int calculateTotal() {
        int total = 0;
        for (Item item : items) {
            total += item.getPrice();
        }
        return total;
    }
}

빌더 패턴

  • 빌더 패턴은 복잡한 객체를 생성하는 과정을 캡슐화하고, 객체를 생성하기 위한 단계별 빌드를 제공하는 패턴
  • 객체 생성을 단순화하고, 객체의 속성을 설정하는 과정을 명확하게 만들어 준다.
public class Person {
    private String firstName;
    private String lastName;
    private int age;
    
    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
    }
    
    public static class Builder {
        private String firstName;
        private String lastName;
        private int age;
        
        public Builder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public Person build() {
            return new Person(this);
        }
    }
}

팩토리 메서드 패턴

  • 객체 생성시 어떤 클래스의 인스턴스로 생성할 지 서브 클래스로 미루는 디자인 패턴이다.
  • 슈퍼 클래스에서는 객체 생성에 관한 인터페이스를 정의하고, 서브 클래스에서 실제 객체 생성을 담당한다.
  • 생성자 대신에 팩토리 메소드 사용을 고려할 수 있다.

// 객체 생성에 관한 인터페이스를 정의
public abstract class Animal {
    public abstract String speak();
}

public class Dog extends Animal {
    @Override
    public String speak() {
        return "Woof!";
    }
}

public class Cat extends Animal {
    @Override
    public String speak() {
        return "Meow!";
    }
}

public interface AnimalFactory {
    Animal createAnimal();
}

public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}

public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}

퍼사드 패턴

  • 서브시스템의 복잡성을 감추고, 간단한 인터페이스를 제공하는 패턴이다.
  • 복잡한 시스템을 단순하게 다룰 수 있다.
class CPU {
    public void processData() {
        System.out.println("Processing data...");
    }
}

class Memory {
    public void load() {
        System.out.println("Loading data...");
    }
}

class HardDrive {
    public void readData() {
        System.out.println("Reading data...");
    }
}

class ComputerFacade {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public ComputerFacade() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }

    public void start() {
        System.out.println("Starting the computer...");
        cpu.processData();
        memory.load();
        hardDrive.readData();
        System.out.println("Computer started successfully.");
    }
}

  • 바운디드 컨텍스트로 구분된 각각의 독립적인 애플리케이션을 UI 서버를 통해 파사드 역할을 담당하도록 두고, 각 바운디드 컨텍스트에서 UI 서버와 통신하기 위해 HTTP, Protobuf, Thrift와 같은 방식을 이용할 수 있다.

Updated:

Leave a comment