STUDY - 디자인패턴

수현·2023년 9월 10일
0

Lesson

목록 보기
8/9

📒 디자인패턴

📕 디자인패턴

1. 개념

  • 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계를 이용하여 해결하기 위한 규약
  • 라이브러리나 프레임워크를 만드는데 사용

2. 종류

  • 1) 생성패턴
    • 객체 생성 방법이 들어간 디자인패턴
    • 🗒️ 예시 : 싱글톤, 팩토리, 추상팩토리, 빌더, 프로토타입패턴
  • 2) 구조패턴
    • 객체, 클래스 등으로 큰 구조를 만들 때 유연하고 효율적으로 만드는 방법이 들어간 디자인 패턴
    • 🗒️ 예시 : 프록시, 어댑터, 브리지, 복합체, 데코레이터, 퍼사드, 플라이웨이트패턴
  • 3) 행동패턴
    • 객체나 클래스 간의 알고리즘, 책임 할당에 관한 디자인패턴
    • 🗒️ 예시 : 이터레이터, 옵저버, 전략, 책임연쇄, 커맨드, 중재자, 메멘토, 상태, 템플릿메서드, 비지터패턴

📕 라이브러리와 프레임워크 차이

1. 라이브러리

  • 1) 개념

    • 공통으로 사용될 수 있는 틍정한 기능을 모듈화
    • 폴더명, 파일명 등 규칙 없음
    • 프레임워크에 비해 자유로움
    • 🗒️ 예시 : axios
  • 2) axios 라이브러리 사례

    • HTTP 요청 처리를 도와줌
    • 일정 규칙을 따라야 하나 프레임워크보다 자유롭고 다른 라이브러리에 넣을 수 있음
    • axios로 get 요청을 하려면 .get() 그리고 그 이후에 콜백함수 처리는 .then(callback) 등으로 해야하는 일정 규칙
    • 핸들러 함수를 넣어줘서 HTTP 요청을 하고 그 요청에 따른 응답에 대한 로직 구현
    axios.get('/user?ID=12345')
    		.then(function (response) {
    		// handle success
    		console.log(response);
    		})
    		.catch(function (error) {
    			// handle error
    			console.log(error);
    		})
    		.then(function () {
    			// always executed
    		});

2. 프레임워크

  • 1) 개념
    • 공통으로 사용될 수 있는 특정한 기능을 모듈화
    • 폴더명, 파일명 등 규칙이 있음
    • 라이브러리에 비해 좀 더 엄격함
    • 🗒️ 예시 : Vue.js, Django
  • 2) Vue.js 프레임워크 사례
    • 프론트엔드에서 많이 쓰이는 프레임워크
    • 규칙이 있고, 라이브러리보다 더 많은 기능 제공
      <script>
      	export default {
      		data() {
      			return {
      				count: 0
      			}
      		}
      	}
      </script>
      <template>
      	<button @click="count++">Count is: {{ count }}</button> 
      // count 변수 ++하기 위해서는 date(){ 메서드 안에 변수 선언해야 하는 규칙
      </template>
      <style scoped> // scoped라는 것을 통해 해당 컴포넌트기반에서만 작동되는 style
      	button {
      		font-weight: bold;
      	}
      </style>

📖 참고 📖 디자인 패턴

  • ✏️ 개념
    • 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호관계를 이용하여 해결할 수 있도록 규약 형태로 만들어 놓은 것
    • 라이브러리, 프레임워크가 어떠한 디자인패턴을 기반으로 만들어짐
    • 🗒️ 예시 : 전략패턴이 들어간 Passport.js 라이브러리
  • ✏️ 생성 패턴
    • 객체 생성 방법이 들어간 디자인 패턴
    • 싱글톤, 팩토리, 추상팩토리, 빌더, 프로토타입 패턴이 있음
  • ✏️ 구조 패턴
    • 객체, 클래스 등으로 큰 구조를 만들 때 유연하고 효율적으로 만드는 방법이 들어간 디자인 패턴
    • 프록시, 어댑터, 브리지, 복합체, 데코레이터, 퍼사드, 플라이웨이트페턴
  • ✏️ 행동 패턴
    • 객체나 클래스 간의 알고리즘, 책임 할당에 관한 디자인 패턴
    • 이터레이터, 옵저버, 전략, 책임연쇄, 커맨드, 중재자, 메멘토, 상태, 템플릿메서드, 비지터 패턴
  • ✏️ flux 패턴, MVC 패턴, MVVM 패턴

📕 싱글톤 패턴

