본문 바로가기

Design Pattern

[Design Pattern] 디자인 패턴

디자인 패턴이란?

디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 관계를 이용하여 해결 할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것을 의미합니다. 

 

디자인 패턴을 배우기전에 의존성, 결합이라는 표현에 대해 짧게 설명해볼게요.

클래스간의 의존성이 높거나 결합이 강하면 특정 클래스를 변경할 때, 다른 클래스에게도 영향을 미칠 수 밖에 없어요.

그러므로 객체지향의 관점에서는 의존성이 낮고, 결합이 느슨한 상태를 지향합니다.

 

자 이제 한번 예시를 볼까요?

#1 싱글톤 패턴 (Singleton Pattern)

개인적으로 디자인 패턴중에서 가장 이해하기 쉬운 패턴입니다!

싱글톤 패턴의 정의는 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴입니다. 

 

public class Dog {
    private static Dog instance = new Dog();
    
    private Dog(){
        // 외부에서 생성자를 호출하지 못하도록 private 을 지정해주어야 합니다.
    }
    
    public static Dog getInstance(){
        return instance;
    }
}

 

보통은 저 Dog 객체가 필요하면 필요한 클래스에서 new를 통해 생성했었죠?

하지만 이 싱글톤 패턴은 그걸 허락해주지 않습니다!

생성자가 private으로 지정되어있어 어느 클래스에서도 new를 통해 생성하지 못할거에요.

대신 Dog 클래스 안에서 미리 만들어놓은 instance(Dog 객체)를 getInstance() 메서드로 빌려주는거죠.

 

싱글톤 패턴의 장점

1. 인스턴스를 생성할 때 드는 비용이 줄어든다.

2. 이미 생성된 인스턴스를 사용하므로 속도도 빠르다.

3. 다른 클래스 간에 데이터 공유가 쉽다.

 

싱글톤 패턴의 단점

1. 하나의 인스턴스로만 객체를 관리하니 동시성문제가 생깁니다.

2. 다른 클래스에서도 사용을 하니 독립된 공간에서의 테스트가 어려워집니다.

3. 클래스간 의존성이 높아진다.

 

단점을 보아하니 클래스간 관계에대한 문제가 많네요.

그렇다는건 클래스간 다양한 관계에 영향을 미치지 않는 DB 연결 모듈같은 곳에서 많이 쓰인다는 이야기겠죠?

 

 

#2 팩토리 패턴 (Factory Pattern)

객체 지향의 기본원칙은 "확장에는 열려 있어야하며, 수정에는 닫혀 있어야한다." 많이 들어본 내용이죠?

여기서 수정에는 닫혀 있어하므로 수정이 일어날 가능성이 큰 부분과 작은 부분을 분리하는 것이 팩토리 패턴의 주된 목적이에요.

보통 상위 클래스에서 중요한 뼈대를 잡고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정해요.

 

abstract class Feed {
    public abstract int getPrice();

    @Override
    public String toString(){
        return "this feed is " + this.getPrice();
    }
}

class FeedFactory {
    public static Feed getFeed(String type, int price){
        if ("Orijen".equals(type)) return new Orijen(price);
        else if ("Fromm".equals(type)) return new Fromm(price);
        else return new Wysong(price);
    }
}

class Wysong extends Feed {
    private int price;

    public Wysong(int price){
        this.price = price;
    }

    @Override
    public int getPrice(){
        return this.price;
    }
}

class Fromm extends Feed {
    private int price;

    public Fromm(int price){
        this.price = price;
    }

    @Override
    public int getPrice(){
        return this.price;
    }
}

class Orijen extends Feed {
    private int price;

    public Orijen(int price){
        this.price = price;
    }

    @Override
    public int getPrice(){
        return this.price;
    }
}

public class Launcher{
    public static void main(String[] args){
        Feed orijen = FeedFactory.getFeed("Orijen", 10000);
        Feed fromm = FeedFactory.getFeed("Fromm", 15000);
        Feed wysong = FeedFactory.getFeed("Wysong", 18000);

        System.out.println("Factory Orijen : " + orijen);
        System.out.println("Factory Orijen : " + fromm);
        System.out.println("Factory Orijen : " + wysong);
    }
}

 

