본문 바로가기
CS 기본 지식

개발 - 디자인 패턴

by khds 2023. 5. 23.

 

이 글은 '면접을 위한 CS 전공지식 노트 - 주홍철'을 읽고 부하고 정리한 내용을 작성한 글이다.  

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

여기서 설명할 디자인 패턴들은 아래와 같다.

 

  1. 싱들톤 패턴
  2. 팩토리 패턴
  3. 전략 패턴
  4. 옵저버 패턴
  5. 프록시 패턴
  6. 이터레이터 패턴
  7. 노출모듈 패턴
  8. MVC 패턴

 

1. 싱글톤 패턴(singleton pattern)

싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다. 클래스는 큰 틀의 형태로 이 틀을 기반으로 알맹이를 채워 넣는 실체가 인스턴스이다. 인스턴스는 여러 가지 만들 수 있지만 싱글톤 패턴은 오직 하나의 인스턴스만을 만들어 이를 기반으로 로직을 만드는데 쓰인다. 보통 데이터베이스 연결 모듈에 많이 사용한다.

싱글톤 패턴을 적용하여 얻을 수 있는 장점은 메모리 측면으로 봤을 때 최초 한번의 new 연산자로 고정된 메모리 영역을 사용하기에 메모리 낭비를 방지할 수 있다. 그리고 생성된 인스턴스를 계속해서 활용하니 속도도 더 빠르다고 할 수 있다. 그리고 데이터 공유가 쉬운데 하나의 인스턴스를 전역적으로 모듈들이 공유하며 사용하므로 접근이 쉽다. 하지만 이에 따른 동시접근 문제도 해결해야 하고 의존성이 높아지는 문제가 있다.

아래는 전형적인 예시 코드이다.

class Singleton {
    //최초의 인스턴스 선언
    private static final Singleton INSTANCE = new Singleton();

    //인스턴스 반환
    public static Singleton getInstance() {
        return INSTANCE;
    }
    
    // 외부에서 new를 사용하지 못하도록 생성자를 private로 선언
    private Singleton() {}
}

 

public class Test {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println("singleton1: " + singleton1);
        System.out.println("singleton1: " + singleton2);
    }
}

 

 

보통 class 인스턴스를 만들 때 new를 통해 생성했었지만 싱글톤 패턴은 프로그램 시작시점에 인스턴스가 알아서 하나만 만들어진다. 그리고 생성자를 외부에서 사용하지 못하도록 하기위해 private 선언을 한다.

싱글톤 패턴은 TDD를 할 때도 단점이 된다. TDD는 단위테스트를 주로 하는데, 단위 테스트는 독립적이어야 하며 테스트와 테스트는 서로 영향을 주면 안된다. 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스만을 다루므로 독립적이라는 말에 모순이 된다. 이러한 문제는 의존성 주입(DI, Dependency Injection)을 통해 해결이 된다.

의존성은 종속성이라고도 하며 A 가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 한다는 것을 의미한다. 

출처: 이 글은 면접을 위한 CS 전공지식 노트 - 주홍철

 

위의 그림처럼 메인 모듈이 직접 다른 하위 모듈에 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식이다. 이를 통해 메인 모듈은 하위 모듈에 대한 의존성이 떨어지게 된다. 

이러한 의존성 주입은 모듈들을 쉽게 교체할 수 있는 구조이기에 테스트하기 쉽고 마이그레이션하기 수월하다. 또한 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션  의존성 향이 일관되고 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 좀 더 명확해진다. 하지만 모듈이 더욱 분리되어 클래스 수가 늘어나 복잡성이 증가될 수 있다. 

나는 이 글을 작성하면서 의존성 주입이라는 말을 듣고 스프링에서의 Bean을 의존성 주입하는 것이 떠올랐다. 혹시나해서 봤는데 Bean 또한 싱글톤 패턴을 적용한 것이었다.

우리는 java로 백엔드 개발을 할 때 주로 사용하는 스프링 프레임워크도 싱글톤 패턴을 활용한다. 스프링 컨테이너는 @Component, @Service, @Bean 등으로 등록한 Bean 객체를 다루는데 이러한 Bean 객체들은 싱글톤 패턴으로 생성된 것들이다. 

 

2. 팩토리 패턴(factory pattern)

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

아래의 예제코드를 봐보자.

//Car.java
public interface Car {
    void print();
}

