[꼼꼼한 개발자] 꼼코더

[WEB] 디자인 패턴이란?(Design pattern) 간단하고 쉽게 이해하기 본문

간단하고 쉽게/WEB

[WEB] 디자인 패턴이란?(Design pattern) 간단하고 쉽게 이해하기

꼼코더 2023. 3. 20. 14:06
반응형

😮 디자인 패턴(Design pattern)이란?

소프트웨어 개발 과정에서 자주 발생하는 문제들을 해결하기 위해 공통적으로 사용되는 설계 방법을 정리한 것이다.

디자인 패턴을 사용하면 개발자들은 이미 검증된 해결책을 활용하여 소프트웨어 시스템을 더욱 효율적이고 유연하게 구현할 수 있다.


📚 디자인 패턴의 대표 3가지
디자인 패턴은 크게 3가지 카테고리로 분류할 수 있다.

 

  • 생성 패턴(Creational Pattern)
    • 객체 생성에 관련된 패턴.
    • 객체를 생성하는 방법과 객체의 유형을 결정하는 방법에 대한 문제를 해결한다.
  • 구조 패턴(Structural Pattern)
    • 클래스와 객체를 조합하여 더 큰 구조를 만들 수 있는 패턴.
    • 객체 간의 관계를 구성하는 방법에 대한 문제를 해결한다.
  • 행동 패턴(Behavioral Pattern)
    • 객체들이 상호작용하는 방법과 책임을 분산하는 방법에 대한 문제를 해결.

 

이제 예제를 통하여 자세히 알아보자

 



🥜 생성 패턴 예제: 싱글톤 패턴(Singleton Pattern)

생성 패턴 중 하나인 싱글톤 패턴은 어떤 클래스의 인스턴스가 딱 하나만 생성되도록 보장하는 패턴이다.

 

싱글톤 패턴 사용시
1. 어디서든지 이 인스턴스에 접근가능

2. 여러 곳에서 동시에 인스턴스를 생성하는 일을 방지
3. 전역 변수를 사용하지 않고도 객체의 공유가 가능.

 

public class Singleton {
    private static Singleton instance = null; // 싱글톤 인스턴스를 담는 private static 변수

    private Singleton() {} // private 생성자로 외부에서 객체 생성을 막음

    // 스레드 안전한 싱글톤 패턴 구현을 위한 getInstance() 메서드
    public static Singleton getInstance() {
        if (instance == null) { // 인스턴스가 생성되어있지 않을 때
            synchronized (Singleton.class) { // 여러 스레드가 동시에 접근하는 경우를 대비한 synchronized 블록
                if (instance == null) { // 인스턴스가 생성되어있지 않을 때
                    instance = new Singleton(); // 싱글톤 인스턴스를 생성함
                }
            }
        }
        return instance; // 싱글톤 인스턴스 반환
    }
}

위 코드에서 getInstance() 메서드는 해당 클래스의 인스턴스를 반환한다.

이 메서드는 처음 호출될 때 인스턴스가 생성되고, 이후 호출될 때는 이미 생성된 인스턴스가 반환된다.

따라서, 이 클래스의 인스턴스는 하나만 존재하게 됩니다.

 

위 코드에서는 또한 인스턴스가 여러 스레드에서 동시에 생성되는 것을 막기 위해 double-checked locking 기법을 사용했다.

이를 통해 인스턴스를 최초 한 번만 생성하고, 이후에는 생성된 인스턴스를 반환하게 된다.


🏄🏻‍♂️ 구조 패턴 예제: 어댑터 패턴(Adapter Pattern)

어댑터 패턴은 호환되지 않는 인터페이스를 갖는 두 클래스를 연결하여 함께 작동할 수 있도록 만드는 패턴.

즉, 어떤 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 패턴이다.

public interface Target { // Target 인터페이스
    void request(); // request 메서드 정의
}

public class Adaptee { // Adaptee 클래스
    public void specificRequest() { // specificRequest 메서드 정의
        System.out.println("Adaptee's specific request"); // "Adaptee's specific request" 출력
    }
}

public class Adapter implements Target { // Target 인터페이스를 구현한 Adapter 클래스
    private Adaptee adaptee; // Adaptee 인스턴스를 갖는 adaptee 변수

    public Adapter(Adaptee adaptee) { // Adaptee 인스턴스를 받는 생성자
        this.adaptee = adaptee; // 인스턴스 변수 adaptee 초기화
    }

    public void request() { // request 메서드 구현
        adaptee.specificRequest(); // Adaptee의 specificRequest 메서드 호출
    }
}

public class Client { // Client 클래스
    public static void main(String[] args) { // main 메서드
        Adaptee adaptee = new Adaptee(); // Adaptee 인스턴스 생성
        Target target = new Adapter(adaptee); // Adapter 인스턴스 생성, Adaptee 인스턴스를 매개변수로 전달
        target.request(); // Target 인터페이스의 request 메서드 호출
    }
}

 

 

위 코드에서 Adaptee 클래스는 특정 요구사항을 처리하기 위한 메서드 specificRequest()를 가지고 있다.

하지만 Target 인터페이스는 request() 메서드를 갖기 때문에, Adaptee와 Target은 서로 다른 인터페이스를 가지고 있다.

이때, Adapter 클래스가 두 인터페이스를 연결하여 호환성을 제공한다.

 


🔔 행동 패턴 예제: 옵저버 패턴(Observer Pattern)

옵저버 패턴은 객체 사이의 일대다 관계를 정의하여, 어떤 객체의 상태가 변할 때

그 객체에 의존하는 다른 객체들이 자동으로 알림을 받도록 하는 패턴입니다.

이를 위해 Subject(주체)와 Observer(관찰자)라는 인터페이스를 정의하고, 이를 구현하는 클래스를 작성한다.

 

// Subject 인터페이스
interface Subject {
    void registerObserver(Observer observer); // Observer 등록
    void removeObserver(Observer observer); // Observer 제거
    void notifyObservers(); // 모든 Observer에게 알림 전송
}

// Observer 인터페이스
interface Observer {
    void update(); // Subject로부터 알림을 받을 때 호출되는 메서드
}

// 구체적인 Subject 클래스
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>(); // Observer 리스트
    private int state; // 상태 정보

    public void setState(int state) {
        this.state = state;
        notifyObservers(); // 상태 변경 시 Observer에게 알림 전송
    }

    public void registerObserver(Observer observer) {
        observers.add(observer); // Observer 등록
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer); // Observer 제거
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(); // 모든 Observer에게 알림 전송
        }
    }
}

// 구체적인 Observer 클래스
class ConcreteObserver implements Observer {
    private int state; // 상태 정보
    private Subject subject; // Subject 참조

    public ConcreteObserver(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this); // 자신을 Subject의 Observer로 등록
    }

    public void update() {
        state = subject.getState(); // Subject의 상태 정보 가져오기
        // 상태 정보에 대한 처리 로직
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject(); // Subject 객체 생성
        ConcreteObserver observer1 = new ConcreteObserver(subject); // Observer1 객체 생성 및 Subject에 등록
        ConcreteObserver observer2 = new ConcreteObserver(subject); // Observer2 객체 생성 및 Subject에 등록
        subject.setState(1); // Subject의 상태 변경
    }
}
Comments