팩토리 패턴의 장점

1. 클래스간 결합이 느슨해진다.

2. 상위 클래스에서는 인스턴스 생성 방식에 대해 알 필요가 없기 때문에 더욱 유연해진다.

3. 코드를 리펙터링 하더라도 한 곳만 고치면 되니 유지 보수성이 증가한다.

 

팩토리 패턴의 단점

1. 클래스가 많아져요.

2. 클래스 계층도 커질 수 있어요.

 

 

 

#3 전략 패턴 (Strategy Pattern)

전략패턴은 객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고 '전략' 이라고 부르는 '캡슐화한 알고리즘'을 바꿔주면서 상호 교체가 가능하게 만드는 패턴입니다.

"우리가 게임을 할 때 공격이나 방어등 행위를 바꾸는 것" 또는 "강아지에게 줄 사료를 살 때 어떤 방식으로 결제를 진행할 지 정해주는 것"들이 좋은 예시라고 말할 수 있습니다.

 

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

interface PaymentStrategy {
    public void pay(int amount);
}

// 결제방식 1
class KAKAOCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public KAKAOCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry){
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.dateOfExpiry = dateOfExpiry;
    }

    @Override
    public void pay(int amount){
        System.out.println(amount + " paid using KAKAOCard");
    }
}

// 결제방식 2
class SAMSUNGCardStrategy implements PaymentStrategy {
    private String cardNumber;
    private String password;

    public SAMSUNGCardStrategy(String cardNumber, String password){
        this.cardNumber = cardNumber;
        this.password = password;
    }

    @Override
    public void pay(int amount){
        System.out.println(amount + " paid using SAMSUNGCard");
    }
}

// 사료
class Feed {
    private String name;
    private int price;
    public Feed(String name, int cost){
        this.name = name;
        this.price = cost;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

// 장바구니
class Cart {
    List<Feed> feeds;

    public Cart(){
        this.feeds = new ArrayList<Feed>();
    }

    public void addFeed(Feed feed){
        this.feeds.add(feed);
    }

    public void removeFeed(Feed feed){
        this.feeds.remove(feed);
    }

    public int calculateTotal(){
        int sum = 0;
        for (Feed feed : feeds) {
            sum += feed.getPrice();
        }
        return sum;
    }

    public void pay(PaymentStrategy paymentStrategy){
        int amount = calculateTotal();
        paymentStrategy.pay(amount);
    }
}

// 최종 실행처
public class Launcher {
    public static void main(String[] args){
        Cart cart = new Cart();

        Feed A = new Feed("Orijen", 10000);
        Feed B = new Feed("Fromm", 15000);

        cart.addFeed(A);
        cart.addFeed(B);

        cart.pay(new KAKAOCardStrategy("mirr", "1234-5678-90", "123", "33/02"));
        cart.pay(new SAMSUNGCardStrategy("1234-5678-90", "qqqq1111"));
    }
}

 

전략 패턴의 장점

1. 확장에 유리한 코드를 작성할 수 있습니다.

2. 예시에서 다른 PaymentStrategy를 바꾸지 않고도 새로운 전략을 추가할 수 있습니다.

3. 런타임시에도 객체 내부에서 사용되는 알고리즘을 바꿀 수 있습니다.

 

전략 패턴의 단점

1. 개발자가 효율적인 전략을 선택하기 위해 전략들을 자세히 알고 있어야합니다.

 

 

#4 옵저버 패턴 (Observer Pattern)

옵저버 패턴은 주체(관찰자)가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴입니다. 주로 이벤트 기반 시스템에 사용하며 MVC 패턴에도 사용됩니다.

많이 사용하는 Youtube를 예로 들자면 구독해놓은 채널에 영상이 올라온다면 여러분에게 알람이 가는 상황이 좋은 예시입니다.

 

코드를 차근차근 따라가 볼게요.

1. topic이라는 우리가 관찰하는 대상을 생성합니다.

2. 관찰자 a, b, c를 생성해요. 이때 생성자를 통해 관찰해야하는 대상을 관찰자에게 전달해줍니다.

3. topic에도 옵저버가 누구누구 있는지 말해줘요.

4. topic의 message가 바뀐다면(postMessage 메서드) 등록 되있는 관찰자들에게 바뀌었다고 알림(notifyObserver 메서드)을줘요.

5. 이를 감지한 관찰자는 그에 맞는 동작을 수행합니다.

 

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


interface Observer {
    public void update();
}


interface Subject {
    public void register(Observer observer);
    public void unregister(Observer observer);
    public void notifyObserver();
    public Object getUpdate(Observer observer);
}


class Topic implements Subject {

    private List<Observer> observerList;
    public String message;

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

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

    @Override
    public void unregister(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObserver() {
        this.observerList.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer observer) {
        return this.message;
    }

    public void postMessage(String message){
        System.out.println("Message sended to Topic: " + message);
        this.message = message;
        notifyObserver();
    }
}


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 message = (String) topic.getUpdate(this);
        System.out.println(name + ": got message >> " + message);
    }
}


public class Launcher {
    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("mirr champion!!");
    }
}

 

