1.1.1) 싱글톤
🐣 서론
디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 ‘규약’ 형태로 만들어 놓은 것을 의미한다.
💁🏻 싱글톤 패턴
싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다. 보통 데이터베이스 연결 모듈에 많이 사용한다.
인스턴스 : 클래스에 소속된 개별적 개체 ex) User 클래스의 홍길동 객체
하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있다. 하지만 의존성이 높아진다는 단점이 있다.
🔭 자바스크립트의 싱글톤 패턴
const obj = {
a:27
}
const obj2 = {
a:27
}
console.log(obj === obj2)
//false
const는 변수값 변경이 불가능한 자료형을 선언한 것이다
console.log로 진위여부를 판단한다. 변수가 같은지 비교하기 위해 ===를 썼다.
자바스크립트에서는 ==보다 ===를 쓰기를 권장한다. (엄격히 비교)
(가능한 ==를 사용하지 않고 자료형을 직접 변환(캐스팅)하여 코드 가독성을 높이자)
자바스크립트에서는 리터럴{} 또는 new Object로 객체를 생성하면 다른 어떤 객체와도 같지 않다.
따라서 엄격히 비교시같지 않으므로 따라서 결과는 false
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
constructor() -> 이 함수로 객체의 초기값을 설정할 수 있음 약속된 이름으로 바꾸면 안된다
싱글톤 패턴에서는 이미 객체가 생성되었는지 판단하는 instance와 같은 내부 변수가 필요
!Singleton.instance - > 인스턴스가 없으면 새로 지정
인스턴스가 있으면 인스턴스를 리턴해준다.
이를 통해 a와 b는 하나의 인스턴스를 가지는 Singleton을 가진다
a에 새로운 싱글톤을 생성해주면 Singleton.instance는 인스턴스가 있으므로 그냥 바로 return this.instance를 해준다
💛 데이터베이스 연결 모듈
싱글톤 패턴은 데이터베이스 연결 모듈에 많이 쓰인다
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)
위에 설명과 같이 a의 경우 DB.instance가 없으므로 생성해주고, 이를 return
있을경우엔 바로 connect() 메서드로 go , return this.instance를 해준다
이를 통해 하나의 인스턴스를 기반으로 a,b를 생성했다. 귀찮게 여러번 DB 객체를 생성 안해주어도 된다는 장점 !
😜 자바에서의 싱글톤 패턴
public class Main {
public static void main(String[] args) {
mySingleton inst1 = mySingleton.getInstance();
mySingleton inst2 = mySingleton.getInstance();
System.out.println("인스턴스가 같은지 비교");
System.out.println(inst1==inst2);
System.out.println(inst1.getId());
System.out.println(inst1.getName());
inst1.setId(2005);
inst1.setName("The new Manager");
System.out.println(inst2.getId());
System.out.println(inst2.getName());
}
}
class mySingleton {
private int id;
private String name;
private static mySingleton instance = new mySingleton();
private mySingleton() {
this.id = 1001;
this.name = "The Instance Manager";
}
public static mySingleton getInstance() {
if(instance == null) {
instance = new mySingleton();
}
return instance;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
추가적으로 인스턴스를 생성하려는 시도는 if 조건문으로 필터링 된다.
싱글톤의 특징은 런타임 동안 단 한개의 인스턴스만 허용된다.
우리가 인스턴스를 생성할 때 new 클래스명() 이렇게 생성하는데, 어떻게 보면 이것도 new 라는 키워드를 사용하여 마음대로 메모리를 사용할 수 있게 허용된 것이다.
싱글톤은 new 라는 키워드를 사용할 원천 자체를 차단해버리므로, 클래스의 사용자는 훨씬 편하게 사용가능하다.
new는 원래 C언어의 malloc( Memory Allocation ) 메모리 할당 함수에다가 객체 관리 기능을 추가해서 나온 키워드이다.
자바의 new 역시 메모리 할당이기 때문에 싱글톤을 사용하여 원천에 막아 놓는것
자바 코드 해석
1단계 : 생성자를 private으로 만들기
class mySingleton {
private int id;
private String name;
생성자가 하나도 없는 클래스는 컴파일러가 자동으로 디폴트 생성자 코드를 넣어주는데, 컴파일러가 만들어 주는 디폴트 생성자는 public이다. 생성자가 public 이면 외부 클래스에서 인스턴스를 여러 개 생성 가능하다. 따라서,접근 제어자를 private으로 지정해준다. 그러면 생성자가 있기에 컴파일러가 디폴트 생성자를 만들지 않고, 접근 제어자가 private이므로 외부 클래스에서 마음대로 인스턴스를 사용 할 수 없게 된다.
2단계 : 클래스 내부에 static으로 유일한 인스턴스 생성하기
private static mySingleton instance = new mySingleton();
private mySingleton() {
this.id = 1001;
this.name = "The Instance Manager";
}
1단계에서 외부 인스턴스를 생성할 수 없게 하였다. 하지만 우리의 프로그램에서는 인스턴스가 하나만 필요하므로 mySingleton 클래스 내부에서 하나의 인스턴스를 생성하도록 한다. 이 인스턴스가 프로그램 전체에서 사용할 유일한 인스턴스가 된다. private으로 선언하여 외부에서 이 인스턴스를 접근하지 못하게 하여 이 인스턴스에 접근하지 못하도록 제한하여야 인스턴스 오류가 없다.
3단계 : 외부에서 참조할 수 있는 public 만들기
public static mySingleton getInstance() {
if(instance == null) {
instance = new mySingleton();
}
return instance;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
이제 private로 선언한 유일한 인스턴스를 외부에서 사용가능하도록 해야한다. 이를 위해 public 메서드를 만들고 유일하게 생성한 인스턴스를 반환한다. 이 때 인스턴스를 반환하는 메서드는 반드시 static으로 선언해주어야한다. getInstance()에서는 인스턴스 생성과 관계없이 호출 가능해야하기 때문이다.
🦋 mySQL의 singleton
자바와의 연결이다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* DB Connection 하는 Singleton class.
*
* @author Administrator
*
*/
public class DBUtils {
private static final String DBCP_POOLING_DRIVER = "org.apache.commons.dbcp.PoolingDriver";
private static final String MYSQL_JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String JDBC_URI = "jdbc:apache:commons:dbcp:/boardpool";
// 자기 스스로에 대한 인스턴스 생성. (생성자가 private이기 때문.)
private static volatile DBUtils instance = null;
private DBUtils() throws Exception {
try {
Class.forName(MYSQL_JDBC_DRIVER);
Class.forName(DBCP_POOLING_DRIVER);
} catch (Exception e) {
throw new Exception("Failed to create JDBC drivers.", e);
}
}
public static DBUtils getInstance() throws Exception {
if (instance == null) {
// 클래스를 생성하려면 클래스 자체에 대해 접근해야하므로 동기화를 걸어줌.
synchronized (DBUtils.class) {
if (instance == null) {
instance = new DBUtils();
}
}
}
return instance;
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(JDBC_URI);
}
}
🔭 싱글톤 패턴의 단점
싱글톤 패턴은 TDD(Test Driven Development)를 할 때 걸림돌이 된다. TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야한다.
하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴으로 각 테스트마다 독립적인 인스턴스를 만들기 어렵다.
🍒 의존성 주입
싱글톤 패턴은 사용하기가 쉽고 실용적이지만, 모듈 간의 결합을 강하게 만들 수 있다. 이 때 의존성 주입(DI,Dependecy Injection)을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있다.
참고로, 의존성이란 종속성이라고도 하며 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 한다는 것을 의미한다.
before
public class Main {
public static void main(String[] args) {
Controller controller = new Controller();
controller.print();
}
}
class Controller {
private Service service;
public Controller() {
this.service = new Service();
}
public void print() {
System.out.println(service.message());
}
}
class Service {
public String message() {
return "Hello World!";
}
}
// 출처: https://7942yongdae.tistory.com/177 [프로그래머 YD:티스토리]
코드로 본다면 객체는 내가 만들게, 넌 사용만 해 라는 느낌이다. 위에 코드는 Controller 객체가 Service 객체를 거쳐서 Hello world를 출력하는 코드이다. 두 객체에는 직접적 연관이 있다. 만약 Service 값이 바뀐다면 출력되는 코드도 달라지기 때문이다. Controller 객체는 Service 객체를 알고 직접 만들어서 메시지를 사용한다.
after
public class Main {
public static void main(String[] args) {
Controller controller = new Controller(new MessageService());
controller.print();
}
}
interface IService {
String message();
}
class Controller {
private IService service;
public Controller(IService service) {
this.service = service;
}
public void print() {
System.out.println(service.message());
}
}
class MessageService implements IService {
public String message() {
return "Hello World!";
}
}
// 출처: https://7942yongdae.tistory.com/177 [프로그래머 YD:티스토리]
인터페이스로 추상화를 주입 시켰다. 이제는 Controller가 MessageService를 거치지 않고 인터페이스인 IService를 이용하여 메시지를 출력한다.
이전 코드와 달리 Controller 가 MessageService가 무엇인지 몰라도 IService를 이용하여 객체를 출력할 수 있다. 정말 “ 객체(MessageService) 는 내가(Main) 만들게 넌 사용(IService) 해” 라는 상황이 되었다.
⛳️ 의존성 주입의 단점
모듈들이 더욱 분리 되므로 클래스 수가 늘어나, 복잡성이 증가할 수 있으며 약간의 런타임 페널티가 생기기도 한다.
😁 의존성 주입 원칙
상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 말아야한다. 또한, 둘다 추상화에 의존해야 하며, 이 때 추상화는 세부 사항에 의존하지 말아야한다.
출처 : 면접을 위한 CS 전공지식 노트
댓글남기기