늘 겸손하게

CS - 디자인 패턴 본문

Computer Science

CS - 디자인 패턴

besforyou999 2023. 11. 11. 15:47

 

디자인 패턴

 

디자인 패턴은 프로그램을 설계할 때 발생했던 문제점을 효율적으로 해결 할 수 있는 코딩 아이디어, 방법론을 말합니다.

 

대표적인 디자인 패턴으로는 '싱글톤 패턴', '옵저버 패턴', '팩토리 패턴' 등이 있습니다.

 

 

싱글톤 패턴

 

하나의 클래스가 하나의 인스턴스만을 가지고 계속 재활용해 메모리를 아끼고, 데이터 공유를 수월하게 하는 디자인 패턴.

 

보통 데이터베이스 연결 모듈에 많이 사용합니다.

 

메모리 사용량을 줄일 수 있는 장점이 존재하나 모듈간의 결합이 강해져 유지보수성이 떨어지고, TDD를 어렵게 하는 단점이 존재합니다.

 

Java

class Singleton {
    private static class singleInstanceHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return singleInstanceHolder.INSTANCE;
    }
}

public class Hello {
    public static void main(String [] args) {
        Singleton a = Singleton.getInstance();
        Singleton b = Singleton.getInstance();
        
        if (a == b) {
            System.out.println(true); // true 출력됨.
        }
    }
}

 

JavaScript

class Singleton {
    constructor() {
        if (!Singleton.instance) {
            Singleton.instance = this
        }
        return Singleton.instance
    }
    
    getInstance() {
        return this.instance
    }
}

const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true

 

싱글톤 패턴의 장점

  1. 같은 인스턴스를 재활용해 메모리 사용을 아낀다.
  2. 메모리 공유하기 유용하다.

싱글톤 패턴의 단점

  1. 모듈간 결합이 강해져 유지보수성이 떨어진다.
  2. TDD를 어렵게 한다. TDD가 잘 이루어지려면 각각의 테스트는 독립적이어야 하나 싱글톤 패턴은 데이터를 공유하기 때문에 독립적인 테스트를 어렵게 한다.

싱글톤 패턴을 활용하기 좋은 경우

  1. 전역 상태 관리 : 싱글톤은 전역 객체로서 중앙에서 상태를 관리하는데 도움을 줌
  2. 리소스 공유
  3. Lazy Loading : 인스턴스를 처음 사용할 때까지 생성하지 않는 '지연 로딩(Lazy Loading)'을 구현할 수 있습니다.

 

옵저버 패턴

 

주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 일어나면 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴입니다.

 

주체는 객체의 상태 변화를 관찰하는 관찰자이며, 상태 변화가 일어나면 옵저버들에게 변화를 알려줍니다.

 

옵저버 패턴을 활용한 서비스로 '트위터'가 있습니다. 트윗을 날리면 옵저버(팔로워)들에게 트윗이 올라왔다는 신호가 감.

 

옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며 MVC(Model-View-Controller) 패턴에도 사용됩니다.

 

Java

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

interface Subject {
    public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);
}

interface Observer {
    public void update(); 
}

class Topic implements Subject {
    private List<Observer> observers;
    private String message; 

    public Topic() {
        this.observers = new ArrayList<>();
        this.message = "";
    }

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj)) observers.add(obj); 
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj); 
    }

    @Override
    public void notifyObservers() {   
        this.observers.forEach(Observer::update); 
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    } 
    
    public void postMessage(String msg) {
        System.out.println("Message sended to Topic: " + msg);
        this.message = msg; 
        notifyObservers();
    }
}

class TopicSubscriber implements Observer {
    private String name;
    private Subject topic;

    public TopicSubscriber(String name, Subject topic) {
        this.name = name;
        this.topic = topic;
    }

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this); 
        System.out.println(name + ":: got message >> " + msg); 
    } 
}

public class HelloWorld { 
    public static void main(String[] args) {
        Topic topic = new Topic(); 
        Observer a = new TopicSubscriber("a", topic);
        Observer b = new TopicSubscriber("b", topic);
        Observer c = new TopicSubscriber("c", topic);
        topic.register(a);
        topic.register(b);
        topic.register(c); 
   
        topic.postMessage("amumu is op champion!!"); 
    }
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/

 

옵저버 패턴의 장점

