자바와 자바스크립트를 모두 사용하여 설명한다.
사용 교재) 면접을 위한 CS 전공지식 노트 (주홍철, 2022)
🔷 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것
💡 라이브러리: 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것, 폴더명이나 파일명에 대한 규칙이 없고 프레임워크에 비해 자유롭다. 배운 것 중에는 React가 대표적인 라이브러리.
💡 프레임워크: 공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것을 의미한다. 폴더명, 파일명데 대한 규칙이 있고 라이브러리에 비해 더 엄격하다. 배운 것 중에는 Spring이 대표적인 프레임워크.
🔷 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
사용처: 데이터베이스 연결 모듈
장점: 인스턴스를 생성할 때 드는 비용이 줄어든다.
단점: 의존성
이 높아진다.
❓ 의존성이 왜 위험한가?
의존 관계에서 하나가 변경되면 그것을 의존하는 다른 것들에게까지 영향을 끼친다. 특히 순환 의존의 경우에는 자기 자신까지도 영향이 돌아올 수 있어 위험성이 높다. 또한 의존성이 높으면 모듈을 따로 떼어내어 테스트하기 어려워지며 모듈의 재사용성을 떨어뜨린다. (SOLID)
의존성 주입
을 이용할 수 있다.💡
의존성 주입
메인 모듈이 직접 다른 하위 모듈에 의존성을 주는게 아니라 의존성 주입자(dependency injector)가 대신 주게 만들어 간접적으로 의존성을 주입하는 방식, 이로써 모듈 간의 결합을 느슨하게 만들며 이를 '디커플링 되다' 라고 한다.
new object
로 객체를 생성하면 다른 어떤 객체와도 같지 않기 때문에 이 자체만으로 싱글톤 패턴을 구현할 수 있다.const obj = {
num: 1
}
const objOther = {
num: 1
}
console.log(obj===objOther); // 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);
// 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(err);
});
});
};
🔷 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴
const num = new Object(1);
const str = new Object('팩토리');
num.constructor.name; // Number
str.constructor.name; // String
인스턴스 생성 메서드를 정적 메서드로 만들면 클래스의 인스턴스 없이 호출이 가능하여 메모리 절약이 가능하고, 개별 인스턴스에 묶이지 않으며 클래스 내의 함수를 정의할 수 있다.
거기에 의존성 주입까지 함께 응용하면 이렇다.
class Sonata {
constructor() {
this.name = "sonata"
}
}
class Porsche {
constructor() {
this.name = "porsche"
}
}
class Genesis {
constructor() {
this.name = "genesis"
}
}
class SonataFactory {
static createCar() {
return new Sonata()
}
}
class PorscheFactory {
static createCar() {
return new Porsche()
}
}
class GenesisFactory {
static createCar() {
return new Genesis()
}
}
const factoryList = {SonataFactory, PorscheFactory, GenesisFactory}
class CarFactory {
static createCar(type) {
const factory = factoryList[type]
return factory.createCar()
}
}
const main = () => {
const car = CarFactory.createCar("PorscheFactory")
console.log(car.name)
}
main()
abstract class Car {
public abstract int getPrice();
@Override
public String toString() {
return "이 차는 " + this.getPrice() + "원입니다.";
}
}
class CarFactory {
public static Car getCar(String type, int price) {
if("Porsche".equalsIgnoreCase(type)) return new Porsche(price);
else if ("Sonata".equalsIgnoreCase(type)) return new Sonata(price);
else if ("Genesis".equalsIgnoreCase(type)) return new Genesis(price);
else return new DefaultCar();
}
}
class DefaultCar extends Car {
private int price;
public DefaultCar() {
this.price = -1;
}
@Override
public int getPrice() {
return this.price;
}
}
class Porsche extends Car {
private int price;
public Porsche(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
class Sonata extends Car {
private int price;
public Sonata(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
class Genesis extends Car {
private int price;
public Genesis(int price) {
this.price = price;
}
@Override
public int getPrice() {
return this.price;
}
}
public class Main {
public static void main(String[] args) {
Car porsche = CarFactory.getCar("Porsche", 178000000);
Car sonata = CarFactory.getCar("Sonata", 80000000);
Car genesis = CarFactory.getCar("Genesis", 102000000);
System.out.println("포르쉐 공장: " + porsche);
System.out.println("소나타 공장: " + sonata);
System.out.println("제네시스 공장: " + genesis);
}
}
저기서 if문을 쓰지 않고 Enum
혹은 Map
을 이용하여 매핑해서 로직을 구성할 수도 있다.
💡
Enum
상수의 집합을 정의할 때 사용되는 타입, 상수나 메서드 등을 집어넣어서 관리하며 코드를 리팩터링할 때 해당 집합에 관한 로직 수정 시 이 부분만 수정하면 되므로 코드 리팩터링 시 강점이 생긴다.
🔷 정책 패턴(policy pattern)이라고도 하며, 객체의 행위를 바꾸고 싶은 경우에 직접 수정하지 않고 전략이라고 부르는 캡슐화한 알고리즘
을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴
💡 컨텍스트
상황, 맥락, 문맥을 의미하며 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보를 말한다.
passport
가 대표적인 전략 패턴 활용 사례이다. 서비스 내의 회원 가입된 아이디와 비밀번호를 기반으로 인증하는 LocalStrategy 전략과 네이버와 같은 다른 서비스를 기반으로 인증하는 OAuth 전략 등을 지원한다.var passport = require('passport'),
LocalStrategy = require('passport-local').strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
username.findOne({ username: username }, function (err, user) {
if(err) {return done(err);}
if(!user) {
return done(null, false, {message: '잘못된 이름입니다.'});
}
if(!user.validPassword(password)) {
return done(null, false, {message: '잘못된 비밀번호입니다.'});
}
return donw(null, user);
});
}
));
import java.util.ArrayList;
import java.util.List;
interface PaymentStrategy {
public void pay(int amount);
}
class SamsungCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public SamsungCardStrategy(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 + "원이 삼성카드로 결제되었습니다.");
}
}
class KAKAOPayStrategy implements PaymentStrategy {
private String userId;
private String password;
public KAKAOPayStrategy(String id, String pwd) {
this.userId = id;
this.password = pwd;
}
@Override
public void pay(int amount) {
System.out.println(amount + "원이 카카오페이로 결제되었습니다.");
}
}
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 calculate() {
int sum = 0;
for(Item item : items) {
sum += item.getPrice();
}
return sum;
}
public void pay(PaymentStrategy paymentMethod) {
int amount = calculate();
paymentMethod.pay(amount);
}
}
public class Shopping {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item A = new Item("팔도비빔면", 4000);
Item B = new Item("바닐라 샴푸", 8500);
Item C = new Item("서울우유", 2400);
cart.addItem(A);
cart.addItem(B);
cart.addItem(C);
cart.pay(new SamsungCardStrategy("Park younggue", "77777777", "123", "0525"));
cart.pay(new KAKAOPayStrategy("dudrb5260@naver.com", "777777"));
}
}
🔷 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴
💡 옵저버
객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 '추가 변화 사항'이 생기는 객체들
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 Shopping {
public static void main(String[] args) {
Topic topic = new Topic();
Observer a = new TopicSubscriber("박영규", topic);
Observer b = new TopicSubscriber("조강원", topic);
Observer c = new TopicSubscriber("이승열", topic);
topic.register(a);
topic.register(b);
topic.register(c);
topic.postMessage("아무무는 쓰레기 챔피언이 맞다.");
}
}
프록시 객체
를 통해 구현할 수 있다.💡 프록시(proxy) 객체
어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체를 뜻하며, 자바스크립트에서 프록시 객체는 두 개의 매개변수를 가진다.
target
: 프록시할 대상
handler
: 프록시 객체의 target 동작을 가로채서 정의할 동작들이 정해져 있는 함수
const handler = {
get: function(target, name) {
return name === 'name' ? `${target.a} ${target.b}` : target[name]
}
}
const p = new proxy({a: 'Bzeromo', b: '는 원딜 황제'}, handler)
console.log(p.name) // Bzeromo는 원딜 황제
🔷 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴
💡 캐싱
캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고 캐시 안에 있는 데이터를 활용하는 것, 이를 통해 불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있다.
🔷 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램
Node.js
서버를 운영할 때 서버 앞단의 프록시 서버로 nginx
가 주로 쓰인다.CloudFlare
💡 CDN(Content Delivery Network)
각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크를 말한다. 이를 통해 사용자가 웹 서버로부터 콘텐츠를 다운로드하는 시간을 줄일 수 있다.