Develope Me!

[면접을 위한 CS 전공지식노트] 라이브러리와 프레임워크의 차이점, 디자인 패턴 (1) 싱글톤 패턴 본문

Study/Book

[면접을 위한 CS 전공지식노트] 라이브러리와 프레임워크의 차이점, 디자인 패턴 (1) 싱글톤 패턴

코잘알지망생 2024. 6. 14. 16:43

디자인 패턴

※ 라이브러리와 프레임워크

둘은 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것을 의미하며 프레임워크는 라이브러리를 포함한 상위의 개념이다. 라이브러리와 프레임워크의 차이점은 '제어 흐름'이 어디에 있는 가에 따라 차이가 있다. 라이브러리는 개발자에게 제어 흐름이 있으며 필요할 때 라이브러리를 호출하여 사용할 수 있다. 반면 프레임워크는 '제어의 역전(Inversion of Control)'의 개념이 적용되어 프레임워크에게 제어의 흐름을 넘겨서 짜놓은 틀에 맞춰 수동적으로 동작한다. 

라이브러리는 폴더명/파일명 등에 대한 규칙이 없고 자유로운 반면 프레임워크는 폴더명/파일명 등에 대한 규칙이 있고 라이브러리에 비해 엄격한 특징을 가지고 있다. 

 

※ 디자인 패턴

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

1) 싱글톤 패턴

싱글톤 패턴(singleton pattern)은 하나의 클래스에 하나의 인스턴스(객체)만 가지는 패턴을 의미하며 보통 DB 연결 모듈에 많이 사용된다. 하나의 인스턴스를 만들어 놓으면 다른 모듈들이 공유해서 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어든다는 장점이 있는 반면 의존성이 높아진다는 단점이 있다. 

 

- 자바스크립트 적용

const obj = {name: 'Fubao', age: 3};
const obj2 = {name: 'Ai Bao', age: 10};

console.log(obj !== obj2); // true

리터럴 {} 또는 new Object로 객체를 생성하면 다른 객체와 같지 않기 때문에 그 자체로 싱글톤 패턴을 구현할 수 있다.

class Singleton{
  constructor(){
    if(!Singleton.instance){
      Singleton.instance = this
    }
    return Singleton.instance
  }

  getInstance(){
  return this.instance
}
}


const a = new Singleton()
const b = new Singleton()
console.log(a === b) // true

위의 예시는 Singleton.instance라는 하나의 인스턴트를 가지는 Singleton 클래스를 구현하였으며 a와 b는 하나의 인스턴스를 가지고 있다. 

 

- DB 연결 모듈

const URL = 'mongodb://localhost:27017/kundolapp'
const createConnection = url => ({"url" : url})
class DB {
	constructor(url) {
    	if (!DB.instance) {
        	DB.instance = createConnection(url)
        }
        return DB.instance
    }
    connect() {
    	return this.instance
    }
}
const a = new DB(URL)
const b = new DB(URL)

console.log(a === b) // true

하나의 인스턴스(DB.instance)를 기반으로 a,b를 생성하는 것을 볼 수 있으며 이를 통해 db 연결에 관한 인스턴스 생성 비용을 아낄 수 있다.  

 

- 자바 적용

class Singleton {
	private static class singleInstanceHolder {
    	private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
    	return singleInstanceHolder.INSTANCE;
    }
}

public class HelloWorld {
	public static void main(String[] args) {
    	Singleton a = Singleton.getInstance();
        Singleton b = Singleton.getInstance();
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        if (a == b) {
        	System.out.println(true);
        }
    }
}

/*
705927765
705927765
true
*/

 

 

- MySql 적용

// 메인 모듈
const mysql = require('mysql');
const pool = mysql.createPool({
	connectionLimit: 10,
    host: 'example.org',
    user: 'kundol',
    password: 'secret',
    database: '승철이디비'
});
pool.connect();

// 모듈 A
pool.query(query, function (error, results, fields) {
	if (error) throw error;
    console.log('The solution is: ', results[0].solution);
});

// 모듈 B
pool.query(query, function (error, results, fields) {
	if (error) throw error;
    console.log('The solution is : ', results[0].solution);
});

Node.js에서 MySQL DB를 연결할 때도 사용하는데 메인 모듈에서 DB 연결에 관한 인스턴스를 정의하고 다른 모듈 A,B에서 해당 인스턴스를 기반으로 쿼리를 보내는 형식으로 쓰인다.

 

-  단점

싱글톤 패턴은 TDD 테스틀 할 때 애로사항을 가진다. TDD를 할 때 단위 테스트를 주로 하는데 단위 테스트는 테스트가 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 한다. 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트 마다 독립적인 인스턴스를 만들기가 어렵다. 

 

- 의존성 주입

싱글톤 패턴은 모듈 간의 결합을 강하게 만들 수 있다는 단점을 가지는데 이때 의존성 주입(Dependency Injection)을 통해 모듈 간의 결합을 느슨하게 만들어 해결할 수 있다. 

 

왼쪽 예시처럼메인 모듈이 직접적으로 하위 모듈에 대한 의존성을 주기보다는 오른쪽처럼 의존성 주입자(dependency injector)가 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식이다. 의존성을 주입하게 되면 메인 모듈은 하위 모듈에 대한 의존성이 떨어지게 된다. (=디커플링 됨)

 

의존성 주입을 통해 모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션도 수월하게 된다. 또한 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되어 애플리케이션을 쉽게 추론 가능하며 모듈 간 관계들이 보다 명확해진다. 

 

하지만 모듈들이 더욱 분리됨에 따라 클래스가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 페널티가 생기기도 한다.

 

의존성 주입 시 '상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 하며 둘 다 추상화에 의존해야 하며 이때 추상화는 세부사항에 의존하지 말아야 한다'는 의존성 주입 원칙을 지키며 만들어야 한다. 

 

Comments