옵저버 패턴의 장점

1. 클래스간 결합을 느슨하게 할 수 있습니다.

2. 객체지향의 원칙인 개방 폐쇄 원칙(OCP)를 지킬 수 있습니다.

3. 실시간으로 한 주체의 변경사항을 다른 주체에게 전파할 수 있습니다.

 

옵저버 패턴의 단점

1. 알림이 가는 순서를 보장할 수는 없습니다.

2. 너무 많이 사용하게 되면, 상태 관리가 힘들 수 있습니다.

 

 

 

#5 프록시 패턴 (Proxy Pattern)

Proxy란 대리라는 의미를 가진 단어로 웹서버에서는 무언가의 중개자 역할을 하는것을 의미합니다. 

다음은 Proxy Server가 쓰이는 웹서버 네트워크의 환경입니다.

 

Forward Proxy

 

이와 비슷하게 프록시 패턴은 대상 객체에 접근하기 전 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴입니다.

앞서 언급한 네트워크 환경에선 Proxy Server는 특정 주체의 대리자 역할을 하며 보안, 캐싱, 필터링, 로드밸런싱등을 수행합니다. 여러분이 자주 쓰시는 기능 중 VPN이 대표적인 예시입니다.

프록시 패턴에서 프록시도 마찬가지로 보안, 데이터 필터링, 캐싱, 로깅등의 작업을 수행합니다.

 

WebServer중 대표적인 Nginx, Apache를 Proxy Server로 사용할 수도 있어요. 

전체 Server 앞단에 위치해서 WebApplicationServer를 보안, 필터링, 캐싱, 로드밸런싱등의 작업을 수행하거든요.

그리고 전세계에 분산된 Server를 배치하기 위한 CDN도 Proxy의 한 일종이라고 볼 수 있어요!

 

프록시 패턴의 장점

1. 앞서 이야기 했던 보안, 데이터필터링, 캐싱, 로깅등의 작업을 수행할 수 있어요.

2. 실제 객체의 public, protected 메소드를 숨기고 인터페이스를 통해 노출 시킬 수 있어요.

3. 로컬에 있지 않고 떨어져있는 객체를 사용할 수 있어요.

 

프록시 패턴의 단점

1. 빈번한 객체 생성(요청)이 필요한 경우 성능이 저하돼요.

2. 로직이 난해해져 가독성이 떨어져요.

 

 

 

#6 이터레이터 패턴 (Iterator Pattern)

이터레이터 패턴은 Iterator를 사용해서 Collection 요소들에 접근하는 디자인 패턴입니다.

Collection이란 다수의 데이터를 쉽고 효과적으로 관리할 수 있는 표준화된 방법을 제공하는 클래스의 집합이에요.

대표적으로 List, Set, Map, Stack, Queue등의 인터페이스들이 존재해요.

 

