SMALL

빌더 패턴은 추상 팩토리를 확장하여 크고 복잡한 객체를 생성할 수 있다.

 

빌더 패턴은 복잡한 구조의 복합 객체를 생성하는 로직을 별도로 분리하여 객체 생성을 처리

복합 객체의 생성 과정을 단계별로 분리함으로써 복합 객체의 생성을 일반화할 수 있습니다.

 

복합, 단일 객체

그럼 여기서 말하는 복합 객체가 무엇인지 알아보자

복합 객체와 대비 되는 단어는 단일 객체이다.

객체 지향에서 객체의 종류는 크게 단일 객체와 복합 객체 2가지가 있다.

 

단일 객체 : 하나의 클래스로 생성된 객체

( 객체는 데이터와 행동을 가지며 때로는 객체를 확장하기 위해 상속 구조를 적용 )

팩토리, 팩토리 메서드, 추상 팩토리 모두 단일 객체를 사용 

 

복합 객체 : 하나의 객체가 다른 객체를 포함하는 관계 구조 ( 구조적 의존 관계를 통해 객체를 확장 ) 

복합 객체는 내부적으로 다른 클래스의 객체를 포함

복합 객체 사용을 중요시하는 것이 최신 객체지향 트렌드

이전에 학습한 팩토리, 팩토리 메서드, 추상 팩토리 패턴으로는 복합 객체를 생성할 수 없다

 

그렇다면 이러한 복합 객체라는 단어가 생긴 이유는 무엇일까

그 이유에 대해서 알아보자

 

복합 객체가 등장한 이유

생성 패턴의 주요 목적은 객체의 생성 과정을 한 곳에 집중화하는 것이다.

패턴을 사용하여 객체를 생성 관리하는 이유는 인스턴스화 과정에서 발생하는 강력한 의존 관계를 해소하기 위해서이다.

 

전형적인 클래스 확장 방식 -> 상속

강력한 상하 결합 관계, 불필요한 모든 행위까지 포함된다는 단점 존재

 

이러한 단점을 개선하기 위해 의존성 주입을 사용

의존성 주입을 통해 복합 객체를 생성해서 사용 

복합 객체 : 하나의 객체가 다른 객체를 포함하는 관계 구조 ( 구조적 의존 관계를 통해 객체를 확장 ) 

장점 : 객체가 생성된 후에도 다른 객체와 관계를 설정해 동적 확장 가능

 

많은 디자인 패턴의 원리와 목적은 -> 상속 결합을 배제하고 의존 관계의 복합 객체로 변경하여 처리하는 것

 

복합 객체의 생성의 어려움으로 빌더 패턴 등장

복합 객체에는 구조에 맞게 객체를 생성하고 관계를 설정하는 로직이 필요합니다.

이러한 생성 로직은 일반적으로 클라이언트 코드 안에 작성됩니다.

복합 객체의 생성 로직을 일반 코드로 작성하면 객체 생성 과정을 효율적으로 관리하기 어렵습니다.

-> 이러한 이유로 빌더 패턴은 복합 객체 생성 과정을 별도의 독립된 클래스로 관리

 

빌더 패턴은 복합 객체의 생성 로직을 별도 클래스로 분리 

 

 

 

빌더 패턴, 추상 팩토리 패턴 비교

빌더 패턴은 추상 팩토리 패턴을 확장하고, 복잡한 단계를 가진 복합 객체를 생성할 수 있다.

  빌더 추상 팩토리
설계 목적 생성 단계를 중점으로 설계 유사한 객체의 생성 과정을 중심으로 제품 군을 설계
반환 내용 관련된 서브 객체의 단계별 생성 절차가 완료된 후 복합 객체를 생성 및 반환 객체를 생성한 즉시 반환 
부품의 의미  만들고자 하는 부품들이 모여야 의미가 있음 각각의 부품에만 의미를 부여 

 

LIST
SMALL

디자인 패턴의 목적 중에 생성에 해당하는 다양한 패턴들 중에 

팩토리 메서드 ( Factory Method )와 추상 팩토리 ( Abstract Factory ) 패턴은 언뜻 보면 비슷해 보인다.

그래서 정확히 각각의 패턴의 차이점이 무엇인지 알아보도록 하자.

 

먼저 각 패턴의 정의를 알아보자.

 

Factory Method Pattern

