CS :: 디자인 패턴과 프로그래밍 패러다임 || 싱글톤, 전략, 팩토리 패턴

suragryen·2024년 3월 26일
0

Udemy-Spring

목록 보기
24/25

디자인패턴이란?

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

1. 싱글톤 패턴

: 하나의 클래스에 오직 하나의 인스턴스만 만드는 패턴

  • 데이터베이스 연결 모듈에 많이 사용함
  • 생성자가 여러차례 호출되더라도 실제로 호출되는 객체는 하나이고 최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다

싱글톤 패턴을 사용하는 이유?

싱글톤 패턴을 사용함으로써 얻을 수 있는 이점 중 하나는 메모리 낭비를 방지할 수 있다.
사용자가 1초에 10번 똑같은 요청을 보내면 요청을 처리하기 위한 똑같은 객체를 1초에 10번 생성하고 소멸되는 메모리 낭비 문제가 발생하게 된다.
하지만 싱글톤 패턴을 사용하면 최초 한번 new로 객체를 생성하고 해당 객체를 이후에도 사용하도록 공유(static)하면 메모리 낭비 문제를 방지할 수 있다.

예시)

public class Singleton {
    // 정적 변수로 유일한 인스턴스를 저장합니다.
    private static Singleton instance;

    // private 생성자를 통해 외부에서 인스턴스 생성을 방지합니다.
    private Singleton() {}

    // 인스턴스에 접근하는 정적 메서드를 제공합니다.
    public static Singleton getInstance() {
        // 인스턴스가 없는 경우에만 생성합니다.
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 클라이언트는 getInstance() 메서드를 통해 싱글톤 인스턴스를 얻을 수 있다.
  • getInstance() 메서드 내부에는 instance가 null이면 생성하고,
  • null이 아니면 instance를 반환한다. 이로써 단 하나만의 객체를 생성하여 사용할 수 있도록 한다.

< 싱글턴 패턴 만들기 과정 >

new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
유일한 단일 객체를 참조할 정적 참조 변수가 필요하다.

사용 예시

public class Client {
    public static void main(String[] args) {
        // private 생성자(에러 발생)
        // Singleton singleton = new Singleton();

        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        Singleton instance3 = Singleton.getInstance();

        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);

        System.out.println(instance1 == instance2);
        System.out.println(instance1 == instance3);
    }
}

// 출력
com.book.objects.designpattern.singleton.Singleton@24d46ca6
com.book.objects.designpattern.singleton.Singleton@24d46ca6
com.book.objects.designpattern.singleton.Singleton@24d46ca6
true
true

싱글톤의 장,단점

😀장점
1. 유일한 인스턴스
싱글톤 패턴이 적용된 클래스의 인스턴스는 애플리케이션 전역에서 단 하나만 존재하도록 보장한다. 이는 객체의 일관된 상태를 유지하고 전역에서 접근 가능하도록 한다.

2. 메모리 절약
인스턴스가 단 하나뿐이므로 메모리를 절약할 수 있다. 생성자를 여러 번 호출하더라도 새로운 인스턴스를 생성하지 않아 메모리 점유 및 해제에 대한 오버헤드를 줄인다.

3. 지연 초기화
인스턴스가 실제로 사용되는 시점에 생성하여 초기 비용을 줄일 수 있다.

😓단점
1. 의존성이 높아진다
싱글톤 패턴을 사용하는 경우 클래스의 객체를 미리 생성한 뒤에 필요한 경우 정적 메서드를 이용하기 대문에 클래스 사이에 의존성이 높아지게 된다는 문제점이 있다.
싱글톤의 인스턴스가 변경 되면 해당 인스턴스를 참조하는 모든 클래스들을 수정해야 하는 문제가 발생한다.(= 강한 결합)

2. private 생성자 때문에 상속이 어렵다
싱글톤 패턴은 기본 생성자를 private로 만들었기 때문에 상속을 통한 자식 클래스를 만들 수 없다는 문제점이 있다. 즉, 자바의 객체지향 언어의 장점 중 하나인 다형성을 적용하지 못한다는 문제로 이어진다.

3. 테스트 하기 어렵다
싱글톤 패턴의 인스턴스는 자원을 공유하고 있다는 특징이 있다. 이는 서로 독립적이어야 하는 단위 테스트를 하는데 문제가 된다.
독립적인 테스트가 진행이 되려면 전역에서 상태를 공유하고 있는 인스턴스의 상태를 매번 초기화해야 한다. 초기화해주지 않으면 전역에서 상태를 공유 중이기 때문에 테스트가 정상적으로 수행되지 못할 가능성이 존재한다.

2. 팩토리 패턴

팩토리 패턴이란?

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

  • 상위 클래스와 하위 클래스가 분리되기 떄문에 느슨한 결합을 가진다.
  • 상위 클래스에서는 인스턴스 생성방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 갖게 된다.
  • 객체 로직이 따로 떼어져 있기 때문에 코드를 리팩터링 하더라도 한곳만 고칠 수 있어 유지 보수성 증가

-> 하나의 음료 제조 기계가 어떤 재료를 넣느냐에 따라 다른 결과물을 갖는것과 비슷!

예시코드

// Product 인터페이스
public interface Product {
    void use();
}

// Product의 구현 클래스 1
public class ConcreteProduct1 implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProduct1");
    }
}

