Builder,Observer,Factory,Singleton패턴

진명재·2024년 1월 29일
0

HereWeGo!(Architecture)

목록 보기
3/3
post-thumbnail

가장 많이 사용되는 Builder,Observer,Factory,Singleton 에 대해 알아보려 한다!!

Builder 패턴

GoF 중 생성 패턴에 속하며, 객체를 생성할 때 유용하게 사용하는 디자인패턴이다.
기존에 생성자를 이용하여 객체를 생성하는데 생길 수 있는 문제를 보완하기 위해 사용되어 지며, 객체의 Optional한 속성이 많을수록 빌더패턴이 더욱 유용해진다.

특징

builder패턴은 dot(.)체인 문법을 사용한다.

장점

  • 객체 생성이 되는 순간 setting을 모두 하기 때문에 변경 불가능한(immutable) 객체를 만들 수 있음.
  • 객체 클래스 내부에 복잡하게 여러 개의 생성자를 만들 필요가 없음
  • build() 함수로 잘못된 값이 세팅되어있는지 검증이 가능
  • 직관적인. 세팅 메서드 네이밍을 통해 인지 값을 파악하는데 용이함

단점
객체 수정이 불가능하기 때문에, 필요에 따라 새로운 객체를 계속 생성해야한다.

그런데 우리가 평소에 사용하던 롬복의 @Builder 애노테이션으로 쉽게 사용할 수 있다.

package WeGoTogether.wegotogether.entity;

// Effective Java의 Builder Pattern
public class HereWeGo {
    private final String frontend;
    private final String backend;
    private final String song;
    private final String jung;
    private final String kim;

    public static class Builder {
        // Required parameters(필수 인자)
        private final String frontend;
        private final String backend;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private String song ="Songhayoung";
        private String jung="Jungjuhyeon";
        private String kim="Kimminchan";

        public Builder(String frontend, String backend) {
            this.frontend = frontend;
            this.backend  = backend;
        }

        public Builder Song(String val) {
            song = val;
            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
        }
        public Builder Jung(String val) {
            jung = val;
            return this;
        }
        public Builder Kim(String val) {
            kim = val;
            return this;
        }

        public HereWeGo build() {
            return new HereWeGo(this);
        }
    }

    private HereWeGo(Builder builder) {
        frontend  = builder.frontend;
        backend   = builder.backend;
        song     = builder.song;
        jung     = builder.jung;
        kim      = builder.kim;
    }
}

이렇게 설정을 하면 Main에서 밑에와 같이 사용이 가능하다.

package WeGoTogether.wegotogether.entity;

public class Main {

    HereWeGo hereWeGo = new HereWeGo
            .Builder("web,flutter","spring")
            .Song("flutterMaster")
            .Jung("springMaster")
            .Kim("webMaster")
            .build();
}

그런데 우리가 평소에 사용하던 롬복의 @Builder 애노테이션으로 쉽게 사용할 수 있다.

@Builder
public static ParticipateDtoRes.participateRes participateDto (Participate participate){
        return ParticipateDtoRes.participateRes.builder()
                .nickName(participate.getUser().getNickName()) // user를 가져와 다시 nickname을 가져옴
                .liked(participate.getLiked())
                .is50Up(participate.getIs_50up())
                .isBestUp(participate.getIs_bestup())
                .participateTime(participate.getParticipateTime())
                .build();
    }

Obserber 패턴

Observer Pattern은 객체 간에 일대다 관계를 정의하는 디자인 패턴 중 하나이다. 이 패턴은 객체들 사이에 느슨한 결합을 제공하며, 객체의 상태 변화에 대한 다른 객체들의 자동 업데이트를 가능하게 한다.

Observer Pattern은 Subject(주체)와 Observer(관찰자)로 구성된다.Subject 객체는 상태를 가지며, 상태가 변경되었을 때 Observer들에게 알림을 보냅니다. Observer 객체는 Subject 객체의 상태를 관찰하며, 상태가 변경될 때마다 업데이트를 받는다.

