말하는 컴공감자의 텃밭

디자인 패턴 정리 Js, Java - 싱글톤, 팩토리, 중재자, 옵저버 본문

기록/CS

디자인 패턴 정리 Js, Java - 싱글톤, 팩토리, 중재자, 옵저버

현콩 2024. 1. 16. 21:49
728x90

디자인 패턴?

디자인 패턴 정리
소웨공 기억나네오

 

소프트웨어 설계에서 재사용을 위한 목적으로 설계된 디자인으로 다양한 상황에서 문제를 해결하는 방법에 대한 설명, 또는 템플릿 이라고 할 수 있다.

 

디자인 패턴 정리
출처 : https://m.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823


싱글톤

 

싱글톤 패턴은 특정 클래스의 인스턴스를 1개만 생성하는 디자인 패턴이다.

생성자를 통해서 여러 번 호출이 되더라도 인스턴스를 새로 생성하지 않고 최초 호출 시에 만들어두었던 인스턴스를 재활용하는 패턴이다. 메모리 효율에 장점이 있는 디자인 패턴이다.

 

자바스크립트 예시

let instance;


// 하나의 인스턴스만 존재하도록 로직 작성
class Counter {
    constructor() {
    	// 이미 인스턴스가 존재하는경우
        if (instance) {
            throw new Error("하나의 인스턴스만 생성 가능합니다");
        }
        this.counter = 0;
        instance = this;
    }

    getCount() {
        return this.counter;
    }

    increment() {
        return this.counter++;
    }

    decrement() {
        return this.counter--;
    }
}

// 객체 인스턴스 생성, 하나만 가능하다.
const singletonCounterA = new Counter();

singletonCounterA.increment();
singletonCounterA.increment();
// 2가 출력된다.

// Error를 던지며 작성해둔 "하나의 인스턴스만 생성 가능합니다"가 표기된다.
const CounterB = new Counter();

 

 

 

자바 예시

public class Singleton {

    // 클래스의 단일 인스턴스
    private static Singleton instance;

    // 다른 클래스에서 사용하지 못하도록 private 생성자
    private Singleton() {}

    // 단일 인스턴스에 대한 전역 접근점
    public static Singleton getInstance() {
        if (instance == null) {
            // 인스턴스가 없다면 초기화
            instance = new Singleton();
        }
        return instance;
    }

    // 예시 메서드
    public void someMethod() {
        System.out.println("싱글톤 클래스의 메소드.");
    }

    // 사용법
    public static void main(String[] args) {
        // 사용 가능한 유일한 객체 가져오기
        Singleton singleton = Singleton.getInstance();

        // 메서드인 someMethod 실행
        singleton.someMethod();
    }
}

 

결국 싱글톤 메모리가 많이 사용되는 상황에서 같은 인스턴스가 필요할때 사용되는 디자인 패턴이다.

어플리케이션에서 유일해야하고, 객체를 새로 만들필요가 없는 상황에서 사용된다고 생각하면 되겠다.

 

사용 예시

 

데이터베이스 연결: 

  • 싱글톤은 데이터베이스 연결을 관리하는 데 자주 사용된다.
  • 데이터베이스 연결에 싱글톤을 사용함으로써 애플리케이션은 애플리케이션 전체에서 연결의 단일 인스턴스를 사용하도록 보장한다.

 

캐싱: 

  • 캐시 관리자의 단일 인스턴스를 사용하면 캐시된 모든 데이터를 중앙에서 관리하고 애플리케이션 전반에 걸쳐 액세스할 수 있으므로 메모리와 리소스 사용이 최적화된다.

 

쓰레드 풀: 

  • 멀티쓰레딩을 사용하는 애플리케이션에서 싱글톤을 사용하여 쓰레드 풀을 관리하고 재사용할 수 있다.
  • 쓰레드를 자주 생성하고 삭제하는 것은 리소스 집약적일 수 있으므로 이는 리소스 관리에 도움이 된다.
  • 다만 다중 쓰레드 환경이나 의존성이 높은경우, 테스트를 방해할 수 있는 경우 문제가 발생하므로 신중하게 사용하여야 한다.

 


 

 

팩토리

 

팩토리 패턴은 특수 함수인 팩토리 함수를 사용하여 비슷한 객체를 많이 생성할 수 있다.

=> 비슷한 객체를 반복적으로 생성해야하는 경우에 사용된다.

 

책으로 구현해보자

 

자바스크립트 예시

