- 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것
- 소프트웨어 공학의 소프트웨어 설계에서 공통으로 발생하는 문제에 대해 자주 쓰이는 설계 방법을 정리한 패턴
유형 | 설명 |
---|---|
생성 | 객체 인스턴스 생성에 관여, 클래스 정의와 객체 생성 방식을 구조화, 캡슐화를 수행하는 패턴 |
구조 | 더 큰 구조 형성 목적으로 클래스나 객체의 조합을 다루는 패턴 |
행위 | 클래스나 객체들이 상호작용하는 방법과 역할 분담을 다루는 패턴 |
🚩 생성
Builder
: 생산 단계를 캡슐화 하여 구축 공정을 동일하게 이용하도록 하는 패턴Prototype
: 복사하여 새 개체를 생성할 수 있도록 하는 패턴Factory Method
: 객체를 생성하기 위한 인터페이스를 정의하여 어떤 클래스가 인스턴스화 될 것인지는 서브 클래스가 결정하도록 하는 패턴Abstract Method
: 생성군들을 하나의 모아놓고 팩토리 중에서 선택하게 하는 패턴Singleton
: 유일한 하나의 인스턴스를 보장하도록 하는 패턴
🚩 구조
Bridge
: 추상과 구현을 분리하여 결합도를 낮춘 패턴Decorator
: 소스를 변경하지 않고 기능을 확장하도록 하는 패턴Facade
: 하나의 인터페이스를 통해 느슨한 결합을 제공하는 패턴Flyweight
: 대량의 작은 객체들을 공유하는 패턴Proxy
: 대리인이 대신 그 일을 처리하는 패턴Composite
: 개별 객체와 복합 객체를 클라이언트에서 동일하게 사용하도록 하는 패턴Adapter
: 인터페이스로 인해 함께 사용하지 못하는 클래스를 함께 사용하도록 하는 패턴
🚩 행위
Interpreter
: 언어 규칙 클래스를 이용하는 패턴Template Method
: 알고리즘 골격의 구조를 정의한 패턴Chain of Responsibility
: 객체들끼리 연결 고리를 만들어 내부적으로 전달하는 패턴Command
: 요청 자체를 캡슐화하여 파라미터로 넘기는 패턴Iterator
: 내부 표현은 보여주지 않고 순회하는 패턴Mediator
: 객체 간 상호작용을 캡슐화한 패턴Memento
: 상태 값을 미리 저장해 두었다가 복구하는 패턴Observer
: 상태가 변할 때 의존자들에게 알리고, 자동 업데이트하는 패턴State
: 객체 내부 상태에 따라서 행위를 변경하는 패턴Strategy
: 다양한 알고리즘 캡슐화하여 알고리즘 대체가 가능하도록 한 패턴Visitor
: 오퍼레이션을 별도의 클래스에 새롭게 정의한 패턴
하나의 클래스
에 오직하나의 인스턴스
만 가지는 패턴- 생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것
- 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줌
- 메모리 측면
- 한번의 new 연산자를 통해서 고정된 메모리 영역을 사용하기 때문에 메모리 낭비를 방지할 수 있음
- 속도 측면
- 이미 생성된 인스턴스를 활용하기 때문에 속도 측면에서도 이점이 있음
- 다른 클래스 간에 데이터 공유가 쉬움
- 전역으로 사용되는 인스턴스이기 때문에 다른 클래스의 인스턴스들이 접근하여 사용할 수 있음
- 의존성이 높아짐
- 의존 관계상 클라이언트가 구체 클래스에 의존하게 됨
- 싱글톤 패턴을 구현하는 코드 자체가 많이 필요함
- 테스트하기 어려움
- TDD(Test Driven Development)를 할 때 걸림돌이 됨
- TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함.
- 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 '독립적인' 인스턴스를 만들기가 어려움.
- 자식클래스를 만들 수 없음
- 내부 상태를 변경하기 어려움
- 객체 지향 설계 원칙 중에 개방-폐쇄 원칙에 위배
- 만약 싱글톤 인스턴스가 혼자 너무 많은 일을 하거나, 많은 데이터를 공유시키면 다른 클래스들 간의 결합도가 높아지게 되는데, 이때 개방-폐쇄 원칙이 위배됨
- 결합도가 높아지게 되면, 유지보수가 힘들고 테스트도 원활하게 진행할 수 없는 문제점이 발생함.
- 멀티 스레드 환경에서 동기화 처리를 하지 않았을 때,** 인스턴스가 2개가 생성되는 문제**도 발생할 수 있음.
따라서, 반드시 싱글톤이 필요한 상황이 아니면 지양하는 것이 좋다고 한다.
- 싱글톤 패턴은 사용하기가 쉽고 굉장히 실용적이지만 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있음
- 이 때
의존성 주입
을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있음 의존성
이란종속성
이라고도 하며 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미함메인 모듈
이 '직접'다른 하위 모듈
에 대한 의존성을 주기보다는 중간에의존성 주입자
가 이 부분을 가로채 메인 모듈이 '간접'적으로 의존성을 주입하는 방식- 이를 통해
메인 모듈(상위 모듈)
은하위 모듈
에 대한 의존성이 떨어지게 된다.- 참고로 이를 '디커플링이 된다'고도 함.
- 모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기도 수월함
- 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해짐.
- 모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생기기도 함.
- 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 함.
- 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 함.
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
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
}
}
}
- 실제로 싱글톤 패턴은 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);
});
});
};
// 메인 모듈
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에서 해당 인스턴스를 기반으로 쿼리를 보내는 형식으로 쓰임