Skip to content

Latest commit

 

History

History
240 lines (188 loc) · 10.5 KB

싱글톤 패턴.md

File metadata and controls

240 lines (188 loc) · 10.5 KB

🎨 디자인 패턴

  • 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것
  • 소프트웨어 공학의 소프트웨어 설계에서 공통으로 발생하는 문제에 대해 자주 쓰이는 설계 방법을 정리한 패턴

👾 디자인 패턴 유형

유형 설명
생성 객체 인스턴스 생성에 관여, 클래스 정의와 객체 생성 방식을 구조화, 캡슐화를 수행하는 패턴
구조 더 큰 구조 형성 목적으로 클래스나 객체의 조합을 다루는 패턴
행위 클래스나 객체들이 상호작용하는 방법과 역할 분담을 다루는 패턴

👀 디자인 패턴 요약

🚩 생성

  1. Builder : 생산 단계를 캡슐화 하여 구축 공정을 동일하게 이용하도록 하는 패턴
  2. Prototype : 복사하여 새 개체를 생성할 수 있도록 하는 패턴
  3. Factory Method : 객체를 생성하기 위한 인터페이스를 정의하여 어떤 클래스가 인스턴스화 될 것인지는 서브 클래스가 결정하도록 하는 패턴
  4. Abstract Method : 생성군들을 하나의 모아놓고 팩토리 중에서 선택하게 하는 패턴
  5. Singleton : 유일한 하나의 인스턴스를 보장하도록 하는 패턴

🚩 구조

  1. Bridge : 추상과 구현을 분리하여 결합도를 낮춘 패턴
  2. Decorator : 소스를 변경하지 않고 기능을 확장하도록 하는 패턴
  3. Facade : 하나의 인터페이스를 통해 느슨한 결합을 제공하는 패턴
  4. Flyweight : 대량의 작은 객체들을 공유하는 패턴
  5. Proxy : 대리인이 대신 그 일을 처리하는 패턴
  6. Composite : 개별 객체와 복합 객체를 클라이언트에서 동일하게 사용하도록 하는 패턴
  7. Adapter : 인터페이스로 인해 함께 사용하지 못하는 클래스를 함께 사용하도록 하는 패턴

🚩 행위

  1. Interpreter : 언어 규칙 클래스를 이용하는 패턴
  2. Template Method : 알고리즘 골격의 구조를 정의한 패턴
  3. Chain of Responsibility : 객체들끼리 연결 고리를 만들어 내부적으로 전달하는 패턴
  4. Command : 요청 자체를 캡슐화하여 파라미터로 넘기는 패턴
  5. Iterator : 내부 표현은 보여주지 않고 순회하는 패턴
  6. Mediator : 객체 간 상호작용을 캡슐화한 패턴
  7. Memento : 상태 값을 미리 저장해 두었다가 복구하는 패턴
  8. Observer : 상태가 변할 때 의존자들에게 알리고, 자동 업데이트하는 패턴
  9. State : 객체 내부 상태에 따라서 행위를 변경하는 패턴
  10. Strategy : 다양한 알고리즘 캡슐화하여 알고리즘 대체가 가능하도록 한 패턴
  11. Visitor : 오퍼레이션을 별도의 클래스에 새롭게 정의한 패턴

👸🏻 싱글톤 패턴 (singleton pattern)

  • 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
  • 생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것

👍🏻 장점

  • 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줌
  1. 메모리 측면
    • 한번의 new 연산자를 통해서 고정된 메모리 영역을 사용하기 때문에 메모리 낭비를 방지할 수 있음
  2. 속도 측면
    • 이미 생성된 인스턴스를 활용하기 때문에 속도 측면에서도 이점이 있음
  3. 다른 클래스 간에 데이터 공유가 쉬움
    • 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있음

👎🏻 단점

  1. 의존성이 높아짐
    • 의존 관계상 클라이언트가 구체 클래스에 의존하게 됨
  2. 싱글톤 패턴을 구현하는 코드 자체가 많이 필요함
  3. 테스트하기 어려움
    • TDD(Test Driven Development)를 할 때 걸림돌이 됨
    • TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함.
    • 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 '독립적인' 인스턴스를 만들기가 어려움.
  4. 자식클래스를 만들 수 없음
  5. 내부 상태를 변경하기 어려움
  6. 객체 지향 설계 원칙 중에 개방-폐쇄 원칙에 위배
    • 만약 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되는데, 이때 개방-폐쇄 원칙이 위배됨
    • 결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생함.
  7. 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때,** 인스턴스가 2개가 생성되는 문제**도 발생할 수 있음.

따라서, 반드시 싱글톤이 필요한 상황이 아니면 지양하는 것이 좋다고 한다.

💡 해결책: 의존성 주입

  • 싱글톤 패턴은 사용하기가 쉽고 굉장히 실용적이지만 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있음
  • 이 때 의존성 주입을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있음
  • 의존성이란 종속성이라고도 하며 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미함
  • 메인 모듈이 '직접' 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 '간접'적으로 의존성을 주입하는 방식
  • 이를 통해 메인 모듈(상위 모듈)하위 모듈에 대한 의존성이 떨어지게 된다.
    • 참고로 이를 '디커플링이 된다'고도 함.

📌 의존성 주입의 장점

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

📌 의존성 주입의 단점

  • 모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생기기도 함.

📌 의존성 주입 원칙

  • 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 함.
  • 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 함.

🏘️ 예시

✏️ Javascript의 싱글톤 패턴

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는 하나의 인스턴스를 가짐

✏️ 데이터베이스 연결 모듈

  • 싱글톤 패턴이 많이 쓰임
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
    }
    connection() {
        return this.instance
    }
}
const a = new DB(URL)
const b = new DB(URL)
console.log(a === b) // true

✏️ JAVA의 싱글톤 패턴

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.printIn(a.hashCode()); // 705927765
        System.out.printIn(b.hashCode()); // 705927765
        if (a == b) {
            System.out.printIn(true); // true
        }
    }
}

✏️ mongoose의 싱글톤 패턴

  • 실제로 싱글톤 패턴은 Node.js에서 MongoDB 데이터베이스를 연결할 때 쓰는 mongoose 모듈에서 볼 수 있음
  • mongoose의 데이터베이스를 연결할 때 쓰는 connect()라는 함수는 싱글톤 인스턴스를 반환함
Mongoose.prototype.connect = function (uri, options, callback) {
  const _mongoose = this instanceof Mongoose ? this : mongoose;
  const conn = _mongoose.connection;

  return _mongoose._promiseOrCallback(callback, (cb) => {
    conn.openUri(uri, options, (err) => {
      if (err != null) {
        return cb(err);
      }
      return cb(null, _mongoose);
    });
  });
};

✏️ 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);
});
  • 메인 모듈에서 데이터베이스 연결에 관한 인스턴스르 정의하고 다른 모듈인 A 또는 B에서 해당 인스턴스를 기반으로 쿼리를 보내는 형식으로 쓰임



References