// 동일한 구성요소이지만 하나하나 만들어야 함.
const book1 = {
     title: 'Harry Potter',
     author: 'JK Rowling',
     isbn: 'AB123',
};

 const book2 = {
     title: 'The Great Gatsby',
     author: 'F. Scott Fitzgerald',
     isbn: 'CD456',
 };

 const book3 = {
     title: 'Moby-Dick',
     author: 'Herman Melville',
     isbn: 'EF789',
 };
const createBook = (title, author, isbn) => ({
    title,
    author,
    isbn,
});

const book1 = createBook('Harry Potter', 'JK Rowling', 'AB123');

const book2 = createBook(
    'The Great Gatsby',
    'F. Scott Fitzgerald',
    'CD456'
);

const book3 = createBook('Moby-Dick', 'Herman Melville', 'EF789');

 

createBook 으로 여러가지 책을 작성하기 수월해졌다.

 

 

자바 예시

public class BookFactory {

    // 책 객체를 생성하는 메소드
    public Book createBook(String title, String author, String isbn) {
        return new Book(title, author, isbn);
    }
}
public class BookDemo {
    public static void main(String[] args) {
        BookFactory bookFactory = new BookFactory();

        Book book1 = bookFactory.createBook("Harry Potter", "JK Rowling", "AB123");
        Book book2 = bookFactory.createBook("The Great Gatsby", "F. Scott Fitzgerald", "CD456");
        Book book3 = bookFactory.createBook("Moby-Dick", "Herman Melville
	}
}

 

깔끔하게 만들어진 모습.

패턴을 사용하지 않았더라면?

 

public class Book {
    private String title;   // 책 제목
    private String author;  // 저자
    private String isbn;    // ISBN 번호

    // 생성자
    public Book(String title, String author, String isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }

    // Getter 메소드들
    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

    // 책 정보를 출력하는 메소드
    public void displayBookInfo() {
        System.out.println("제목: " + title + ", 저자: " + author + ", ISBN: " + isbn);
    }
}

 

결국 팩토리 패턴클라이언트가 팩토리 인터페이스를 사용하여 객체요청하고, 팩토리에서 요청된 타입에 맞는 객체를 생성하여 반환해준다.

 

장점클라이언트는 객체가 어떻게 생성되고, 어떤 구체적인 클래스 타입인지 모르게되고. 결합도 또한 낮아진다.

이러한 접근 방식은 복잡한 객체 생성 과정이나 다양한 종류의 객체가 필요한 경우에 효과적이다.

 


중재자

중재자는 말 그대로 중앙에 권한을 가지고 관리하기 위한 디자인 패턴이다.

카카오톡을 예시로 든다면 A와 B가 채팅을 하게되면 카카오톡에서 필터링이나 관리가 들어간 후 A와 B에게 채팅이 전해지는것을 생각하면 되겠다..

 

채팅창으로 구현해보자

 

자바스크립트 예시

 

Participant에 유저 이름과 채팅방, 메세지가 담긴다.

send, receive, showMessages의 간단한 메서드를 담고있다.

// 참여자들 생성
class Participant {
    constructor(name) {
        this.name = name
        this.chatRoom = null
        this.messages = []
    }
	// 송신
    send(message, to) {
        this.chatRoom.send(message, this, to)
    }
	//수신
    receive(message, from) {
        this.messages.push({ message, from })
    }
	//메세지 띄우기
    showMessages() {
        console.log(this.messages)
    }
}

 

중재자인 ChatRoom에서 라우팅을 통해 채팅을 전달하게 된다.

// 채팅룸 작성
class ChatRoom {
    constructor() {
        this.participants = {}
    }
	// 입장
    enter(participant) {
        this.participants[participant.name] = participant
        participant.chatRoom = this
    }
	// 보내기
    send(message, participant, to) {
        this.participants[to.name].receive(message, participant)
    }
}
//인스턴스들 작성
const chatRoom = new ChatRoom()

const user1 = new Participant('user1')
const user2 = new Participant('user2')
const user3 = new Participant('user3')

chatRoom.enter(user1)
chatRoom.enter(user2)
chatRoom.enter(user3)

user1.send('Hello', user2)
user2.send('Nice meet to you', user1)
user3.send('Boring....', user1)

user1.showMessages()
user2.showMessages()
user3.showMessages()

 

 

이렇게 작성하게 된다면 ChatRoom 에서 입장과 채팅을 관리할 수 있는 구조로 작성되었다.

ex) 사용자가 메세지를 보낸다면 ChatRoom 의 send를 통해 보내게된다.

 

 

자바 예시

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