부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다. 부모(상위) 클래스 코드에 구체 클래스 이름을 감추기 위한 방법으로도 사용한다.

 

Abstract Factory Pattern

인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성
다양한 구성 요소 별로 '객체의 집합'을 생성해야 할 때 유용하다. 
이 패턴을 사용하여 상황에 알맞은 객체를 생성할 수 있다.

 

Abstract Factory VS Factory Method

  팩토리 메서드 추상 팩토리
결합도를 낮추는 대상 클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야 할때 사용 
어떤 구상 클래스를 필요로 하게 될지 미리 알 수 없는 경우 사용
( ConcreteProduct <->  Client )
클라이언트에서 서로 연관된 일연의 제품들을 만들어야 할때 사용
( ConcreteFactory <-> Client )
Factory 클래스에서 객체 생성 지원 범위 한 팩토리당 한 종류 
( create 메서드가 Factory 클래스에 1개)
한 팩토리에서 서로 연관된 여러 종류 모두 지원
( create() 메서드가 팩토리 클래스에 여러 개)
사용 목적  클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야 할때 사용 
어떤 구상 클래스를 필요로 하게 될지 미리 알 수 없는 경우 사용 

클라이언트에서 서로 연관된 일연의 제품들을 만들어야 할때 사용 

 

위의 내용들을 이해한 내용을 나만의 용어로 정리해 보면

 

팩토리 메서드 패턴은 

사용자 코드와 concrete product와의 결합도를 낮추기 위해서 그 사이에 factory를 제공하는 것

실제 어떤 product를 사용자에게 넘겨줄지는 concrete factory가 결정한다. 

( 추가적으로 template 패턴이 조합되어 product를 생성하기 위한 일렬의 과정을 상위 factory를 통해 공통화할 수 있다. )

 

추상 팩토리 패턴은

팩토리 메서드의 확장판으로 객체의 집합을 생성 할 때 사용한다.

해당 팩토리를 통해 생성해야할 concrete product의 종류가 여러 가지 일 때 객체의 집합 별로 묶어서 생성할 수 있도록 도와준다.

 

LIST
SMALL

Abstract Factory Pattern

추상 팩토리 패턴은 큰 규모의 객체 군을 형성하는 생성 패턴이다.

팩토리 메서드 패턴을 확장한 패턴이라고 생각할 수 있다.

출처: https://johngrib.github.io/wiki/abstract-factory-pattern/#fnref:structure

추상 팩토리는 여러 개의 팩토리 메서드를 그룹으로 묶은 것과 유사하다.

추상 팩토리는 다양한 객체 생성 과정에서 그룹화가 필요할 때 매우 유용한 패턴이며 공장의 개념을 추상화한 것이다. ( 팩토리 메서드는 추상 팩토리와 동일하게 추상화 과정을 적용할 수 있지만 단일 그룹으로 제한한다.) 

 

각각의 그룹은 해결해야 하는 문제에 따라서 형성됩니다.

문제가 다양할 경우 새로운 객체를 생성해 그룹을 추가합니다. 

 

추상 팩토리는 생성 패턴을 그룹화된 구조로 분리합니다.

추상 클래스를 상속받는 하위 클래스를 추가해 새로운 그룹을 쉽게 만들 수 있지만 그룹의 하위 클래스를 추가하는 것은 쉽지 않습니다. 

( 위의 그림을 예시로 설명하면 새로운 그룹 -> ConcreteFactory1,2 && 그룹의 하위 클래스-> AbstractProductA, B )

 

AbstractFactory, ConcreteFactory

public abstract class Factory {
	
	abstract public TireProduct createTire();
	abstract public DoorProduct createDoor();
}

public class KoreaFactory extends Factory{

	@Override
	public TireProduct createTire() {
		return new KoreaTireProduct();
	}

	@Override
	public DoorProduct createDoor() {
		return new KoreaDoorProduct();
	}

}

public class StateFactory extends Factory{

	@Override
	public TireProduct createTire() {
		return new StateTireProduct();
	}

	@Override
	public DoorProduct createDoor() {
		return new StateDoorProduct();
	}

}

 

AbstractProduct, ConcreteProduct

public abstract class DoorProduct {
	abstract public String makeAssemble();
}

public class KoreaDoorProduct extends DoorProduct{