//Hyundai.java
public class Hyundai implement Car {
    @Override
    public void print() {
        System.out.println("My type is Hyundai");
    }	
}

//Kia.java
public class Kia implement Car {
    @Override
    public void print() {
        System.out.println("My type is Kia");
    }	
}
   
public class CarFactory {
    public static Car getCar(String type) {
        if (type.equealsIgnoreCase("Hyundai")){
            return new Hyundai();
        }
        if (type.equealsIgnoreCase("Kia")){
            return new Kia();
        } 
        return null;
    }
}


//test
public class test {
    public static void main(String[] args) {
        Car car1 = CarFactory.getCar("Hyundai");
        Car car2 = CarFactory.getCar("Kia");
        car1.print();
        car2.print();
    }
}

 

결과는 어떻게 되는가? 

아래와 같다.

My type is Hyundai
My type is Kia

 

CarFactory 객체에서 type에 따라 어떤 객체를 줄지가 결정된다. 

즉, Car 객체라는 뼈대를 가지고 Hyundai와 Kia라는 내용물들이 만들어졌고, Factory를 통해 어느 객체를 리턴할지를 변경할 수 있도록 한다. 

뼈대를 가지고 여러 종류의 내용물이 만들어지고 공장에서 요청에 따라 내용물들을 선별적으로 뽑아낸다고 생각하면 된다.

위 패턴은 상위 클래스와 하위클래스가 분리되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 가진다. 이는 하위클래스를 추가로 생성이 가능하다는 점에서 확장성이 용이하다는 것을 알 수 있다. 또한 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리펙토링 하더라도 한 곳만 고칠 수 있게 되니 유지 보수성이 증가한다.

 

3. 전략 패턴(strategy pattern)

전략 패턴은 정책 패턴(policy pattern)이라고도 하며 객체의 행위를 바꾸고 싶은 경우 '직접' 수정하지 않고 전략이라고 부르는 '캡슐화한 알고리즘'을 콘텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴이다.

아래의 예시를 봐보자.

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

class KakaoPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
       System.out.println(amount + " paid using KakaoPay");
    }
}

class NaverPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
       System.out.println(amount + " paid using NaverPay");
    }
}

class Item {
    private String name;
    private int price;
    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    
    public String getName() {
        return name;
    }
    
    public int getPrice() {
        return price;
    }
}

class ShoppingCart() {
  List<Item> items;
  
  public ShoppingCart() {
      this.items = new ArrayList<Item>();
  }
  
  public void addItem(Item item) {
      this.items.add(item);
  }
  
  public void removeItem(Item item) {
      this.items.remove(item);
  }
  
  public int caculateTotal() {
      int sum = 0;
      
     for (Item item : items) {
         sum += item.getPrice();
     }
     return sum;
  }
  
  public void pay(PaymentStrategy paymentMethod) {
      int amount = calculateTotal();
      paymentMethod.pay(amount);
  }
}




//test
public class Test {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        Item A = new Item("apple", 1000);
        Item B = new Item("banana", 2000);
        
        cart.addItem(A);
        cart.addItem(B);
        
        cart.pay(new KakaoPayStrategy());
        cart.pay(new NaverPayStrategy());
    }
}

 

결과는 어떻게 나올까?

아래와 같다.

1000 paid using KakaoPay
2000 paid using NaverPay

 

즉, 동일한 기능을 실행하는데 어떠한 방식, 어떠한 알고리즘으로 실행할지를 결정할 수 있게 해주는 패턴이다.

위 패턴은 어떤 방식이 추가된다 하더라도 전체가 아닌 그 방식만을 추가하면 되니까 확장성에 좋고 유지보수에 용이하다는 장점이 있다. 

 

여기서 문득 드는 생각이 있을 수 있다. 전략 패턴과 팩토리 패턴이 거의 유사하지 않은가?

나도 공부를 하면서 이 부분에서 궁금했었다. 

팩토리 패턴이나 전략 패턴이나 하나의 뼈대가 있고 내용물이나 전략이 여러 개이고 사용자에 요청에 따라 어떤 내용물 혹은 어떤 방식을 사용하는 것이 매우 유사하다. 

두 패턴의 차이는 무엇인가, 어떠한 상황에 어떤 패턴을 사용하는 것이 더 유익한가? 궁금해서 구글링을 해봤더니 이미 여러 글에서 정리를 해놓은 상태였다.