public class Participant {
    private String name;
    private ChatRoom chatRoom;
    private List<String> messages;

    public Participant(String name) {
        this.name = name;
        this.chatRoom = null;
        this.messages = new ArrayList<>();
    }

    public void send(String message, Participant to) {
        this.chatRoom.send(message, this, to);
    }

    public void receive(String message, Participant from) {
        messages.add(from.getName() + ": " + message);
    }

    public void showMessages() {
        for (String message : messages) {
            System.out.println(message);
        }
    }

    // Getter
    public String getName() {
        return name;
    }

    // Setter chatRoom에서 받아옴
    public void setChatRoom(ChatRoom chatRoom) {
        this.chatRoom = chatRoom;
    }
}

 

public class MediatorPattern {
    public static void main(String[] args) {
        ChatRoom chatRoom = new ChatRoom();

        Participant user1 = new Participant("user1");
        Participant user2 = new Participant("user2");
        Participant user3 = new Participant("user3");

        chatRoom.enter(user1);
        chatRoom.enter(user2);
        chatRoom.enter(user3);

        user1.send("Hello", user2);
        user2.send("Nice to meet you", user1);
        user3.send("Boring....", user1);

        System.out.println("user1's messages:");
        user1.showMessages();
        System.out.println("\nuser2's messages:");
        user2.showMessages();
        System.out.println("\nuser3's messages:");
        user3.showMessages();
    }
}

 

JS와 작성된 것과 동일하게 작성했다.

ChatRoom이 동일하게 메세지 전송을 중재하고,  Participant 가 메세지를 보내고 받게된다.

상호작용을 중앙에서 관리함으로써, 시스템의 유지보수성확장성을 향상시키는 특징이 있다.

각 컴포넌트 또는 객체가 서로를 직접 참조하지 않아도 되므로, 결합도를 낮추고 각 컴포넌트의 재사용성높다.

 

스마트 홈이나 워크플로우 시스템 등에서 사용된다.

 


 

옵저버

특정 객체를 관찰하는 Observer이 존재하는 패턴이다.

특정객체동작한다면 옵저버에게 notify가 가는 패턴이다.

한 객체의 상태변화다른 객체들이 감지하고 자동적으로 반응 할 수 있는 패턴인데, 주로 이벤트 핸들링 시스템에서 자주 사용된다.

 

주식을 예시로 코드를 작성해보자.

사용자가 특정 테마의 주식의 변동을 알고싶어 한다면 변동이 있을때 알람이 가는 코드이다.

 

자바 예시

import java.util.*;

// 관찰자 인터페이스
interface Observer {
	void update(String stockSymbol, float stockValue);
}

// 주제 인터페이스
interface Subject {
	void registerObserver(Observer o);

	void removeObserver(Observer o);

	void notifyObservers();
}

// 주식시장 주제 클래스
class StockMarket implements Subject {
	private Map<String, Float> stockList = new HashMap<>();
	private List<Observer> observers = new ArrayList<>();

	// 주가
	public void setStockPrice(String stockSymbol, float stockValue) {
		stockList.put(stockSymbol, stockValue);
		// 가격에 변동이 생긴다면 notify이 간다.
		notifyObservers();
	}

	@Override // 옵저버로 등록하는 메서드
	public void registerObserver(Observer o) {
		observers.add(o);
	}

	@Override // 옵저버를 지우는 메서드
	public void removeObserver(Observer o) {
		observers.remove(o);
	}

	@Override // 옵저버에게 알림을 주는 메서드
	public void notifyObservers() {
		for (Observer observer : observers) {
			for (Map.Entry<String, Float> entry : stockList.entrySet()) {
				observer.update(entry.getKey(), entry.getValue());
			}
		}
	}
}

// 구체적인 관찰자 클래스
class Investor implements Observer {
	private String name;
	
	public Investor(String name) {
		this.name = name;
	}

	@Override // 가격 업데이트 시 알람가는  메서드
	public void update(String stockSymbol, float stockValue) {
		System.out.println(name + "에게 알림: " + stockSymbol + "의 새로운 주가는 " + stockValue + "입니다.");
	}

	public class ObserverPatternDemo {
		public static void main(String[] args) {
			StockMarket stockMarket = new StockMarket();
			Investor investor1 = new Investor("투자자 1");
			Investor investor2 = new Investor("투자자 2");

			stockMarket.registerObserver(investor1);
			stockMarket.registerObserver(investor2);
			stockMarket.setStockPrice("삼성전자", 55000);
			stockMarket.setStockPrice("애플", 150);
			// 애플이 관심이 없어졌다면
			stockMarket.removeObserver(investor1);
			stockMarket.setStockPrice("삼성전자", 56000);
		}
	}
}

 

