디자인 패턴 정리 1탄 -생성 패턴(싱글톤, 빌더, 팩토리)

Jiwon Jung·2023년 11월 3일
0
post-thumbnail

디자인 패턴은 소프트웨어 과정에서 자주 발생하는 문제들에 대한 전형적인 해결책입니다.

왜 패턴을 배워야 할까요?

사실 패턴에 대해 아무것도 알지 못해도 수년 동안 프로그래머로 일할 수 있습니다. 그리고 실제로 많은 개발자분들이 패턴에 대한 지식 없이 업무를 수행합니다. (저도 잘 모르고 일했습니다 ㅜ 흑반성....!!)

자신도 모르는 사이에 일부 패턴을 이미 구현하고 있을 수 있습니다. 흔히 웹개발 할 때 많은 프로젝트에 개발시 사용하는 Spring Framework도 수많은 디자인 패턴으로 만들어져 있기에 이미 디자인 패턴이 적용된 프레임워크를 개발자들은 사용중인 것이죠. 그렇다면 왜 패턴을 배워야 할까요?

  1. 디자인 패턴은 소프트웨어 디자인의 일반적인 문제들에 대해 시도되고 검증된 해결책 모음입니다. "바퀴를 다시 발명하지 마라"는 말이 있습니다. 이미 만들어져서 잘 되는 것을 처음부터 다시 만들 필요가 없다는 것입니다. 따라서 패턴을 알고 있는 개발자는 객체 지향 디자인의 원칙들을 사용해 많은 종류의 문제를 해결하는 방법들을 배울 수 있습니다.
  1. 팀원들이 더 효율적으로 의사소통하도록 공통 언어가 됩니다. 예를 들어 팀원들이 디자인 패턴을 알고 있다면 제가 누군가에게 업무 처리 시 '그 문제를 위해서는 그냥 싱글턴 사용하면 돼요.'라고 말하면 모두가 무엇을 뜻했는지 알 수 있을 것입니다.

디자인 패턴의 분류

크게 3가지 그룹으로 분류할 수 있습니다.

  1. 생성 패턴 (Creational Pattern)
    • 객체 생성에 관련된 패턴
      특정 객체가 생성/변경 되어도 프로그램 구조에 영향을 크게 받지 않도록 유연성 제공
  2. 구조 패턴 (Structural Pattern)
    • 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
  3. 행동 패턴 (Behavioral Pattern)
    • 객체나 클래스가 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴입니다.

이번 포스팅은 1탄으로 생성 패턴에 대해 알아볼건데요.
그 중에서도 자주 쓰이는 패턴인 싱글톤, 팩토리 메소드, 빌더패턴 3가지에 대해 이번 포스팅에서 다루겠습니다.

1. 싱글톤(Singleton) 패턴

  • 인스턴스가 딱 1개만 존재하게 하여 모든 곳에서 이 인스턴를 참조할 수 있도록 하는 패턴. 즉 객체는하나만 존재하게 하는 패턴

스프링 기반의 웹개발 하다 보면 가장 많이 접하는 패턴이 싱글톤입니다. @Bean으로 관리되는 객체는 디폴트가 싱글톤 객체로 생성됩니다.

싱글톤 구현

public class Singleton {
    // 단 1개만 존재해야 하는 객체의 인스턴스로 static 으로 선언
    private static Singleton instance;

    // private 생성자로 외부에서 객체 생성을 막아야 한다.
    private Singleton() {
    }

    // 외부에서는 getInstance() 로 instance 를 반환
    public static Singleton getInstance() {
        // instance 가 null 일 때만 생성
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

싱글톤 패턴의 기본적인 구현 방법은 다음과 같습니다.

먼저 private static으로 Singleton객체의 instance를 선언하고 getInstance()메서드가 처음 실행 될 때만 하나의 instance가 생성되고 그 후에는 이미 생성되어진 instance를 return하는 방식으로 진행이 됩니다.

여기서 핵심은 private로 된 기본 생성자입니다. 생성자를 private로 생성을 하며 외부에서 새로운 객체의 생성을 막아줘야 합니다.

// 같은 instance인지 Test
public class Application {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        System.out.println(singleton1);
        System.out.println(singleton2);
    }
}
        /** Output
         * vendingmachine.Singleton@15db9742
         * vendingmachine.Singleton@15db9742
         **/

싱글톤 객체를 생성하는 위의 코드를 실행해보면 두 객체가 하나의 인스턴스를 사용하여 같은 주소 값을 출력하는 것을 확인하실 수 있습니다.

싱글톤 장점