	@Override
	public String makeAssemble() {
		return "korea door product";
	}

}

public class StateDoorProduct extends DoorProduct{

	@Override
	public String makeAssemble() {
		return "state door product";
	}

}

 

public abstract class TireProduct {
	abstract public String makeAssemble();
}

public class KoreaTireProduct extends TireProduct{

	@Override
	public String makeAssemble() {
		return "korea tire product";
	}

}

public class StateTireProduct extends TireProduct{

	@Override
	public String makeAssemble() {
		return "state tire product";
	}

}

 

이제 각각의 클래스들은 정의가 되었고, 해당 클래스들을 호출하는 코드를 보자 

public class Main {
	public static void main(String[] args) {
		Factory factory = new KoreaFactory();
		TireProduct tire = factory.createTire();
		DoorProduct door = factory.createDoor();
		
		System.out.println(tire.makeAssemble());
		System.out.println(door.makeAssemble());
		
		factory = new StateFactory();
		tire = factory.createTire();
		door = factory.createDoor();
		
		System.out.println(tire.makeAssemble());
		System.out.println(door.makeAssemble());
		
	}
}

 

패턴의 장단점

앞에 살펴본 예제에서 각 그룹에 새로운 부품인 Engine을 추가해야 한다고 가정해봅시다.

새로운 부품을 추가할 때는 추상 클래스 그룹으로 분리된 모든 클래스에 Engine 부품 관련 코드를 삽입해야 합니다. 

추상 클래스의 경우 그룹을 나누는 것은 쉽지만 서브 생성 객체를 추가하는 것은 어렵습니다.

 

장점

  • 추상 팩토리의 그룹은 동일한 처리 로직을 갖고 있고, 다른 그룹으로 변경돼도 하위 클래스를 통해 선택적 객체를 다르게 생성할 수 있다
  • 추상 팩토리는 큰 변화 없이 시스템의 군을 생성하고 변경할 수 있다.

단점

  • 새로운 종류의 군을 추가하는 것이 쉽지 않다.
    ( 기존 군에서 새로운 군을 추가하여 확장할 때 모든 서브 클래스들이 동시에 변경돼야 한다)
  • 추상 팩토리는 팩토리 메서드와 비슷하지만 관리할 그룹이 많다 
    ( 계층의 크기가 커질수록 복잡한 문제가 발생 )

 

LIST
SMALL

Singleton Pattern

 

싱글턴은 자원 공유를 위해 객체 생성 개수를 1개로 제한합니다.

싱글턴은 다른 생성 패턴과 달리 하나의 객체만 생성을 제한하는 패턴입니다.

그리고 생성된 객체는 공유되어 어디서든 접근할 수 있습니다.

출처: https://subscription.packtpub.com/book/application_development/9781786463593/2/ch02lvl1sec19/singleton-pattern

 

싱글턴 패턴은 다음과 같은 상황에서 매우 유용합니다

  • 공유 자원 접근
  • 복수의 시스템이 하나의 자원에 접근할 때
  • 유일한 객체가 필요할 때
  • 값의 캐시가 필요할 때

예시 코드

public class Config {
	
	private static Config instance = null;
	
	private Config() {
		// private constructor
	}
	
	public static Config getInstance() {
		if (instance == null) {
			instance = new Config();
		}
		return instance;
	}
}

위의 예제에서는 multi thread 환경에서 어떠한 thread에서 getInstance를 호출하여 instance를 생성하는 시점에 

다른 thread가 해당 메서드를 호출하는 경우에 문제가 될 수 있습니다.

instance를 생성 중이긴 하지만, instance를 생성이 완료가 되지 않아서 null check 하는 로직에 true로 통과하여 싱글턴이 깨질 수 있습니다.

 

Multi Thread 환경에서의 Singleton

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

내부 정적 클래스를 선언하여 그 내부 클래스에서 인스턴스를 생성하도록 하는 방식

 

MultiThreadConfig 클래스에는 InnerClz 클래스의 변수가 없기 때문에, static 멤버 클래스더라도, 클래스 로더가 초기화 과정을 진행할 때 InnerClz 클래스는 초기화되지 않는다.
InnerClz 클래스는 getInstance()가 호출됐을 때 비로소 JVM 메모리에 로드되고, 인스턴스를 생성하게 됩니다.

 

LIST

+ Recent posts