자바 싱글톤 패턴자바 팩토리 패턴자바 옵저버 패턴자바 디자인 패턴디자인 패턴이란?

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

KUKJIN LEE
KUKJIN LEE
2024년 9월 26일
177

디자인 패턴(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"); // 두 옵저버가 자동으로 상태를 통지받음
    }
}

관련 글

[SQL 입문] 필요한 데이터만 콕 집어 가져올 수 있는 WHERE 절

사용자는 테이블에 있는 모든 데이터를 다 보고 싶어 하지는 않습니다. 오히려 특정 항목에 대한 데이터만 가져오고 싶을 때가 훨씬 많습니다. 예를 들어, 어떤 사용자의 이메일이나 ID는 알고 있는데, 그 사람이 언제 우리 앱에 가입했는지 확인하고 싶다고 가정해 봅시다...

2026년 1월 27일10

SQL에서 SELECT란 무엇인가?

데이터베이스를 거대한 서류 보관함이라고 생각하면 됩니다. 보관함 안에는 수많은 데이터가 차곡차곡 쌓입니다. SELECT는 이 보관함에서 "내가 원하는 정보를 찾아줘!"라고 요청하는 명령어입니다. 이 과정을 전문 용어로 '쿼리(Query)'라고 부릅니다. &amp;nbs...

2026년 1월 26일10

[SQL 기초] "언제 하나씩 다 넣어?" 데이터 한 번에 넣기

개발을 하다 보면 데이터베이스(DB)에 샘플 데이터를 대량으로 넣어야 할 때가 있습니다. 메뉴 100개를 추가해야 하는데 INSERT 문을 100번 쓰고 있다면? 너무 비효율적이죠! 오늘은 SQL에서 여러 데이터를 한 번에 넣는 '다중 삽입(Multiple Inse...

2026년 1월 21일13

SQL 데이터 삽입하기

데이터가 없는 데이터베이스는 연료 없는 로켓과 같습니다. 이제 'Missions' 테이블에 새로운 행(row)을 추가하는 방법을 알아보겠습니다. "삽입(Inserting)"은 '데이터 추가'를 의미합니다. 기존 Missions 테이블 ...

2026년 1월 20일12