[Java] 디자인 패턴에 대해서 (싱글톤, 팩토리, 옵저버)
KUKJIN LEE • 2주 전 작성
디자인 패턴(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"); // 두 옵저버가 자동으로 상태를 통지받음
}
}