코드가 꽤 길어졌는데 시나리오로 설명하자면

 

1. StockMarket 객체에 다양한 객체 ( 투자자들 )이 생성된다.

2. 투자자들은 StockMarket에서 옵저버로 등록한다.( registerObserver )

3. 옵저버로 등록이 되어있다면 주가가 변동이 생길때( setStockPrice ) notifyObservers 메서드를 통해 알림을 받는다.

4. 만약 해당 주제가 관심이 없어졌다면 removeObserver를 통해 옵저버를 지워준다.

 

삼성전자라는 주식이 55000에서 56000으로 set 되었다면,  해당되는 값옵저버들에게 전달해주는 과정이다.

 


모듈

코드를 더 작고 재사용한 조각으로 쓸 수 있도록 모듈화 하는 패턴이다.

하나의 파일에 모두 작성하는게 아니라 각각의 기능에 맞게 나눠 작성하고, import 해주는 방식이다.

 

기능별로 따로 작성

// import로 가져와서 사용
import { validateInput } from "./input.js";
import { sum } from "./math.js";

console.log(validateInput, sum);

 

JS

<script type = "module"> 로 모듈화 시킬 수 있다.

다만 엄격모드가 실행되고, 지연실행된다.

defer 속성 없이도 모듈화를 사용하면 지연실행을 하게 된다.

 

스크립트 관련

- 스크립트 실행시간이 조절된다.

  • 일반
    • 브라우저가 HTML을 파싱하다 멈추고 스크립트를 다운 받은 후 실행한다. 이후 HTML을 파싱을 이어나간다.
  • async
    • 브라우저가 HTML을 파싱하면서 스크립트를 다운 받는다. 실행할 때만 파싱을 멈추고 이후 HTML을 파싱을 이어나간다.
  • defer(지연)
    • 브라우저가 HTML을 파싱하면서 스크립트를 다운 받는다. HTML 문서 준비가 끝난 후 스크립트를 실행한다.

 

지연, 일반 차이 예시

  <body>
    <script type="module">
      alert(`${typeof button} module`);
    </script>

    <script >
      alert(`${typeof button} no module`);
    </script>

    <button id="button">Button</button>
 </body>

 

 

일반적인 경우 파싱이 끝나지 않은 상태로 호출하므로 udefined가 출력되고,

module의 경우 html이 파싱이 된 후 호출되므므로 object를 출력하게 된다.

 

또한 엄격모드로 인해 변수로 선언이 안되어있다면 오류를 발생시킨다.

엄격모드이기 때문에 에러가 발생한다.
    <!-- <script type="module">
      a = 4; // 에러
    </script> -->

 

 

자바 예시

사용자 인터페이스, 비즈니스 로직, 데이터 접근 계층 3가지를 모듈화 해보았다.

 

//사용자 인터페이스 모듈
module com.myapp.ui {
    requires com.myapp.business;
    exports com.myapp.ui;
}

사용자에게 정보를 표시, 입력을 처리하는 모듈

// 비즈니스 로직 모듈
module com.myapp.business {
    requires com.myapp.data;
    exports com.myapp.business;
}

애플리케이션 핵심 기능, 비즈니스 규칙을 담은 모듈

// 데이터 접근 계층 모듈 
module com.myapp.data {
    requires java.sql;
    exports com.myapp.data;
}

데이터베이스와 다른 저장소의 상호작용을 처리하는 모듈

 

모듈화의 장점

  • 캡슐화: 각 모듈은 특정 기능에 집중하고, 모듈 내부의 구현 세부 사항을 숨긴다.
  • 재사용성: 각 모듈은 독립적으로 개발되고 테스트될 수 있고, 필요한 경우 다른 프로젝트에서 재사용할 수 있다.

 

  • 유지보수성: 모듈 별로 코드를 분리함으로써 코드베이스가 더 관리하기 쉬워지고, 오류를 찾아 수정하기 쉽다.
  • 의존성 관리: 각 모듈은 필요한 다른 모듈에 대한 의존성을 명시한다. 이는 애플리케이션 전체의 의존성 구조를 명확하게 하고, 불필요한 의존성을 줄이는 데 효과적이다.

 

728x90

'기록 > CS' 카테고리의 다른 글

레거시 프로젝트 ?  (0) 2024.04.15
Comments