[Java] 디자인 패턴에 대해서 (싱글톤, 팩토리, 옵저버)

KUKJIN LEE's profile picture

KUKJIN LEE3개월 전 작성

디자인 패턴(Design Patterns)은 소프트웨어 개발에서 자주 발생하는 문제들을 해결하기 위한 재사용 가능한 솔루션입니다. 특정 상황에서 사용할 수 있는 코드 구조를 정의하며, 객체지향 개발의 핵심 원칙인 유연성재사용성을 높이는 데 도움을 줍니다.

 

어디까지나 개발자의 경험을 체계화하여 효율적이고, 보다 나은 소프트웨어 아키텍처를 설계하기 위함이지 100% 정답이 아닙니다.

(개인적으로 팩토리, 옵저버는 꼭 알아가시길 추천드립니다.)

 

1. 싱글톤 패턴 (Singleton Pattern)

싱글톤 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 디자인 패턴입니다. 전역적으로 접근 가능한 단일 객체가 필요할 때 사용됩니다.

  • 로그 관리

  • 설정 관리

  • DB 연결

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnection {
    // 싱글톤 인스턴스를 담을 private static 변수
    private static DatabaseConnection instance;
    private Connection connection;

    // DB 연결에 필요한 정보들
    private String url = "jdbc:mysql://localhost:3306/mydb";
    private String username = "root";
    private String password = "password";

    // private 생성자로 외부에서 객체 생성 방지
    private DatabaseConnection() throws SQLException {
        try {
            // JDBC 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");
            // DB 연결 생성
            this.connection = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
            throw new SQLException("DB 연결에 실패했습니다.");
        }
    }

    // 싱글톤 인스턴스를 제공하는 메서드
    public static DatabaseConnection getInstance() throws SQLException {
        if (instance == null) {
            instance = new DatabaseConnection();
        } else if (instance.getConnection().isClosed()) {
            instance = new DatabaseConnection();
        }
        return instance;
    }

    // DB 연결 객체를 반환하는 메서드
    public Connection getConnection() {
        return connection;
    }
}

 

2. 팩토리 패턴 (Factory Pattern)

팩토리 패턴은 객체 생성 로직을 클라이언트 코드로부터 분리하여, 객체 생성을 서브클래스가 팩토리 클래스에서 처리하는 패턴입니다. 객체를 생성할 때 구체적인 클래스 이름을 알 필요 없고 인터페이스나 추상 클래스만으로 객체를 생성할 수 있게 합니다.

  • 캡슐화를 통한 유지보수 용이

  • 확장성이 높아, 새로운 객체 타입 추가 시 기존 코드 수정 불필요

(최대한 쉽게 설명드리면, 팩토리 패턴은 확장성이 높다고 설명했습니다. 옵저버 패턴과 다르게 팩토리 패턴에서는 `implements Product`를 통해 `ConcreteProductA`, `ConcreteProductB`를 확장하는 걸 확인 할 수 있습니다.)

// Product 인터페이스
interface Product {
    void create();
}

// 구체적인 Product 구현 클래스
class ConcreteProductA implements Product {
    public void create() {
        System.out.println("ConcreteProductA created");
    }
}

class ConcreteProductB implements Product {
    public void create() {
        System.out.println("ConcreteProductB created");
    }
}

// 팩토리 클래스
class ProductFactory {
    public static Product createProduct(String type) {
        if (type.equals("A")) {
            return new ConcreteProductA();
        } else if (type.equals("B")) {
            return new ConcreteProductB();
        }
        throw new IllegalArgumentException("Unknown product type");
    }
}

// 클라이언트 코드
public class FactoryPatternExample {
    public static void main(String[] args) {
        Product productA = ProductFactory.createProduct("A");
        productA.create(); // 출력: ConcreteProductA created
    }
}

 

옵저버 패턴 (Observer Pattern)

옵저버 패턴(Observer Pattern)은 객체 간 1대 다 (One to Many) 관계를 정의하여, 하나의 객체 상태가 변경되었을 때 연관된 다른 객체들이 자동으로 통지받아 업데이트 되도록 하는 패턴입니다. 이벤트 기반 시스템에 적합합니다.

  • 상태 변경을 자동으로 다른 객체에 전파

  • 이벤트 처리, 알림 시스템, 데이터 변경 감지에 적합

(반대로 옵저버 패턴을 쉽게 파악하기 위해서 팩토리 패턴과 비교해보면, `interface Subject` 내부에 이미 `attach`, `detach`, `notifyObservers`가 들어있는 걸 확인할 수 있습니다. 이걸 관찰한다고 보시면 쉽게 이해할 수 있습니다.)

import java.util.ArrayList;
import java.util.List;

// Subject 인터페이스
interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

// ConcreteSubject
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    public String getState() {
        return state;
    }

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

// Observer 인터페이스
interface Observer {
    void update();
}

// ConcreteObserver
class ConcreteObserver implements Observer {
    private ConcreteSubject subject;
    public ConcreteObserver(ConcreteSubject subject) {
        this.subject = subject;
    }
    public void update() {
        System.out.println("Observer notified. Subject state: " + subject.getState());
    }
}

// 클라이언트 코드
public class ObserverPatternExample {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();

        Observer observer1 = new ConcreteObserver(subject);
        Observer observer2 = new ConcreteObserver(subject);

        subject.attach(observer1);
        subject.attach(observer2);

        subject.setState("New State"); // 두 옵저버가 자동으로 상태를 통지받음
    }
}

New Tech Posts