  • 최초 한번만 생성하기 때문에 메모리 측면에서 이점이 있음
  • 이미 생성된 객체를 가져와서 사용하기 때문에 속도도 빠름

싱글톤 단점

  • 하나의 객체를 공유하기 때문에 동시성 문제가 생길 수 있다.
  • 테스트하기 어렵다.

2. 빌더(Builder) 패턴

  • 객체를 단계별로 생성할 수 있도록 함

빌더 장점

  • 필요한 데이터만 설정 가능
  • 가독성 높임
    - 생성자 패턴에서는 값들(ex. "dog", 12, "male"이 무얼 의미하는지 파악 힘들고, 갯수가 늘수록 더욱 코드 읽기 힘들어짐. 그러나 빌더 패턴을 적용하면 직관적으로 어떤 데이터에 어떤 값이 설정되는지 쉽게 파악 가능
  • 유연성 확보

빌더 단점

  • Lombok 같은 라이브러리 사용하지 않으면 빌더 만드는 코드 매번 생성하기 복잡

3. 팩토리 메소드 (Factory Methods)

  • 객체를 생성하는 인터페이스를 미리 정의하지만, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 결정하는 패턴
  • 여러개의 서브 클래스를 가진 슈퍼 클래스가 있을때, 들어오는 인자에 따라서 하나의 자식클래스의 인스턴스를 반환해주는 방식

언제 활용하면 좋을까?

  • 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때
  • 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 때
public interface Figure {
	void draw();
}
public class Circle implements Figure {
    @Override
    public void draw() {
    	System.out.println("Circle의 draw 메소드");
    }
    
}
public class Rectangle implements Figure {
    @Override
    public void draw() {
    	System.out.println("Rectangle의 draw 메소드");
    }
    
}
public class Square implements Figure {

    @Override
    public void draw() {
    	System.out.println("Square의 draw 메소드");
    }
    
}
public class FigureFactory {
    public Figure getFigure(String figureType) {
    	if(figureType == null) {
            return null;
        }
        if(figureType.equalsIgnoreCase("CIRCLE") {
            return new Circle();
        } else if (figureType.equalsIgnoreCase("RECTANGLE") {
            return new Rectangle();
        } else if (figureType.equalsIgnoreCase("SQUARE") {
            return new Square();
        }
        
        return null;
    }
    
}
public class FactoryPattern {
    public static void main(String[] args) {
        FigureFactory figureFactory = new FigureFactory();
        
        Figure fig1 = figureFactory.getFigure("CIRCLE");
        
        // Circle의 draw 메소드 호출
        fig1.draw();
        
        Figure fig2= figureFactory.getFigure("RECTANGLE");
        
        // Rectangle의 draw 메소드 호출
        fig2.draw();
        
        Figure fig3 = figureFactory.getFigure("SQUARE");
        
        // Square의 draw 메소드 호출
        fig3.draw();
    }
}

위의 코드에서 본 것 처럼 팩토리 메서드는 객체를 생성하는 인터페이스는 미리 정의하되, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 내리는 패턴 입니다.

팩토피 메서드에서는 클래스의 인스턴스를 만드는 시점을 서브 클래스로 미룹니다.

팩토리 패턴 장점

  • 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 합니다.
  • 클라이언트와 구현 객체들 사이에 추상화를 제공 합니다.

팩토리 패턴의 단점

  • 새로 생성할 객체가 늘어날 때마다, Factory 클래스에 추가해야 되기 때문에 클래스가 많아집니다.
profile
게을러지고 싶어 부지런한 개발자

0개의 댓글