1. 개념

  • 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
  • 인스턴스 생성에 많은 코스트가 드는 데이터베이스 연결모듈에 사용

2. 특징

  • 장점
    • 인스턴스 생성을 효율적으로 함
  • 단점
    • 의존성이 높음
    • TDD를 할 때 불편함

📖 참고 📖 TDD (Test Driven Development, 테스트 주도 개발)

  • 반복 테스트를 이용한 소프트웨어 방법론
  • 작은 단위의 테스트 케이스를 작성하고, 이를 통과하는 코드를 추가하는 단계를 반복하여 구현
  • 애자일 방법론 중 하나인 eXtream Programming(XP)의 ‘Test-First’ 개념에 기반을 둔 단순한 설계

3. 싱클톤이 아닌 클래스

class Rectangle {
	constructor(height, width) {
		this.height = height;
		this.width = width;
	}
}
const a = new Rectangle(1, 2)
const b = new Rectangle(1, 2)
console.log(a === b) // false

4. 실습

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
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
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);
});
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);
		});
	});
});

6. 싱글톤 구현 7가지 방법

  • 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. 개념

  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대 결정, 하위 클래스에서 객체 생성에 관한 구체적인 내용 결정
  • 장점
    • 상위 클래스에서 객체 생성방식에 대해 알 필요가 없어져 유연성 가짐
    • 객체 생성 로직은 하위클래스에서만 관리되어 유지보수성 증가

2. 실습

class CoffeeFactory {
	static createCoffee(type) {
		const factory = factoryList[type]
		return factory.createCoffee()
	}
}
class Latte {
	constructor() {
		this.name = "latte"
	}
}
class Espresso {
	constructor() {
		this.name = "Espresso"
	}
}
class LatteFactory extends CoffeeFactory{
	static createCoffee() {
		return new Latte()
	}
}
class EspressoFactory extends CoffeeFactory{
	static createCoffee() {
		return new Espresso()
	}
}
const factoryList = { LatteFactory, EspressoFactory }
	const main = () => {
	// 라떼 커피를 주문한다.
	const coffee = CoffeeFactory.createCoffee("LatteFactory")
	// 커피 이름을 부른다.
	console.log(coffee.name) // latte
}
main()
  • java
enum CoffeeType {
	LATTE,
	ESPRESSO
}
abstract class Coffee {
	protected String name;
	public String getName() {
		return name;
	}
}
class Latte extends Coffee {
	public Latte() {
		name = "latte";
	}
}
class Espresso extends Coffee {
	public Espresso() {
		name = "Espresso";
	}
}
class CoffeeFactory {
	public static Coffee createCoffee(CoffeeType type) {
		switch (type) {
			case LATTE:
				return new Latte();
			case ESPRESSO:
				return new Espresso();
			default:
				throw new IllegalArgumentException("Invalid coffee type: " + type);
		}
	}
}
public class Main {
	public static void main(String[] args) {
		Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
		System.out.println(coffee.getName()); // latte
	}
}

📘 이터레이터 패턴

1. 개념

  • 이터레이터(iterator)를 사용하여 컨테이너 요소들에 접근하는 디자인 패턴
  • 장점
    • 각기 다른 자료구조들을 똑같은 인터페이스로 순휘를 쉽게 할 수 있음

📖 참고 📖 컨테이너

  • 동일한 요소들을 담아놓은 집합
  • 🗒️ 예시 : 배열, 맵

2. 실습

  • 자바스크립트의 for of 예시
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. 개념

  • 전략(캡슐화된 알고리즘)을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 디자인 패턴