// Product의 구현 클래스 2
public class ConcreteProduct2 implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProduct2");
    }
}

// 팩토리 인터페이스
public interface ProductFactory {
    Product createProduct();
}

// 팩토리의 구현 클래스
public class ConcreteProductFactory implements ProductFactory {
    @Override
    public Product createProduct() {
        // 원하는 Product 인스턴스를 생성하여 반환
        return new ConcreteProduct1();
    }
}

// 클라이언트
public class Client {
    public static void main(String[] args) {
        // 팩토리를 사용하여 Product 인스턴스를 생성
        ProductFactory factory = new ConcreteProductFactory();
        Product product = factory.createProduct();
        
        // 생성된 Product 인스턴스 사용
        product.use();
    }
}

팩토리 패턴의 장, 단점

😂장점
1. 결합도 낮춤 : 객체간의 결합도를 낮출 수 있다. 단일책임 원칙 따른다.

2. 개방 폐쇄 원칙을 따름: 기존 client의 코드를 파괴하지 않고 새로운 타입 추가할 수 있다.

🥺단점
패턴을 구현할 많은 서브 클래스를 도입하므로써 코드가 복잡해질 수 있다.

3. 전략패턴

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

// 전략 인터페이스
public interface EngineStartStrategy {
    void startEngine();
}

// 구체적인 전략 클래스 1
public class ElectricStartStrategy implements EngineStartStrategy {
    @Override
    public void startEngine() {
        System.out.println("Starting the engine with electric starter");
    }
}

// 구체적인 전략 클래스 2
public class KickStartStrategy implements EngineStartStrategy {
    @Override
    public void startEngine() {
        System.out.println("Starting the engine with kick starter");
    }
}

// 컨텍스트 클래스
public class Motorcycle {
    private EngineStartStrategy startStrategy;

    // 전략을 설정하는 메서드
    public void setStartStrategy(EngineStartStrategy startStrategy) {
        this.startStrategy = startStrategy;
    }

    // 전략을 사용하여 엔진을 시작하는 메서드
    public void startEngine() {
        startStrategy.startEngine();
    }
}

// 클라이언트 코드
public class Main {
    public static void main(String[] args) {
        // 전략을 선택하여 Motorcycle 객체를 생성
        Motorcycle motorcycle = new Motorcycle();

        // 전기 시동 전략을 선택하여 설정
        motorcycle.setStartStrategy(new ElectricStartStrategy());
        motorcycle.startEngine(); // 전기 시동으로 엔진 시작

        // 킥 스타터 전략을 선택하여 설정
        motorcycle.setStartStrategy(new KickStartStrategy());
        motorcycle.startEngine(); // 킥 스타터로 엔진 시작
    }
}

https://victorydntmd.tistory.com/292
블로그 설명 잘 돼 있음

전략 패턴의 장, 단점

😮장점
1. 유연성(Flexibility): 전략 패턴은 알고리즘을 캡슐화하고 교체 가능하도록 만들어 줌. 이는 알고리즘을 독립적으로 변경하고 재사용할 수 있게 해준다.

2. 유지보수성(Maintainability): 전략 패턴을 사용하면 알고리즘을 별도의 클래스로 분리하여 유지보수성을 향상시킬 수 있다. 새로운 알고리즘을 추가하거나 기존 알고리즘을 수정할 때, 해당 클래스만 수정하면 되므로 다른 부분에 영향을 미치지 않음.

3. 확장성(Scalability): 전략 패턴은 새로운 전략을 추가하거나 기존 전략을 수정하여 애플리케이션의 기능을 확장하기에 용이. 이는 애플리케이션이 변화에 대응할 수 있는 더 큰 유연성을 제공.

4. 코드 재사용성(Reusability): 각각의 전략은 독립적으로 캡슐화되어 있으므로, 동일한 전략을 여러 부분에서 재사용할 수 있다.

😓단점
1. 클래스 수 증가(Class Overhead): 전략 패턴을 사용하면 각각의 전략을 별도의 클래스로 구현해야 함. 따라서 전략이 많아질수록 클래스의 수가 증가하고, 클래스 간의 관계가 복잡해질 수 있다.

2. 코드 복잡성(Complexity): 전략 패턴을 구현하는 데 필요한 추가적인 클래스와 인터페이스는 코드의 복잡성을 증가시킬 수 있다. 특히, 각각의 전략에 대한 인터페이스와 구현 클래스를 작성하는 데 시간이 소요될 수 있다.

3. 선택 과정(Selection Overhead): 클라이언트는 실행 시간에 적절한 전략을 선택해야 함. 이 과정은 추가적인 오버헤드를 발생시킬 수 있으며, 잘못된 전략을 선택할 경우 예기치 않은 동작이 발생할 수 있다.

profile
블로그 이사중 ☃︎

0개의 댓글

관련 채용 정보