class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this
}
return Singleton.instance
}
getInstance() {
return this
}
}
const a = new Singleton()
const b = new Singleton()
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.println(a.hashCode());
System.out.println(b.hashCode());
if (a == b){
System.out.println(true);
}
}
}
/*
705927765
705927765
true
1. 클래스안에 클래스(Holder), static이며 중첩된 클래스인 singleInstanceHolder를
기반으로 객체를 선언했기 때문에 한 번만 로드되므로 싱글톤 클래스의 인스턴스는
애플리케이션 당 하나만 존재하며
클래스가 두 번 로드되지 않기 때문에 두 스레드가 동일한 JVM에서 2개의 인스턴스를 생성할
수 없습니다.
그렇기 때문에 동기화, 즉 synchronized를 신경쓰지 않아도 됩니다.
2. final 키워드를 통해서 read only 즉, 다시 값이 할당되지 않도록 했습니다.
3. 중첩클래스 Holder로 만들었기 때문에 싱글톤 클래스가 로드될 때 클래스가 메모리에
로드되지 않고
어떠한 모듈에서 getInstance()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게
됩니다.
*/
5. 단점
// npm install -g mocha
// mocha single1.js
const assert = require('assert');
const a = [1, 2, 3]
describe('Array', function () {
describe('#indexOf()', function () {
it('should return -1 when the value is not present', function () {
assert.equal(a.indexOf(4), -1);
a[0] = 4;
});
});
describe('#indexOf()', function () {
it('should return -1 when the value is not present', function () {
assert.equal(a.indexOf(4), -1);
});
});
});
싱글톤 패턴 생성 여부를 확인하고 싱글톤이 있으면 만들어진 인스턴스 반환/없으면 새로 만듦
멀티스레드 환경에서 싱글톤 인스턴스를 2개 이상 만들 수 있음
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// thread.java
public class YunhaSync {
private static String yunha = "오르트구름";
public static void main(String[] agrs) {
YunhaSync a = new YunhaSync();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
a.say("사건의 지평선");
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
a.say("오르트 구름");
}
}).start();
}
public void say(String song) {
yunha = song;
try {
long sleep = (long) (Math.random() * 100);
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!yunha.equals(song)) {
System.out.println(song + " | " + yunha);
}
}
}
// thread2.java
public class YunhaSync {
private static String yunha = "오르트구름";
public static void main(String[] agrs) {
YunhaSync a = new YunhaSync();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
a.say("사건의 지평선");
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
a.say("오르트 구름");
}
}).start();
}
public synchronized void say(String song) {
yunha = song;
try {
long sleep = (long) (Math.random() * 100);
Thread.sleep(sleep);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!yunha.equals(song)) {
System.out.println(song + " | " + yunha);
}
}
}
2) synchronized
인스턴스를 반환하기 전까지 격리 공간에 놓기 위해 synchronized 키워드로 잠금 가능
최초로 접근한 스레드가 해당 메서드 호출시에 다른 스레드가 접근하지 못하도록 잠금(lock) 걸기
getInstance() 메서드를 호출할 때마다 lock이 걸려 성능 저하
인스턴스 만들어졌는데도, getInstance() 호출 가능한 문제 발생
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3) 정적 멤버 (static)
정적(static) 멤버나 블록은 런타임이 아니라 최초에 JVM이 클래스 로딩때 모든 클래스를 로드할 때 미리 인스턴스를 생성하는데 이용하는 방법
클래스 로딩과 동시에 싱글톤 인스턴스 생성
모듈들이 싱글톤 인스턴스를 요청할 때 그냥 만들어진 인스턴스를 반환함
단점 : 불필요한 자원낭비 (싱글톤 인스턴스가 필요없는 경우도 무조건 싱글톤 클래스를 호출해 인스턴스를 만들어야 함)
public class Singleton {
private final static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
4) 정적 블록
정적 블록을 사용해도 됨
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
5) 정적 멤버와 Lazy Holder(중첩 클래스)
추천
singleInstanceHolder라는 내부 클래스를 하나 더 만듦
Singleton 클래스가 최초에 로딩되더라도 함께 초기화 되지 않고, getInstance()가 호출될 때 singleInstanceHolder클래스가 로딩되어 인스턴스 생성
class Singleton {
private static class singleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return singleInstanceHolder.INSTANCE;
}
}
6) 이중 확인 잠금 (Double Checked Locking, DCL)
인스턴스 생성 여부를 싱글톤 패턴 잠금 전에 1번, 객체를 생성하기 전에 1번 체크
총 2버 체크하여 인스턴스가 존재하지 않을 때만 잠금을 걸 수 있어 문제점 해결 가능
public class Singleton {
private volatile Singleton instance;
private Singleton() {
}
public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile 키워드
java에서 스레드 2개 열리면 변수를 메인 메모리(RAM)이 아닌 캐시 메모리에소 각각 캐시메모리 기반으로 가져옴
main memory 기반으로 저장하고 읽어옴 (변수 값 불일치 문제 해결)
public class Test {
boolean flag = true;
public void test() {
new Thread(()->{
int cnt = 0;
while (flag) {
cnt++;
}
System.out.println("Thread1 finished\n");
}
).start();
new Thread(()-> {
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
System.out.println("flag to false");
flag = false;
}
).start();
}
public static void main(String[] args) {
new Test().test();
}
}
7) enum 인스턴스
추천
기본적으로 스레드세이프(thread safe)한 점이 보장되어 enum 통해 생성
public enum SingletonEnum {
INSTANCE;
public void oortCloud() {
}
}
📕 팩토리 패턴
1. 개념
상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대 결정, 하위 클래스에서 객체 생성에 관한 구체적인 내용 결정
const mp = new Map()
mp.set('a', 1)
mp.set('b', 2)
mp.set('cccc', 3)
const st = new Set()
st.add(1)
st.add(2)
st.add(3)
const a = []
for(let i = 0; i < 10; i++)a.push(i)
for(let aa of a) console.log(aa)
for(let a of mp) console.log(a)
for(let a of st) console.log(a)
/*
0
1
2
3
4
5
6
7
8
9
[ 'a', 1 ]
[ 'b', 2 ]
[ 'cccc', 3 ]
1
2
3
*/
📕 DI와 DIP
1. DI (Dependecy Injection, 의존성 주입)
메인 모듈이직접다른 하위 모듈에 대한 의존성을 주지X
중간에 의존성 주입자(dependency injector)가 이 부분을 가로채 메인 모듈이간접적으로 의존성 주입
메인 모듈과 하위 모듈간의 의존성을 조금 더 느슨하게 만듦 ➡️ 쉽게 교체 가능한 구조의 모듈
2. 의존 의미
A가 B에 의존한다 = B가 변하면 A에 영향을 미치는 관계 (A ➡️ B)
import java.util.*;
class B {
public void go() {
System.out.println("B의 go()함수");
}
}
class A {
public void go() {
new B().go();
}
}
public class main{
public static void main(String args[]) {
new A().go();
}
}
// B의 go()함수
3. DI 적용 예시
1) Project의 DI를 적용하지 않은 사례
import java.util.*;
class BackendDeveloper {
public void writeJava() {
System.out.println("자바가 좋아 인터네셔널~");
}
}
class FrontEndDeveloper {
public void writeJavascript() {
System.out.println("자바스크립트가 좋아 인터네셔널~");
}
}
public class Project {
private final BackendDeveloper backendDeveloper;
private final FrontEndDeveloper frontEndDeveloper;
public Project(BackendDeveloper backendDeveloper, FrontEndDeveloper frontEndDeveloper) {
this.backendDeveloper = backendDeveloper;
this.frontEndDeveloper = frontEndDeveloper;
}
public void implement() {
backendDeveloper.writeJava();
frontEndDeveloper.writeJavascript();
}
public static void main(String args[]) {
Project a = new Project(new BackendDeveloper(), new FrontEndDeveloper());
a.implement();
}
}
2) Project의 DI를 적용한 사례
interface Developer {
void develop();
}
class BackendDeveloper implements Developer {
@Override
public void develop() {
writeJava();
}
public void writeJava() {
System.out.println("자바가 좋아~ 새삥새삥");
}
}
class FrontendDeveloper implements Developer {
@Override
public void develop() {
writeJavascript();
}
public void writeJavascript() {
System.out.println("자바스크립트가 좋아~ 새삥새삥");
}
}
public class Project {
private final List<Developer> developers;
public Project(List<Developer> developers) {
this.developers = developers;
}
public void implement() {
developers.forEach(Developer::develop);
}
public static void main(String args[]) {
List<Developer> dev = new ArrayList<>();
dev.add(new BackendDeveloper());
dev.add(new FrontendDeveloper());
Project a = new Project(dev);
a.implement();
}
}
4. 의존관계역전원칙 (DIP, Dependency Inversion Principle)
1) 규칙
상위 모듈은 하위 모듈에 의존해서는 안됨 (둘 다 추상화 의존해야 함)
추상화는 세부사항에 의존해서는 안됨 (세부 사항은 추상화에 따라 달라져야 함)
2) 장점
1️⃣ : 외부에서 모듈을 생성하여dev.add(new BackendDeveloper())추가한 구조 (모듈 쉽게 교체 가능)
2️⃣ : 단위 테스팅과 마이그레이션 쉬워짐
3️⃣ : 애플리케이션 의존성 방향이 좀 더 일관되어 코드 추론하기 쉬움
3) 단점
1️⃣ : 모듈이 더 생겨 복잡도 증가
2️⃣ : 종속성 주입 자체가 런타임(컴파일X)때 일어나서 컴파일 할 때 종속성 주입 관련 에러 잡기 어려움
📖 참고 📖 마이그레이션
데이터나 소프트웨어를 다른 운영환경으로 옮기는 것
DB이동, 데이터 이동 등
📕 전략 패턴
1. 개념
전략(캡슐화된 알고리즘)을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 디자인 패턴
보통 서비스 앞단에 프록시 서버로 cloudflare을 둬서 불필요한/공격적인 트래픽 막음
function createReactiveObject(target, callback) {
const proxy = new Proxy(target, {
set(obj, prop, value){
if(value !== obj[prop]){
const prev = obj[prop]
obj[prop] = value
callback(`${prop}가 [${prev}] >> [${value}] 로 변경되었습니다`)
}
return true
}
})
return proxy
}
const a = {
"형규" : "솔로"
}
const b = createReactiveObject(a, console.log)
b.형규 = "솔로"
b.형규 = "커플"
// 형규가 [솔로] >> [커플] 로 변경되었습니다
📕 MVC 패턴, MVP 패턴, MVVM 패턴
1. MVC 패턴
1) 개념
Model(모델), View(뷰), Controller(컨트롤러)로 이루어진 디자인 패턴
🗒️ 예시 : Spring WEB MVC (MVC 패턴을 반영한 프레임워크)
2) 모델
애플리케이션의 데이터베이스, 상수, 변수 등 의미
뷰에서 데이터를 생성하거나 수정할 때 컨트롤러를 통해 모델이 생성/업데이트
🗒️ 예시 : 사용자가 네모박스에 글자를 적을 경우
모델은 네모박스의 크기정보, 글자내용, 글자의 위치, 글자의 포맷 정보
3) 뷰
inputbox, checkoutbox, textarea등 사용자 인터페이스 요소
모델을 기반으로 사용자가 볼 수 있는 화면
모델이 가지고 있는 정보를 따로 저장하지 않고, 변경이 일어나면 컨트롤러에 이를 전달함
4) 컨트롤러
하나 이상의 모델-뷰를 잇는 다리 역할
이벤트 등 메인 로직 담당
모델과 뷰의 생명주기 관리
모델이나 뷰의 변경 통지를 받으면 해석하여 각각 구성 요소에 해당 내용을 알려줌
5) 장점
1️⃣ : 애플리케이션의 구성 요소를 3가지 역할로 구분하여 개발 프로세스에서 각 구성요소에만 집중 가능
2️⃣ : 재사용성과 확장성 용이
6) 단점
1️⃣ : 애플리케이션이 복잡해질수록 모델-뷰의 관계가 복잡해짐
7) Spring의 MVC패턴 적용
Spring은 자바 기반으로 애플리케이션 개발을 할 때 많은 기능을 제공하는 프레임워크
🗒️ 예시 : Spring Web MVC - 디스패처 서블릿 요청 처리과정 - 1️⃣ : 클라이언트가 요청을 했을 때 가장 먼저 디스페처 서블릿이 받음 (프론트 컨트롤러 역할) ➡️ 어떤 컨트롤러가 처리하도록 결정 (@requestmapping 참고) - 2️⃣ : 하나 이상의 handler mapping 참고해서 적절한 컨트롤러 설정 ➡️ 이후 컨트롤러로 요청 보냄 - 3️⃣ : 컨트롤러는 데이터베이스에 접근하여 데이터 가져오는 등 비즈니스 로직 수행 - 4️⃣ : 사용자에게 전달해야할 정보인 모델 생성 - 5️⃣ : 뷰를 구현하기 위한 view resolver 참고 - 6️⃣ : 해당 정보를 기반으로 뷰 렌더링함 - 7️⃣ : 응답 데이터 보냄
@RequestMapping(value = "/ex/foos", method = POST)
@ResponseBody
public String postFoos() {
return "Post some Foos";
}
2. MVP 패턴
1) 개념
C가 P(presenter)로 교체된 패턴
V와 P는 1:1 관계
MVC보다 더 강한 결합을 지닌 디자인 패턴
3. MVVM 패턴
1) 개념
C가 VM(view model)로 바뀐 패턴
VM은 뷰를 추상화한 계층
VM:V = 1:N 관계
🗒️ 예시 : Vue.js (MVVM 패턴 가진 프레임워크)
2) VM 구성
커맨드 (여러 요소에 대한 처리를 하나의 액션으로 처리할 수 있는 기법)
데이터바인딩 (화면에 보이는 데이터와 브라우저 상의 메모리 데이터를 일치시키는 방법)
📕 flux 패턴
1. 개념
단방향으로 데이터 흐름을 관리하는 디자인패턴
뷰에서 일어난 것이 모델에도 영향을 미치는 로직 등 문제 해결하기 위해 데이터를 일관성 있게 뷰에 공유
장점
데이터 일관성의 증대
버그를 찾기 쉬워짐
단위테스팅 쉬워짐
2. 구조
1) Action
사용자의 이벤트 담당
마우스 클릭, 글 쓰기 등 의미
해당 이벤트에 관한 객체를 만들어내 dispatcher에게 전달
2) Dispatcher
들어오는 Action 객체 정보를 기반으로 어떠한 "행위" 할지를 결정
보통 action객체의 type을 기반으로 미리 만들어 놓은 로직 수행 ➡️ store에 전달
3) Store
애플리케이션 상태를 관리하고 저장하는 계층
도메인의 상태, 사용자의 인터페이스 등 상태를 모두 저장
4) View
데이터를 기반으로 표출이 되는 사용자인터페이스
3. flux 패턴이 적용된 redux
const initialState = {
visibilityFilter: 'SHOW_ALL',
todos: []
}
function appReducer(state = initialState, action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER': {
return Object.assign({}, state, {
visibilityFilter: action.filter
})
}
case 'ADD_TODO': {
return Object.assign({}, state, {
todos: state.todos.concat({
id: action.id,
text: action.text,
completed: false
})
})
}
case 'TOGGLE_TODO': {
return Object.assign({}, state, {
todos: state.todos.map(todo => {
if (todo.id !== action.id) {
return todo
}
return Object.assign({}, todo, {
completed: !todo.completed
})
})
})
}
case 'EDIT_TODO': {
return Object.assign({}, state, {
todos: state.todos.map(todo => {
if (todo.id !== action.id) {
return todo
}
return Object.assign({}, todo, {
text: action.text
})
})
})
}
default:
return state
}
}
📖 참고 📖 전략패턴과 의존성주입의 차이
✏️ 전략패턴
어떠한 동일한 행동 계약을 기반으로 다양한 구현이 명시되어 있는 인터페이스를 만드는 것
✏️ 의존성 주입
단지 일부 동작을 구현하고 의존성을 주입하기만 하는 패턴
📖 참고 📖 컨텍스트
✏️ 의미
어떤 종류의 상태, 환경을 캡슐화한 것
작업이 중단되고, 나중에 같은 지점에서 계속 될 수 있도록 저장하는 최소 데이터 집합 (ex. context switching)
✏️ 구성
context
contextual information
HTTP 요청을 하는 context에서 HTTP Header는 contextual information이라고 할 수 있음
✏️ 예시
context API (react.js에서 context API를 통해 전역적으로 static contextType을 통해 상태 관리)