즉, 이터레이터 패턴은 Iterator를 통해 여러가지 자료형의 구조들을 하나의 인터페이스로 순회시켜주는 기능이에요.

interface Aggregator {
    Iterator iterator();
}


interface Iterator {
    boolean next();
    Object current();
}


class Feed {
    private String name;
    private int cost;

    public Feed(String name, int cost) {
        this.name = name;
        this.cost = cost;
    }

    @Override
    public String toString(){
        return "(" + name + ", " + cost + ")";
    }
}


class Array implements Aggregator {
    private Feed[] feeds;

    public Array(Feed[] feeds) {
        this.feeds = feeds;
    }

    public Feed getFeed(int index){
        return feeds[index];
    }

    public int getCount(){
        return feeds.length;
    }

    @Override
    public Iterator iterator() {
        return new ArrayIterator(this);
    }
}


class ArrayIterator implements Iterator {
    private Array array;
    private int index;

    public ArrayIterator(Array array) {
        this.array = array;
        this.index = -1;
    }

    @Override
    public boolean next() {
        index++;
        return index < array.getCount();
    }

    @Override
    public Object current() {
        return array.getFeed(index);
    }
}

public class Launcher {
    public static void main(String[] args) {
        Feed[] feeds = {
                new Feed("Orijen", 10000),
                new Feed("Fromm", 15000),
                new Feed("Wysong", 18000)
        };

        Array array = new Array(feeds);
        Iterator it = array.iterator();

        while (it.next()){
            Feed feed = (Feed) it.current();
            System.out.println(feed);
        }
    }
}

 

해당 코드에서 feeds의 자료구조 타입을 알지 못해도 Iterator 패턴을 통해 유연하게 접근할 수 있는걸 알 수 있어요.

Iterator 패턴의 핵심은 다양한 형태의 자료구조를 참조할 수 있는 표준화된 공통 Api를 제공 할 수 있다는 것이에요.

 

이터레이터 패턴의 장점

1. Aggregator의 형태를 몰라도 표준화된 api를 통해 접근할 수 있어요.

2. 순회 코드의 중복을 줄일 수 있어요.

3. 사전에 데이터 구조가 알려지지 않아도 동작을 구현할 수 있어요.

 

이터레이터 패턴의 단점

1. 간단한 자료구조인 경우, 해당 패턴은 과도할 수 있어요.

2. 특정 위치의 요소에 바로 접근해야할 때, 해당 패턴은 비효율적일 수 있어요.

 

 

#7 노출 모듈 패턴 (Revealing module Pattern)

노출 모듈 패턴은 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴을 말해요.

자바스크립트는 public, private 같은 접근 제어자가 존재하지 않고 전역 범위에서 스크립트가 실행돼요.

그렇기에 노출 모듈 패턴을 통해 public, private 접근 제어자를 구현하기도 합니다.

 

노출 모듈 패턴의 장점

1. 문법이 더욱 일관성 있어집니다.

2. 명시적으로 접근제어자를 사용하여 가독성이 좋아집니다.

3. 전역변수에 대한 가독성도 증가시켜줍니다.

 

노출 모듈 패턴의 단점

1. private 메소드에 대해 함수 확장하는데 어려움이 있습니다.

 

 

#8 MVC 패턴 (MVC Pattern)

MVC 패턴은 모델(Model), 뷰(View),  컨트롤러(Controller)로 이루어진 디자인 패턴입니다.

 

MVC 패턴

 

Model : 애플리케이션에서 사용되는 데이터가 무엇인지를 정의합니다. .

 

View : 사용자에게 보여지는 inputbox, checkbox, textarea등 사용자 인터페이스 요소를 나타내는 UI 부분입니다.

 

Controller : 하나 이상의 Model과 하나 이상의 View를 잇는 다리 역할을 하며 Model과 뷰의 생명주기를 관리하고, 사용자의 요청(Request)를 받고 처리하는 부분입니다. 

추가로 Controller는 여러개의 View를 선택할 수 있는 1 : N의 관계입니다.

 

일반적인 동작

1. 사용자의 Request들은 Controller에 들어오게 됩니다.