장점

  • 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다. 이는 객체 사이의 상호의존성을 최소화할 수 있기 때문이다.
  • Open / Close 원칙(개방 폐쇄 원칙)을 지킬 수 있다. (개방 폐쇄 원칙: 확장에는 열려있고, 변경에는 닫혀있어야 한다. )

단점

  • Observer에게 알림이 가는 순서를 보장할 수 없습니다.
  • Observer, Subject의 관계가 잘 정의되지 않으면 원하지 않는 동작이 발생할 수 있습니다.

느슨한 결합(Loose Coupling)
두 객체가 느슨하게 결합되어 있다는 것은, 그 둘이 상호작용을 하긴 하지만 서로에 대해 잘 모르는 것을 의미합니다. 옵저버 패턴에서는 주제와 옵저버가 느슨하게 결합되어 있는 객체 디자인을 제공합니다.

느슨한 결합의 장점은 다음과 같습니다.

  • 옵저버를 언제든 새로 추가, 제거할 수 있다.
  • 새로운 형식의 옵저버라 할 지라도 주제를 전혀 변경할 필요가 없다.
  • 주제와 옵저버는 서로 독립적으로 재사용 할 수 있다.
  • 주제나 옵저버가 바뀌더라도 서로에게 영향을 미치지 않는다.

//Observer 인터페이스는 Observer 역할을 하는 객체가 구현해야 하는 메서드를 정의합니다. 이 인터페이스는 다음과 같이 구성됩니다.

package WeGoTogether.wegotogether.entity;

public interface Observer {
    void update(int member);
}

Subject 인터페이스는 Subject 역할을 하는 객체가 구현해야 하는 메서드를 정의합니다. 이 인터페이스는 다음과 같이 구성됩니다.

package WeGoTogether.wegotogether.entity;




public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

Subject 클래스는 Subject 인터페이스를 구현하며, 상태를 저장하고 Observer 객체들을 등록하고 삭제하는 메서드를 구현합니다. 이 클래스는 다음과 같이 구성됩니다.

package WeGoTogether.wegotogether.entity;

import java.util.ArrayList;
import java.util.List;

public class HereWeGoData implements Subject {
    private List<Observer> observers;
    private int member;

    public HereWeGoData() {
        observers = new ArrayList<>();
    }

    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(member);
        }
    }

    public void setMember(int member) {
        this.member = member;
        measurementsChanged();
    }

    public void measurementsChanged() {
        notifyObservers();
    }
}

Observer 클래스는 Observer 인터페이스를 구현하며, Subject 객체의 상태가 변경될 때마다 업데이트를 받는 메서드를 구현합니다. 이 클래스는 다음과 같이 구성됩니다.

package WeGoTogether.wegotogether.entity;

public class HereWeGoObserver implements Observer {
    private float member;

    public void update(int member) {
        this.member = member;
        display();
    }

    public void display() {
        System.out.println("Current temperature: " + member);
    }

    public static void main(String[] args){
        HereWeGoData hereWeGoData = new HereWeGoData();

        HereWeGoObserver hereWeGoObserver = new HereWeGoObserver();
        hereWeGoData.registerObserver(hereWeGoObserver);

        hereWeGoData.setMember(12);
        hereWeGoData.setMember(13);
    }
}

참고자료
방리님 블로그

Factory pattern

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 패턴이다.

객체는 속성과 함수가 변경, 또는 추가 될 수 있다. 이에 따라 객체의 생성을 담당하는 코드는 변경의 가능성이 높다. 객체의 생성을 담당하는 클래스를 한 곳에서 관리하여 결합도를 줄이기 위하여 팩토리 패턴이 나타나게 된 것이다.

장점

  • 객체간의 결합도를 낮출 수 있다.
  • 단일 책임 원칙을 따른다. 프로그램의 코드에서 생성자 코드를 분리함으로써 코드를 더욱 간결하게 만들 수 있다.
  • 개방 폐쇄 원칙을 따른다. 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가 할 수 있다.