아래의 주소를 참고하길 바란다.

https://medium.com/@ehd8266/strategy-vs-factory-design-patterns-in-java-4913fa3f760f

 

Strategy vs Factory Design Patterns in Java

개발자들의 대부분은 Strategy 디자인 패턴뿐만 아니라 Factory 디자인 패턴을 사용합니다. 그러나 개발자들은 두 디자인 패턴의 차이점에 대해 설명하기 어려워하거나, 프로젝트를 시작할 때 어떤

medium.com

 

https://dzone.com/articles/strategy-vs-factory-design-pattern-in-java

 

Strategy vs. Factory Design Patterns in Java - DZone

In this tutorial, we demonstrate how to use and the overall difference between strategy and factory design patterns in Java using helpful examples and code.

dzone.com

 

위 글에서 핵심적으로 언급한 부분은 아래와 같다. 

Factory 디자인 패턴은 생성 디자인 패턴으로, 객체를 만드는 가장 좋은 방법 중 하나이다. 해당 패턴은 Factory 메서드를 사용하여 객체의 정확한 클래스를 정의하지 않고 객체 생성 문제를 처리한다.

전략패턴은 어떤 특정 알고리즘을 런타임시에 변경을 하고 싶을 때 Context에 전략 사용을 위한 인터페이스 Reference를 저장하고 동작을 요청한다.

중요한 점은 Reference를 저장할 때 특정 전략에 대한 객체에 대해 new를 통해 객체를 생성하고, context에 저장된 객체정보를 바탕으로 알고리즘을 수행한다.

4. 옵저버 패턴(observer pattern)

옵저버 패턴은 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다.

여기서 주체란 객체의 상태 변화를 보고 있는 관찰자이며, 옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들을 의미한다.

옵저버 패턴을 활용하는 서비스로는 트위터가 있다. 

주체를 팔로우 한 사람들(옵저버)들에게 새로운 트윗을 알려주는 기능이다. 

 

5. 프록시 패턴(proxy pattern)

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

 

프록시 패턴하면 떠오르는 것은 프록시 서버라는 것이 있다.

프록시 서버는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.

프록시 서버를 사용하는 것 중 대표적으로 Nginx가 있다.

nginx는 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버이며, 주로 Node.js 서버 앞단의 프록시 서버로 활용된다. 

Node.js의 창시자 라이언 달(Ryan Dahl)은 다음과 같이 말했다.

"Node.js의 버퍼 오버플로우 취약점을 예방하기 위해서는 nginx를 프록시 서버로 앞단에 놓고 Node.js를 뒤쪽에 놓는 것이 좋다"

즉 사용자로 하여금 서버에 직접 접근하지 않고 프록시 서버를 통해 간접적으로 접근하라는 것이다.

 

출처: 면접을 위한 CS 전공지식노트 - 주홍철

 

위의 사항 말고 CORS문제에서도 프록시 서버가 사용된다. 자세한 사항은 아래의 글을 확인하자.

https://khdscor.tistory.com/64

 

백엔드, 프론트 서버를 연결할 때: 'CORS' 문제 및 해결법

나는 백엔드를 spring 프레임워크로, 프론트를 react 프레임워크로 개발을 할 때 가장 처음 직면한 문제가 있었다. 그것은 바로 'CORS' 문제였다. 아마 대부분의 개발자들이 프론트 담당이든 백엔드

khdscor.tistory.com

 

 

6. 이터에이터 패턴(iterator pattern)

이터레이터 패턴은 이터레이터를 사용하여 컬렉션의 요소들에 접근하는 디자인 페턴이다. 이를 통해 순회할 수 있는 여러 가지 자료형의 구조와는 상관없이 이터레이터라는 하나의 인터페이스로 순회가 가능하다.

 

7. 노출모듈 패턴(revealing module pattern)

노출모듈 패턴은 즉시 실행 함수를 통해 priavate, public 같은 접근 제어자를 만드는 패턴을 말한다. private나 public 같은 접근 제어자가 존재하지 않고 전역 범위에서 스크립트가 실행된다. 그렇기에 노출모듈 패턴을 통해 private와 public 접근 제어자를 구현하기도 한다.

 

8. MVC 패턴

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

 