2. Controller는 사용자의 Request를 확인하고, Model을 업데이트합니다.

3. Controller는 Model을 나타내줄 View를 선택합니다.

4. View는 Model을 이용하여 화면을 나타냅니다.

 

MVC 패턴의 장점

1. 재사용성 확장성이 용이해집니다.

2. 정말 단순한 패턴이고 따라서 보편적으로 많이 사용되는 디자인 패턴입니다.

 

MVC 패턴의 단점

1. View와 Model 사이의 의존성이 높습니다. 이는 어플리케이션이 커질수록 복잡해지고 유지보수가 어렵습니다.

 

 

#9 MVP 패턴 (MVP Pattern)

MVP 패턴은 MVC 패턴으로부터 파생되었으며 MVC에서 C에 해당하는 Controller가 Presenter로 교체된 패턴입니다.

 

MVP 패턴

 

Model : 애플리케이션에서 사용되는 데이터인 데이터베이스, 상수, 변수 등을 처리하는 부분입니다.

 

View : 사용자에게 보여지는 inputbox, checkbox, textarea등 사용자 인터페이스 요소를 나타내는 UI 부분입니다.

 

Presenter : View에서 요청한 정보로 Model을 가공하여 View에 전달해 주는 부분입니다. View와 Model 사이의 매게체 역할을 합니다.

추가로 Presenter는 하나의 View를 선택할 수 있는 1 : 1의 관계입니다.

 

일반적인 동작

1. 사용자의 Request들은 View에 들어오게 됩니다.

2. View는 사용자의 Request를 확인하고, Presenter에 데이터를 요청합니다.

3. Presenter는 Model에게 데이터를 요청합니다.

4. Model은 Presenter에게 요청받은 데이터를 응답합니다.

5. Presenter은 View에게 데이터를 응답합니다.

6. View는 Presenter가 응답한 데이터를 이용하여 화면을 나타냅니다.

 

MVP 패턴의 장점

1. View와 Model의 의존성이 없습니다.

 

MVP 패턴의 단점

1. View와 Presenter 사이의 의존성이 높아졌습니다.

 

 

#10 MVVM 패턴 (MVVM Pattern)

MVVM 패턴은 Model + View + View Model을 합친 용어입니다.

 

MVVM 패턴

 

Model : 애플리케이션에서 사용되는 데이터인 데이터베이스, 상수, 변수 등을 처리하는 부분입니다.

 

View : 사용자에게 보여지는 inputbox, checkbox, textarea등 사용자 인터페이스 요소를 나타내는 UI 부분입니다.

 

ViewModel : View를 표현하기 위해 만든 ViewModel입니다. View를 나타내 주기 위한 Model이자 데이터 처리를 하는 부분입니다.

추가로 ViewModel은 다수의 View를 선택할 수 있는 1 : N의 관계입니다.

 

일반적인 동작

1. 사용자의 Request들은 View에 들어오게 됩니다.

2. View는 사용자의 Request를 확인하고, Command 패턴으로 ViewModel에 데이터를 전달합니다.

3. ViewModel은 Model에게 데이터를 요청합니다.

4. Model은 ViewModel에게 요청받은 데이터를 응답합니다.

5. ViewModel은 응답 받은 데이터를 가공하여 저장합니다.

6. View는 ViewModel과 Data Binding하여 화면을 나타냅니다.

 

MVVM 패턴의 장점

1. View와 Model 사이의 의존성이 없습니다.

2. 또한 Command 패턴과 Data Binding을 사용하여 View와 ViewModel 사이의 의존성도 없습니다.

3. 각각은 독립적이기 때문에 모듈화하여 개발할 수 있습니다.

 

MVVM 패턴의 단점

1. View Model의 설계가 쉽지 않습니다.

 


 

● 참고자료 : 면접을 위한 CS 전공지식 노트 (주홍철 | 길벗출판사)

 

 

'Design Pattern' 카테고리의 다른 글

[Design Pattern] Proxy Pattern, Decorator Pattern  (0) 2023.06.01
[Design Pattern] Template Method Pattern  (0) 2023.05.31