2. 실습

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
interface PaymentStrategy {
	public void pay(int amount);
}
class KAKAOCardStrategy implements PaymentStrategy {
	private String name;
	private String cardNumber;
	private String cvv;
	private String dateOfExpiry;
	public KAKAOCardStrategy(String nm, String ccNum, String cvv, String expiryDate){
		this.name=nm;
		this.cardNumber=ccNum;
		this.cvv=cvv;
		this.dateOfExpiry=expiryDate;
	}
	@Override
	public void pay(int amount) {
		System.out.println(amount +" paid using KAKAOCard.");
	}
}
class LUNACardStrategy implements PaymentStrategy {
	private String emailId;
	private String password;
	public LUNACardStrategy(String email, String pwd){
		this.emailId=email;
		this.password=pwd;
	}
	@Override
	public void pay(int amount) {
		System.out.println(amount + " paid using LUNACard.");
	}
}
class Item {
	private String name;
	private int price;
	public Item(String name, int cost){
		this.name=name;
		this.price=cost;
	}
	public String getName() {
		return name;
	}
	public int getPrice() {
		return price;
	}
}
class ShoppingCart {
	List<Item> items;
	public ShoppingCart(){
		this.items=new ArrayList<Item>();
	}
	public void addItem(Item item){
		this.items.add(item);
	}
	public void removeItem(Item item){
		this.items.remove(item);
	}
	public int calculateTotal(){
		int sum = 0;
		for(Item item : items){
			sum += item.getPrice();
		}
		return sum;
	}
	public void pay(PaymentStrategy paymentMethod){
		int amount = calculateTotal();
		paymentMethod.pay(amount);
	}
}
public class HelloWorld{
	public static void main(String []args){
		ShoppingCart cart = new ShoppingCart();
		Item A = new Item("kundolA",100);
		Item B = new Item("kundolB",300);
		cart.addItem(A);
		cart.addItem(B);
		// pay by LUNACard
		cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
		// pay by KAKAOBank
		cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
	}
}
/*
400 paid using LUNACard.
400 paid using KAKAOCard.
*/
var passport = require('passport'), LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
	function(username, password, done) {
		User.findOne({ username: username }, function (err, user)
		{
			if (err) { return done(err); }
		if (!user) {
			return done(null, false, { message: 'Incorrect username.' });
		}
		if (!user.validPassword(password)) {
			return done(null, false, { message: 'Incorrect password.' });
		}
		return done(null, user);
		});
	}
));

📘 옵저버 패턴

1. 개념

  • 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려 주는 디자인 패턴
  • 🗒️ 예시 : 트위터의 메인 로직, MVC 패턴에 적용

2. 실습

import java.util.ArrayList;
import java.util.List;
interface Subject {
	public void register(Observer obj);
	public void unregister(Observer obj);
	public void notifyObservers();
	public Object getUpdate(Observer obj);
}
interface Observer {
	public void update();
}
class Topic implements Subject {
	private List<Observer> observers;
	private String message;
	public Topic() {
		this.observers = new ArrayList<>();
		this.message = "";
	}
	@Override
	public void register(Observer obj) {
		if (!observers.contains(obj)) observers.add(obj);
	}
	@Override
	public void unregister(Observer obj) {
		observers.remove(obj);
	}
	@Override
	public void notifyObservers() {
		this.observers.forEach(Observer::update);
	}
	@Override
	public Object getUpdate(Observer obj) {
		return this.message;
	}
	public void postMessage(String msg) {
		System.out.println("Message sended to Topic: " + msg);
		this.message = msg;
		notifyObservers();
	}
}
class TopicSubscriber implements Observer {
	private String name;
	private Subject topic;
	public TopicSubscriber(String name, Subject topic) {
		this.name = name;
		this.topic = topic;
	}
	@Override
	public void update() {
		String msg = (String) topic.getUpdate(this);
		System.out.println(name + ":: got message >> " + msg);
	}
}
public class HelloWorld {
	public static void main(String[] args) {
		Topic topic = new Topic();
		Observer a = new TopicSubscriber("a", topic);
		Observer b = new TopicSubscriber("b", topic);
		Observer c = new TopicSubscriber("c", topic);
		topic.register(a);
		topic.register(b);
		topic.register(c);
		topic.postMessage("amumu is op champion!!");
	}
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/

📕 프록시 패턴

1. 개념

  • 객체가 어떤 객체에 접근하기 전, 그 접근에 대한 흐름을 가로채서 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 디자인패턴
  • 🗒️ 예시 : 프록시서버
    • 서버 앞단에 두어 캐싱, 로깅 등 활용하는 프록시서버

2. 실습

  • 보통 서비스 앞단에 프록시 서버로 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을 통해 상태 관리)
      const ThemeContext = React.createContext('light');
      class App extends React.Component {
      	render() {
      		return (
      			<ThemeContext.Provider value="dark">
      			<Toolbar />
      			</ThemeContext.Provider>
      		);
      	}
      }
      function Toolbar() {
      	return (
      		<div>
      		<ThemedButton />
      		</div>
      	);
      }
      class ThemedButton extends React.Component {
      	static contextType = ThemeContext;
      	render() {
      		return <Button theme={this.context} />;
      	}
      }
profile
Notion으로 이동 (https://24tngus.notion.site/3a6883f0f47041fe8045ef330a147da3?v=973a0b5ec78a4462bac8010e3b4cd5c0&pvs=4)

0개의 댓글