뷰는 사용자 인터페이스 요소를 나타낸다. 즉 사용자가 실제로 보는 화면이다.

모델은 애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 뜻한다.

컨트롤러는 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 하며 이벤트 등 메인 로직을 담당한다.

 

MVC하면 생각하는 것이 Spring MVC가 있다. Spring는 MVC패턴을 사용하며 자세한 내용은 아래를 참고하길 바란다.

https://khdscor.tistory.com/41

 

스프링 MVC에 대한 간단 정리

스프링 프레임워크로 웹 개발을 하면서 많이 본 용어는 스프링 MVC라는 용어이다. 스프링 프로젝트를 생성할 때 spring web라는 라이브러리를 추가했었는데 spring mvc를 사용할 수 있도록 하는 것이

khdscor.tistory.com

 

참고사항으로 MVC 패턴에서 파생된 MVP, MVVM 패턴도 있으며 이들은 C가 P(presenter), VM(view model)로 교체된 패턴이다.

 

참고 

이 글은 면접을 위한 CS 전공지식 노트 - 주홍철

https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/

 

싱글톤(Singleton) 패턴이란?

이번 글에서는 디자인 패턴의 종류 중 하나인 싱글톤 패턴에 대해 알아보자. 싱글톤 패턴이 무엇인지, 패턴 구현 시 주의할 점은 무엇인지에 대해 알아보는 것만으로도 많은 도움이 될 것이라

tecoble.techcourse.co.kr

 

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%8B%B1%EA%B8%80%ED%86%A4Singleton-%ED%8C%A8%ED%84%B4-%EA%BC%BC%EA%BC%BC%ED%95%98%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

💠 싱글톤(Singleton) 패턴 - 꼼꼼하게 알아보자

Singleton Pattern 싱글톤 패턴은 디자인 패턴들 중에서 가장 개념적으로 간단한 패턴이다. 하지만 간단한 만큼 이 패턴에 대해 코드만 던져주고 끝내버리는 경우가 있어, 어디에 쓰이는지 어떠한 문

inpa.tistory.com

 

https://devjem.tistory.com/11

 

스프링 빈의 싱글톤 관리

싱글톤 패턴 싱글톤 패턴은 애플리케이션이 시작 될 때 static을 통해 인스턴스를 메모리에 딱 하나 할당하고, 뒤의 호출 시 마다 해당 인스턴스를 반환해주는 디자인 패턴이다. 생성자를 private으

devjem.tistory.com

 

https://niceman.tistory.com/143

 

Java(자바) 디자인패턴 - 팩토리(Factory Method) 패턴 설명 및 예제소스

Java 디자인패턴 - 팩토리 메소드 패턴 이번 시간에는 자바 디자인 패턴 중 팩토리 메소드 패턴(Factory Method Pattern)에 대해서 쉬운 예제와 함께 설명 드리려 합니다. 팩토리 메소드 패턴은 요약해서

niceman.tistory.com

 

https://jackjeong.tistory.com/entry/Java-Strategy-Pattern%EC%A0%84%EB%9E%B5%ED%8C%A8%ED%84%B4feat-Interface

 

[Java] Strategy Pattern(전략패턴)[feat. Interface]

안녕하세요~ 잭코딩입니다! 이번에는 인터페이스의 활용에 대해 글을 써보려고 합니다! 요즘 글쓴이는 우아한테크캠프 Pro라는 교육과정을 듣고 있습니다 이번 미션에서 Strategy Pattern을 적용해

jackjeong.tistory.com

 

https://medium.com/@ehd8266/strategy-vs-factory-design-patterns-in-java-4913fa3f760f

 

Strategy vs Factory Design Patterns in Java

개발자들의 대부분은 Strategy 디자인 패턴뿐만 아니라 Factory 디자인 패턴을 사용합니다. 그러나 개발자들은 두 디자인 패턴의 차이점에 대해 설명하기 어려워하거나, 프로젝트를 시작할 때 어떤

medium.com

 

https://recordsoflife.tistory.com/1140

 

MVC와 MVP 패턴의 차이점

1. 개요 이 사용방법(예제)에서는 Model View Controller 및 Model View Presenter 패턴 에 대해 알아봅니다 . 또한 그들 사이의 차이점에 대해서도 논의할 것입니다. 2. 디자인 패턴과 아키텍처 패턴 2.1. 건축

recordsoflife.tistory.com