단점

  • 패턴을 구현할 많은 서브 클래스를 도입합으로써 코드가 복잡 해 질 수 있다.

생성자를 서브클래스에 작성

package WeGoTogether.wegotogether.entity.FactoryPattern;

public interface HereWeGo {
    void part();
}

spring 클래스 작성

package WeGoTogether.wegotogether.entity.FactoryPattern;

public class Spring implements HereWeGo{
    @Override
    public void part(){
        System.out.println("Spring의 HereWego 메소드");
    }
}

web 클래스 작성

package WeGoTogether.wegotogether.entity.FactoryPattern;

public class Web implements HereWeGo{
    @Override
    public void part(){
        System.out.println("Web의 part 메소드");
    }
}

flutter 클래스 작성

package WeGoTogether.wegotogether.entity.FactoryPattern;

public class Flutter implements HereWeGo{
    @Override
    public void part(){
        System.out.println("Flutter의 part 메서드");
    }
}
package WeGoTogether.wegotogether.entity.FactoryPattern;

public class PartFactory {
    public HereWeGo getPart(String partType) {
        if(partType == null){
            return null;
        }
        if(partType.equals("Web")){
            return new Web();
        }
        else if(partType.equals("Spring")){
            return new Spring();
        }
        else if(partType.equals("Flutter")){
            return new Flutter();
        }
        return null;
    }
}
package WeGoTogether.wegotogether.entity.FactoryPattern;



public class FactoryPattern {
    public static void main(String[] args){
        PartFactory partFactory = new PartFactory();


        HereWeGo part1 = partFactory.getPart("Web");
        // web의 part 메소드 호출
        part1.part();

        HereWeGo part2 = partFactory.getPart("Flutter");

        part2.part();

        HereWeGo part3 = partFactory.getPart("Spring");

        part3.part();

    }
}

추상팩토리

추상팩토리에 대해서는 간결하게 설명하고 넘어 가겠다.

추상 팩토리 패턴은 많은 수의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 교체할 수 있도록 만든 디자인 패턴이다.

내가 만든 코드에 빗대어 보자면 spring은 backend로 web과flutter는 frontend로 묶는다는 뜻이다.

장점

  • Factory로 부터 만들어진 각 객체들이 서로 compatible 함을 보장할 수 있다.
  • 객체간의 결합도를 낮출 수 있다.
  • 단일 책임 원칙을 따른다. 프로그램의 코드에서 생성자 코드를 분리함으로써 코드를 더욱 간결하게 만들 수 있다.
  • 개방 폐쇄 원칙을 따른다. 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가 할 수 있다.

단점

  • 패턴을 구현할 많은 서브 클래스를 도입합으로써 코드가 복잡 해 질 수 있다.

참고자료
elly님

Singleton pattern

싱글톤(Singleton) 패턴의 정의는 단순하다. 객체의 인스턴스가 오직 1개만 생성되는 패턴을 의미한다.
쉽게 말하자면 메모리 절약을 위해, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 만들지 않고 기존의 인스턴스를 가져와 활용하는 기법을 말한다.

우리가 자주사용하는 전역변수와 비슷하다. 전역변수를 생성해 놓으면 한번 만들어놓고, 가져와 사용하면 효율적이기 때문이다.

싱글톤 패턴 구현

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하는 방식으로 진행이 됩니다.

문제점으로는 멀티스레드에서 사용될시 스레드는 경쟁하게 되는데 그럴때 여러개가 생성 될 가능성이 있다.
이를 해결하기 위해 synchronized라는것을 사용 하게 되는데, 이를 사용하면 동기화로 인해 성능 저하가 발생한다.

참고자료
코딩수첩

profile
하고싶은게 많은 대학생입니다 :)

1개의 댓글

comment-user-thumbnail
2024년 4월 9일

죄송한데 블로그 활동 접으셨나여?

답글 달기