백엔드에서 자주 사용되는 디자인패턴 -생성 패턴형-

jinhan han·2024년 8월 13일
0

spring과 java 개발

목록 보기
6/6
post-thumbnail

--- 생성 패턴 ----

깃허브 링크 : https://github.com/Jinhan-Han-Jeremy/RealDesignPattern

-------- 팩토리 메서드(Factory Method) --------

객체 생성을 서브 클래스나 메서드로 분리해서 위임하는 패턴

  • 팩토리 메서드 패턴을 사용하는 이유
    • 클래스의 생성과 사용 처리 로직을 분리하여 결합도를 낮추기 위함 (유지보수 up).
    • 객체 생성 처리를 서브클래스로 분리 함으로써, 다양한 객체 생성 가능 (확장성 up).
    • 객체 생성 로직을 캡슐화하여 여러 곳에서 재사용 가능 (일관성 유지).

자주 사용되는 경우 : 데이터베이스 연결 객체 생성, HTTP 클라이언트 생성, 파서(Parser) 객체 생성, 알림(Notification) 서비스 객체 생성, 보고서(Report) 객체 생성, 파일 처리기(File Handler) 객체 생성, 지불 게이트웨이(Payment Gateway) 객체 생성, 메시지 브로커(Message Broker) 클라이언트 생성

  • 유연성 제공, 코드 재사용성 향상, 객체 생성을 관리하고 제어, 확장성 향상, 일관성 유지

추상 팩토리(Abstract Factory)
구체적인 클래스를 지정하지 않고, 상황에 맞는 객체를 생성하기 위한 인터페이스를 제공하는 패턴

추상 팩토리 매서드 vs 팩토리 매서드

  • 추상 팩토리 매서드
    장점 - 관련 객체들을 일관된 방식으로 생성, 기존 코드를 수정하지 않고 객체 생성, 코드 재사용성
    단점 - 인터페이스 증가, 객체 생성 로직의 복잡성 증가
  • 팩토리 매서드
    장점 - 객체 생성 방식을 쉽게 변경 가능, 코드 재사용성
    단점 - 클래스 증가, 객체 생성 로직이 분산

팩토리 매서드 코드