  1. 느슨한 결합 : 주체와 옵저버 간 결합이 느슨하게 되어 있어 주체는 옵저버의 존재를 알지만, 구체적인 옵저버에 대해서는 알지 못합니다. 이는 코드의 유연성을 높여주고 유지보수를 쉽게 만듭니다.
  2. 이벤트 기반 프로그래밍 : 옵저버 패턴은 이벤트 기반 프로그래밍에 적합합니다. 이벤트가 발생할 때마다 필요한 동작을 수행할 수 있어 비동기적인 이벤트 처리를 용이하게 합니다.
  3. 모듈화와 재사용성 : 각 옵저버는 독립적으로 작성되어 모듈화가 잘 이루어집니다. 이는 새로운 옵저버를 추가하거나 기존의 옵저버를 변경해 재사용성을 높여줍니다.

옵저버 패턴의 단점

  1. 오버헤드와 성능 문제 : 옵저버 수가 많을 경우, 옵저버에게 알림을 보내는데 오버헤드가 커 성능에 영향을 미칠 수 있습니다.
  2. 복잡성 : 다수의 주체와 옵저버, 복잡한 이벤트 처리 로직이 있는 경우 코드를 이해하고 유지보수하기 어려울 수 있습니다.

옵저버 패턴을 활용하기 좋은 경우

  1. MVC 아키텍처 : 옵저버 패턴은 MVC 아키텍처에서 모델과 뷰 간의 통신에 사용됩니다. 모델의 상태가 변경될 때 뷰에게 알리고, 뷰는 갱신된 상태를 반영해 사용자에게 보여줍니다.
  2. 분산 시스템에서 이벤트 처리 : 분산 시스템에서 여러 컴포넌트 간에 이벤트를 효과적으로 처리하는 데 사용할 수 있습니다. 여러 서비스나 모듈 간의 통신을 위해 옵저버 패턴을 활용 가능합니다.

 

팩토리 패턴

 

객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴.

 

상위 클래스는 인스턴스 생성 방식에 대해 알 필요가 없기 때문에 더 많은 유연성을 갖게 됩니다.

 

class Latte {
    constructor() {
        this.name = "latte"
    }
}

class Espresso {
    constructor() {
        this.name = "Espresso"
    }
}

class LatteFactory {
    static createCoffee() {
        return new Latte()
    }
}

class EspressoFactory {
    static createCoffee() {
        return new Espresso()
    }
}

const factoryList = { LatteFactory, EspressoFactory }

class CoffeeFactory {
    static createCoffee(type) {
        const factory = factoryList[type]
        return factory.createCoffee()
    }
}

const main = () => {
    const coffee = CoffeeFactory.createCoffee("LatteFactory")
    
    console.log(coffee.name) // Latte
}

main()

 

 

팩토리 패턴의 장점

  1. 유연성과 확장성 : 새로운 객체가 추가되거나 기존 객체의 변경이 있을 때, 해당 객체를 생성하는 팩토리 클래스만 수정하면 됩니다. 이는 코드 변경을 최소화하고 시스템의 유연성과 확장성을 높입니다.
  2. 의존성 주입 : 팩토리 패턴은 의존성 주입을 통해 객체 생성에 필요한 의존성을 외부에서 주입할 수 있도록 합니다. 이는 테스트 용이성과 코드의 재사용성을 증가시킵니다.

팩토리 패턴의 단점

  1. 코드의 증가 : 클래스 당 하나의 팩토리 클래스를 만들어야 하므로 클래스 수에 비례해 코드의 양이 증가할 수 있습니다.
  2. 클래스와 팩토리 간의 결합도 : 팩토리 클래스와 생성되는 객체 간의 결합도 증가. 팩토리의 변경이 생성되는 객체에 영향을 줄 수 있다.

팩토리 패턴을 활용하기 좋은 경우

  1. 객체 생성이 복잡하거나 변경이 빈번한 경우 : 객체 생성 로직을 팩토리 클래스에 캡슐화해 간편한 유지보수와 변경을 가능하게 합니다.
  2. 객체 생성 로직을 중앙화하고 표준화하고자 할때 : 여러 곳에서 동일한 타입의 객체를 생성하는데 객체 생성 로직을 중앙에서 표준화하고자 할 때 팩토리 패턴을 사용해 일관성 있는 객체 생성을 유지하는데 도움을 줍니다.

'Computer Science' 카테고리의 다른 글

CS - Web Worker  (0) 2023.10.25
CS - GET, POST 차이  (0) 2023.10.16
CS - 상대경로, 절대경로  (0) 2023.08.23
CS - 라이브러리와 프레임워크 차이  (0) 2023.04.15
CS - MariaDB, RDBMS  (0) 2022.09.28