- 추상 팩토리 패턴은 생성패턴(Creational Pattern) 중 하나이다. - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다. - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.
- 생성 패턴을 이용하면 무엇이 생성되고, 누가 이것을 생성하며, 이것이 어떻게 생성되는지, 언제 생성할 것인지 결정하는 데 유연성을 화 보할 수 있습니다.
장점
- 추상 팩토리 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.
- 추상 팩토리 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.
- 추상 팩토리 패턴에서는 팩토리 클래스에서 서브 클래스를 생성하는 데에 있어서 필요한 if-else, switch 문을 걷어냅니다.
팩토리 메소드 패턴 vs 추상 팩토리 패턴
- 둘 다 구체적인 객체 생성 과정을 추상화한 인터페이스를 제공한다.
팩토리 메소드 패턴
추상 팩토리 패턴
관점
"팩토리를 구현하는 방법"에 초점
"팩토리를 사용하는 방법"에 초점
목적
구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적
관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적
코드
* 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)
- 중개 정책 interface 생성 :
interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.
java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)
public interface BrokeragePolicy {
BrokerageRule createBrokerageRule(Long price);
default Long calculate(Long price) {
BrokerageRule rule = createBrokerageRule(price);
return rule.calcMaxBrokerage(price);
}
}
- 매매 중개 수수료 class :
java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상
public class PurchaseBrokeragePolicy implements BrokeragePolicy{
public BrokerageRule createBrokerageRule(Long price) {
BrokerageRule rule;
if(price < 50_000_000) {
rule = new BrokerageRule(0.6, 250_000L);
} else if(price < 200_000_000) {
rule = new BrokerageRule(0.5, 800_000L);
}else if(price < 600_000_000) {
rule = new BrokerageRule(0.4, null);
}else if(price < 900_000_000) {
rule = new BrokerageRule(0.5, null);
}else{
rule = new BrokerageRule(0.9, null);
}
return rule;
}
}
- 임대차 중개 수수료 class
public class RentBrokeragePolicy implements BrokeragePolicy{
public BrokerageRule createBrokerageRule(Long price) {
BrokerageRule rule;
if(price < 50_000_000) {
rule = new BrokerageRule(0.5, 200_000L);
} else if(price < 100_000_000) {
rule = new BrokerageRule(0.4, 300_000L);
}else if(price < 300_000_000) {
rule = new BrokerageRule(0.3, null);
}else if(price < 600_000_000) {
rule = new BrokerageRule(0.4, null);
}else{
rule = new BrokerageRule(0.8, null);
}
return rule;
}
}
- 추상 팩토리 Interface Class :
추상 팩토리 역할을 하는 인터페이스 생성
public interface BrokeragePolicyAbstractFactory {
public BrokeragePolicy createBrokerageRule();
}
- 팩토리 Interface를 구현하는 클래스(매매 중개 수수료) :
작성한 팩토리 인터페이스의 createBrokerageRule() 메소드의 리턴 타입이 super class인 BrokeragePolicy이다.
이는 자바의 다형성을 잘 활용한 방식이다.
public class PurchaseBrokerageFactory implements BrokeragePolicyAbstractFactory{
@Override
public BrokeragePolicy createBrokerageRule() {
return new PurchaseBrokeragePolicy();
}
}
- 팩토리 Interface를 구현하는 클래스(임대차 중개 수수료)
public class RentBrokerageFactory implements BrokeragePolicyAbstractFactory{
@Override
public BrokeragePolicy createBrokerageRule() {
return new RentBrokeragePolicy();
}
}
- 서브 클래스들을 생성하기 위해서 if-else 없이 분개 처리해주는 클래스
public class BrokeragePolicyFactory {
public static BrokeragePolicy getBrokeragePolicy(BrokeragePolicyAbstractFactory policyAbstractFactory) {
return policyAbstractFactory.createBrokerageRule();
}
}
- 중개 수수료 계산 api
// 매매 중계수수료 계산 메소드
@GetMapping("/api/calc/brokeragepur")
public Long calcBrokeragePurchase(@RequestParam ActionType actionType,
@RequestParam Long price) {
BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new PurchaseBrokerageFactory());
return policy.calculate(price);
}
// 임대차 중계수수료 계산 메소드
@GetMapping("/api/calc/brokeragerent")
public Long calcBrokerageRent(@RequestParam ActionType actionType,
@RequestParam Long price) {
BrokeragePolicy policy = BrokeragePolicyFactory.getBrokeragePolicy(new RentBrokerageFactory());
return policy.calculate(price);
}
실무 사용 사례
Spring에서 FactoryBean과 그 구현체
정리
- 객체 생성을 담당 및 처리하는 팩토리 클래스를 생성하여서 객체 생성에 관한 확장도 쉽게 구성할 수 있다.
- 싱글톤 패턴은 생성패턴(Creational Pattern) 중 하나이다. - 싱글톤 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다. - 싱글톤 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.
- 하나의 객체만을 생성해 이후에 호출된 곳에서는 생성된 객체를 반환하여 프로그램 전반에서 하나의 인스턴스만을 사용하게 하는 패턴이다.
장점
- 한번의 객체 생성으로 재사용이 가능하여 메모리 낭비를 방지한다.
- 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 생기는 문제를 미연에 방지할 수 있다.
문제점
- 멀티 스레드 환경에서 안전하지 않다. (대처가 가능하다.)
- 객체간의 결함도가 높아지고 변경에 유연하게 대처할 수 없다. 싱글톤 객체가 변경되면 이를 참조하고 있는 모든 값들이 변경되어야 한다.
- 객체 상태를 유지하면 큰 문제가 발생한다.(stateful)
코드
* 여러가지 싱글턴 구현 방법이있다.
기본 Singleton
이른 초기화(Eager Initialization)
synchronized 사용
static inner SIngleton(권장하는 방법)
enum SIngleton(권장하는 방법)
1. 기본 Singleton
- 단점이 Thread Safe 하지 못하다.
public class SingletonStatic {
private static SingletonStatic instance;
private SingletonStatic() {}
public static SingletonStatic getInstance() {
if(instance == null) {
instance = new SingletonStatic();
}
return instance;
}
}
2. Eager Initialization
- Thread Safe
- 단점이 인스턴스를 미리 만들어 놓는다는 단점이 있다.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
3. Thread Safe Singleton(synchronized 사용)
- synchronized를 통해서 여러 쓰레드에서 동시에 접근하는 것을 막을 수 있다.(Thread Safe)
- 동기화 처리하는 작업 때문에 성능에 불이익이 생길 수 있다.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) { instance = new ThreadSafeSingleton();}
return instance;
}
}
4. static inner SIngleton(권장하는 방법)
- SingletonHelper 클래스는 SIngleton 클래스가 Load 될 때에도 Load 되지 않다가 getInstance()를 호출할 때 jvm 메모리에 로드되어 인스턴스를 생성한다. synchronized를 사용하지 않기 때문에 synchronized 키워드 자체에 대한 성능 저하 문제를 해결할 수 있다.
- 단점으로 자바 리플렉션을 이용하면 싱글톤 패턴이 깨진다.
public class BillPughSIngleton {
private BillPughSIngleton(){}
private static class SingletonHelper{
private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
}
public static BillPughSIngleton getInstance(){
return SingletonHelper.INSTANCE;
}
}
객체 상태를 유지했을 경우 주의 (stateful)
BillPughSIngleton.java
public class BillPughSIngleton {
private String apartment;
private BillPughSIngleton(){}
private static class SingletonHelper{
private static final BillPughSIngleton INSTANCE = new BillPughSIngleton();
}
public static BillPughSIngleton getInstance(){
return SingletonHelper.INSTANCE;
}
public String getApartment() {
return apartment;
}
public void setApartment(String apartment) {
this.apartment = apartment;
}
}
- 팩토리 메소드 패턴은 생성패턴(Creational Pattern) 중 하나이다. - 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴이다. - 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현방법을 시스템과 분리해준다.
장점
- 확장에 열려있고 변경에 닫혀있는 객체 지향 원칙을 적용해서 기존에 인스턴스를 만드는 과정의 로직을 건드리지 않고, 새로운 인스턴스를 다은 방법으로 확장이 가능하다.
- 팩토리 메소드 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며(Loosely Coupled), 확장을 쉽게 한다.
- 팩토리 메소드 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공한다.
단점
- 각자의 역할을 나누어서 생성하다 보니 클래스 개수가 늘어난다.
코드
* 부동산 중개 수수료 계산 프로그램 가정(매매/임대차)
- 중개 정책 interface 생성 :
interface를 구현하는 하위 클래스에서 수수료 계산 로직을 구현한다.
java8 이후로 default 메소드를 사용하면 interface 내부에서 로직 작성이 가능하다. (default 명시)
java9 이후로는 interface에 priave 메소드도 사용이 가능하다.
public interface BrokeragePolicy {
BrokerageRule createBrokerageRule(Long price);
default Long calculate(Long price) {
BrokerageRule rule = createBrokerageRule(price);
return rule.calcMaxBrokerage(price);
}
}
- 매매 중개 수수료 class :
java7 이후부터 _는 숫자 리너털 어디에도 사용할 수 있다. 가독성 향상
public class PurchaseBrokeragePolicy implements BrokeragePolicy{
public BrokerageRule createBrokerageRule(Long price) {
BrokerageRule rule;
if(price < 50_000_000) {
rule = new BrokerageRule(0.6, 250_000L);
} else if(price < 200_000_000) {
rule = new BrokerageRule(0.5, 800_000L);
}else if(price < 600_000_000) {
rule = new BrokerageRule(0.4, null);
}else if(price < 900_000_000) {
rule = new BrokerageRule(0.5, null);
}else{
rule = new BrokerageRule(0.9, null);
}
return rule;
}
}
- 임대차 중개 수수료 class
public class RentBrokeragePolicy implements BrokeragePolicy{
public BrokerageRule createBrokerageRule(Long price) {
BrokerageRule rule;
if(price < 50_000_000) {
rule = new BrokerageRule(0.5, 200_000L);
} else if(price < 100_000_000) {
rule = new BrokerageRule(0.4, 300_000L);
}else if(price < 300_000_000) {
rule = new BrokerageRule(0.3, null);
}else if(price < 600_000_000) {
rule = new BrokerageRule(0.4, null);
}else{
rule = new BrokerageRule(0.8, null);
}
return rule;
}
}
- Factory Class (핵심 클래스)
public class BrokeragePolicyFactory {
public static BrokeragePolicy of(ActionType actionType) {
switch (actionType) {
case RENT:
return new RentBrokeragePolicy();
case PURCHASE:
return new PurchaseBrokeragePolicy();
default:
throw new IllegalArgumentException("해당 actionType에 대한 정책이 존재하지 않습니다.");
}
}
}
- 중개 수수료 계산 api
public Long calcBrokerage(@RequestParam ActionType actionType,
@RequestParam Long price) {
// 타입 정의 - 매매 / 임대차
BrokeragePolicy policy = BrokeragePolicyFactory.of(actionType);
return policy.calculate(price);
}