팩토리 매서드 예제) 프로덕트의 오브젝트와 인터페이스 구성
1. public interface FactoryProduct { : 객체의 함수 기능을 하는 인터페이스 선언

package FactoryMethodProduct;
public interface FactoryProduct {
    void use();
}

2. public class ConcreteProductA implements FactoryProduct { : 인터페이스를 활용한 오브젝트 구성 - 하나의 인터페이스를 재사용

// ConcreteProductA 클래스 - Product 인터페이스의 구현체
package FactoryMethodProduct;
public class ConcreteProductA implements FactoryProduct {
    @Override
    public void use() {
        System.out.println("Using Product A");
    }
}
// ConcreteProductB 클래스 - Product 인터페이스의 또 다른 구현체
package FactoryMethodProduct;
public class ConcreteProductB implements FactoryProduct {
    @Override
    public void use() {
        System.out.println("Using Product B");
    }
}```

팩토리 매서드 예제) 추상 클래스를 이용하여 오브젝트 생성
3. public abstract class FactoryCreator { : 추상 클래스를 선언.
4. public abstract FactoryProduct factoryMethod(); : 인터페이스를 추상 형태로 선언 및 활용.

public abstract class FactoryCreator {
    // 팩토리 메서드 - 구체적인 객체 생성을 위한 추상 메서드
    public abstract FactoryProduct factoryMethod();
// 클라이언트 메서드 - 팩토리 메서드를 사용하여 객체를 생성하고 사용하는 메서드
    public void doSomething() {
        FactoryProduct product = factoryMethod();
        product.use();
    }
}
  1. public class ConcreteCreatorA extends FactoryCreator { : 특정 객체를 만들기 위해 추상 객체 상속.
  2. @Override public FactoryProduct factoryMethod() { : 인터페이스를 이용하여 특정 객체의 함수 상속.
package FactoryMethodProduct;
public class ConcreteCreatorA extends FactoryCreator {
    @Override
    public FactoryProduct factoryMethod() {
        return new ConcreteProductA();
    }
}
// ConcreteCreatorB 클래스 - Creator 클래스를 확장하여 ProductB 객체를 생성하는 클래스
package FactoryMethodProduct;
public class ConcreteCreatorB extends FactoryCreator {
    @Override
    public FactoryProduct factoryMethod() {
        return new ConcreteProductB();
    }
}

팩토리 매서드 예제) 메인에서 클라이언트 구성

// MainByFactory 클래스 - 팩토리 메서드 패턴을 사용하는 클라이언트 코드
import FactoryMethodProduct.FactoryCreator;
import FactoryMethodProduct.ConcreteCreatorA;
import FactoryMethodProduct.ConcreteCreatorB;
public class MainByFactory {
    public static void main(String[] args) {
        // ConcreteCreatorA를 사용하여 ProductA 객체 생성
        FactoryCreator creatorA = new ConcreteCreatorA();
        creatorA.doSomething();
        // ConcreteCreatorB를 사용하여 ProductB 객체 생성
        FactoryCreator creatorB = new ConcreteCreatorB();
        creatorB.doSomething();
    }
}

전체적 구조 :

  • 기본 팩토리 클래스 { 추상 객체 생성 함수, 객체를 생성하고 사용하는 메서드}
  • 하위 팩토리 implements 기본 팩토리 클래스{@Override객체(서비스 기능 담당) 생성 기능}
  • 기본 서비스 인터페이스{서비스 함수}
  • 기본 서비스 클래스 implements 인터페이스{@Override서비스 함수}

-------- 추상 팩토리 매서드 코드 --------

추상 팩토리 매서드 예제) 프로덕트의 오브젝트와 인터페이스 구성
1. public interface AbstractFactory { : 인터페이스들을 관리하는 인터페이스 생성
2. AbstractFactoryProduct createProductA(); : 인터페이스 기반으로 서비스 객체 생성 기능을 하는 함수를 콜

package AbstractFactoryMethodProduct;
public interface AbstractFactory {
    AbstractFactoryProduct createProductA();
    AbstractFactoryProduct createProductB();
}```
  1. public class ConcreteFactory1 implements AbstractFactory{ : 팩토리 오브젝트로 두가지(1,2) 유형이 존재하며 인터페이스를 활용하여 객체 생성
  2. @Override public AbstractFactoryProduct createProductA() { : 두가지(A,B) 유형의 객체 생성을 하는 클래스
package AbstractFactoryMethodProduct;
public class ConcreteFactory1 implements AbstractFactory{
    @Override
    public AbstractFactoryProduct createProductA() {
        return new ConcreteProductA1();
    }
    @Override
    public AbstractFactoryProduct createProductB() {
        return new ConcreteProductB1();
    }
}
package AbstractFactoryMethodProduct;
public class ConcreteFactory2 implements AbstractFactory {
    @Override
    public AbstractFactoryProduct createProductA() {
        return new ConcreteProductA2();
    }
    @Override
    public AbstractFactoryProduct createProductB() {
        return new ConcreteProductB2();
    }
}

추상 팩토리 매서드 예제) 클라이언트에서 기능을 하는 오브젝트 구현
5. public interface AbstractFactoryProduct { : 핵심 객체의 인터페이스 구현
6. public class ConcreteProductA1 implements AbstractFactoryProduct {
@Override public void use() { :
핵심 객체로 인터페이스 참조 및 핵심 메소드 override

package AbstractFactoryMethodProduct;
public interface AbstractFactoryProduct {
    void use();
}
package AbstractFactoryMethodProduct;
public class ConcreteProductA1 implements AbstractFactoryProduct {
    @Override
    public void use() {
        System.out.println("Using Product A1");
    }
}
package AbstractFactoryMethodProduct;
public class ConcreteProductA2 implements AbstractFactoryProduct {
    @Override
    public void use() {
        System.out.println("Using Product A2");
    }
}
  1. AbstractFactory factory1 = new ConcreteFactory1( ); : 인터페이스 기반에 객체 생성 그리고 그 인터페이스를 받아 이용했던 ConcreteFactory1 클래스 할당
  2. AbstractFactoryProduct productA1 = factory1.createProductA(); : 서비스를 위한 인터페이스 객체에 서비스를 위한 핵심 프로덕트를 구현하는 메소드 할당
  3. productA1.use(); : 서비스 기능을 하는 핵심 객체의 함수 콜
import AbstractFactoryMethodProduct.*;
public class MainByAbstractFactory {
    public static void main(String[] args) {
        // ConcreteFactory1을 사용하여 관련 객체들 생성
        AbstractFactory factory1 = new ConcreteFactory1();
        AbstractFactoryProduct productA1 = factory1.createProductA();
        AbstractFactoryProduct productB1 = factory1.createProductB();
        productA1.use();
        productB1.use();
        // ConcreteFactory2를 사용하여 관련 객체들 생성
        AbstractFactory factory2 = new ConcreteFactory2();
        AbstractFactoryProduct productA2 = factory2.createProductA();
        AbstractFactoryProduct productB2 = factory2.createProductB();
        productA2.use();
        productB2.use();
    }
}

전체적 구조 :

  • 기본 팩토리 인터페이스{객체(서비스 기능 담당) 생성 기능들의 집합}
  • 기본 팩토리 클래스 implements 인터페이스{ 클래스 특정 객체(서비스 기능 담당)를 생성 }
  • 기본 서비스 인터페이스{서비스 함수}
  • 기본 서비스 클래스 implements 인터페이스{@Override서비스 함수}

-------- 싱글톤(Singleton) --------

한 클래스마다 인스턴스를 하나만 생성해서 인스턴스가 하나임을 보장하고 어느 곳에서도 접근할 수 있게 제공하는 패턴

  • 싱글톤 패턴을 사용하는 이유
    • 객체를 생성할 때마다 메모리 영역을 할당받지 않아서, 메모리낭비를 방지 가능.
    • 싱글톤으로 구현한 인스턴스는 '전역' 이므로, 다른 클래스의 인스턴스들이 데이터를 공유 가능.

자주 사용되는 경우 : 설정 관리 (Configuration Management), 로깅 (Logging), 캐시 관리 (Cache Management), 데이터베이스 연결 풀 (Database Connection Pool), 스레드 풀 (Thread Pool), 디바이스 설정 (Device Configuration), 외부 서비스 연결 (External Service Connection), 플러그인 관리 (Plugin Management), 세션 관리 (Session Management), 타이머 및 스케줄러 (Timer and Scheduler), 보안 설정 (Security Configuration)

  • 메모리 효율성, 전역 접근성, 인스턴스 제어, 자원 관리, 일관성 유지

싱글톤의 문제 해결 - statelsess

  • 싱글톤의 단점을 해결하기 위해 무상태(stateless)로 설계해야 한다.
    • 특정 클라이언트에 의존적인 필드가 있으면 안 된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안 된다.

싱글톤 예제) 로그를 기록하고 저장하는 역할
1. private static SingletonLogger instance; : private static으로 클래스 레벨에서 단일 인스턴스를 공유할 수 있도록 하며, 정적 메서드가 이 인스턴스에 접근할 수 있게 함.
2. private SingletonLogger() : 외부에서 인스턴스를 생성하지 못하도록 하여 단일 인스턴스 생성을 보장 더하여 특정 상태를 나타내는 변수를 함수를 포함 하지않는다.
이유는 private static등으로 변수가 관리되기 때문에 인스턴스들이 상태값을 모두 변형시키는 문제를 초래함.

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class SingletonLogger {
    // 1. 유일한 인스턴스를 저장하기 위한 정적 필드
    private static SingletonLogger instance;
    // 로그 파일을 기록할 PrintWriter 객체
    private PrintWriter writer;
    // 2. private 생성자: 외부에서 인스턴스를 생성하지 못하도록 함
    private SingletonLogger() {
        try {
            FileWriter fileWriter = new FileWriter("app.log", true);
            writer = new PrintWriter(fileWriter, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  1. public static synchronized SingletonLogger getInstance() : 여러 스레드에 의해 동시에 인스턴스들을 호출하는 경우를 예방하고, getInstance를 통하여 여러 인스턴스들을 생성 및 관리.
    // 3. 인스턴스를 반환하는 정적 메서드
    //synchronized로 여러 스레드에 의해 동시에 인스턴스들을 호출하는 경우를 예방
    // 싱글톤은 여러 인스턴스가 생성되는 것을 예방
    public static synchronized SingletonLogger getInstance() {
        if (instance == null) {
            instance = new SingletonLogger();
        }
        return instance;
    }
    // 로그 메시지를 기록하는 메서드
    public void log(String message) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        writer.println(timestamp + " - " + message);
    }
}

싱글톤 예제) 인스턴스 활용과 스테이트레스 싱글톤

import java.util.Scanner;
public class Main {
    public static String LevelAccess(int lv)
    {
        String LevelState;
        if(lv <= 10){
            LevelState = "Low level is accessed";
        }
        else {
            LevelState = "High level is accessed";
        }
        return LevelState;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 싱글톤 인스턴스를 가져옴
        //아래처럼 부르는건 스테이트레스로 인스턴스의 상태를 변경하지 않으며, 외부로부터 주어진 데이터를 처리하는 역할
        SingletonLogger singletonLogger = SingletonLogger.getInstance();
        // 로그 메시지를 기록
        try {
            // 애플리케이션 시작 로그
            singletonLogger.log("Application started.");
            // 첫 번째 질문을 출력하고 사용자로부터 입력을 받음
            System.out.print("What is your name? ");
            String name = scanner.nextLine();
            singletonLogger.log(name);
            // 두 번째 질문을 출력하고 사용자로부터 입력을 받음
            System.out.print("What is your lv? ");
            int lv = scanner.nextInt();
            // 버퍼를 비워 다음 입력을 받을 준비를 함
            scanner.nextLine(); // nextInt 후에 남아있는 newline 문자를 제거하기 위해 nextLine() 호출
            // 레벨에 따른 접근 로그 생성
            String accessLog = LevelAccess(lv);
            singletonLogger.log(accessLog);
        } catch (Exception e) {
            // 예외 발생 시 로그에 기록
            singletonLogger.log("An error occurred: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // Scanner 객체를 닫음
            scanner.close();
            // 애플리케이션 종료 로그
            singletonLogger.log("Application finished.");
        }
    }
}

전체적 구조 :

  • 싱글톤 클래스 { 상태없는 기본 생성자, 단일 인스턴스를 생성하고 사용하는 메서드, 서비스 메소드}

참조 :
https://dev-coco.tistory.com/177

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%B6%94%EC%83%81-%ED%8C%A9%ED%86%A0%EB%A6%ACAbstract-Factory-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#%EC%B6%94%EC%83%81_%ED%8C%A9%ED%86%A0%EB%A6%AC_%ED%8C%A8%ED%84%B4_%ED%8A%B9%EC%A7%95

https://innovation123.tistory.com/9#%EC%8B%B1%EA%B8%80%ED%86%A4%EC%9D%98%20%EB%AC%B8%EC%A0%9C%20%ED%95%B4%EA%B2%B0%20-%20statelsess-1

https://inpa.tistory.com/entry/GOF-💠-템플릿-메소드Template-Method-패턴-제대로-배워보자

https://appleg1226.tistory.com/category/Study?page=2

https://ssow93.tistory.com/53

profile
개발자+분석가+BusinessStrategist

0개의 댓글