ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 1장 디자인 패턴과 프로그래밍 패러다임
    Books/면접을 위한 CS 전공지식 노트 2022. 12. 31. 17:57

    1.1 디자인 패턴

    디자인 패턴 : 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계등을 이용하여 해결할 수 있도록 하나의 

                          '규약' 형태로 만들어 놓은 것을 의미합니다.

     

    1.1.1 싱글톤 패턴

    - 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

    - 하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만, 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 사용

    - 보통 데이터베이스 연결 모듈에 많이 사용

    - 인스턴스를 공유하며 사용하기에 인스턴스 생성 비용은 줄어들고, 의존성은 높아진다.

    // javascipt
    const obj = {
        a:27
    }
    const obj2 = {
        a:27
    }
    console.log(obj === obj2);	//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
    // 데이터베이스 연결 모듈
    const URL = 'mongodb://localhost:27017/test'
    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
    // java
    class Singleton {
        private static class singleInstanceHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() {
            return singleInstanceHodler.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());
    
            System.out.println(a == b);			// true
        }
    }
    // MongoDB
    // Node.js - MongoDB 연결 - 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)
            });
        });
    };
    // MySQL
    // 메인 모듈
    const mysql = require('mysql');
    const pool = mysql.createPool({
        connectionLimit: 10,
        host: 'example.org',
        user: 'test',
        password: 'test',
        database: 'testDB'
    });
    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);
    });
    
    // 메인모듈에서 인스턴스를 정의하고 다른 모듈에서 쿼리를 보내는 방식

    - 단점

        ㆍTDD가 어렵다 : 각 테스트마다 독립적인 인스턴스를 만들기가 어렵다.

    - 의존성 주입 

        ㆍ모듈 간의 결합을 강하게 만든다는 단점을 의존성 주입을 통해 모듈 간의 결합을 느슨하게 만들어 해결 가능

        ㆍ메인 모듈이 직접 하위모듈에 의존성을 주기보단 중간에 의존성 주입자가 간접적으로 의존성을 주입

        ㆍ메인 모듈은 하위 모듈에 대한 의존성이 떨어진다. → "디커플링 된다"

    - 의존성 주입의 장점

        ㆍ모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기도 수월

        ㆍ구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고,

            애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해진다.

    - 의존성 주입의 단점

        ㆍ모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생기기도 한다.

    - 의존성 주입의 원칙

        ㆍ"상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다. 또한, 둘 다 추상화에 의존해야하며, 이때 추상화는

             세부사항에 의존하지 말아야 합니다."

     

    1.1.2 팩토리 패턴

    - 팩토리 패턴은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴

    - 상위 클래스와 하위 클래스가 분리 → 느슨한 결합

    - 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요없음 → 유연성 증가

    - 객체 생성 로직이 따로 떼어져 있기에 리팩터링하더라도 한 곳만 수정 → 유지 보수성 증가

    // javascript
    
    // 1. new Object
    const num = new Object(42);
    const str = new Object('abc');
    num.constructor.name;	// Number
    str.constructor.name;	// String
    
    // 2. 
    class Latter {
        constructor() {
            this.name = "latte"
        }
    }
    class Espresso {
        constructor() {
            this.name = "Espresso"
        }
    }
    class LatteFactory {
        static createCoffee() {
            return new Latte()
        }
    }
    class EspressoFactory {
        static createCoffee() {
            return new Espresso()
        }
    }
    const factoryList = { LatteFactory, EspressoFactory }
    
    class CoffeeFactory {
        static createCoffee(type) {
            const factory = factoryList[type];
            return factory.createCoffee()
        }
    }
    const main = () => {
        // 라떼 커피를 주문한다.
        const coffee = CoffeeFactory.createCoffee("LatteFactory")
        // 커피 이름을 부른다.
        console.log(coffee.name)	// latte
    }
    main();

    - CoffeeFactory라는 상위 클래스가 중요한 뼈대를 결정하고 하위 클래스인 LatteFactory가 구체적인 내용을 결정 → 의존성 주입

    - 정적 메서드를 쓰면 클래스의 인스턴스 없이 호출이 가능하여 메모리를 절약할 수 있고 개별 인스턴스에 묶이지 않으며 클래스 내의 함수를 정의할 수 있는 장점이 있다.

    // java
    abstract class Coffee {
        public abstract int getPrice();
        
        @Override
        public String toString() {
            return "Hi this coffee is " + this.getPrice();
        }
    }
    class CoffeeFactory {
        public static Coffee getCoffee(String type, int price) {
            if("Latte".equalsIgnoreCase(type)) return new Latte(price);
            else if ("Americano".equalsIgnoreCase(type)) return new Americano(price);
            else { return new DefaultCoffee(); }
        }
    }
    class DefaultCoffee extends Coffee {
        private int price;
        
        public DefaultCoffee() {
            this.price = -1;
        }
        @Overrride
        public int getPrice() {
            return this.price;
        }
    }
    class Latte extends Coffee {
        private int price;
        
        public Latte(int price) {
            this.price = price;
        }
        @Override
        public int getPrice() {
            return this.price;
        }
    }
    class Americano extends Coffee {
        private int price;
        
        public Americano(int price) {
            this.price = price;
        }
        @Override
        public int getPrice() {
            return this.price;
        }
    }
    public class HelloWorld {
        public static void main(String[] args) {
            Coffee latte = CoffeeFactory.getCoffee("Latte", 4000);
            Coffee ame = CoffeeFactory.getCoffee("Americano", 3000);
            System.out.println("Factory latte ::" + latte);
            System.out.println("Factory ame ::" + ame);
        }
    }
    /*
    Factory latte ::Hi this coffee is 4000
    Factory ame ::Hi this coffee is 3000
    */

     * Enum

        ㆍ상수의 집합을 정의할 때 사용되는 타입

        ㆍ상수나 메서드 등을 집어넣어서 관리하며 코드를 리팩터링할 때 해당 집합에 관한 로직 수정 시 이 부분만 수정하면 되므로

            코드 리팩터링 시  강점이 생긴다.

    1.1.3 전략 패턴

    - 전략 패턴은 정책 패턴이라고도 한다.

    - 객체의 행위를 바꾸고 싶은 경우 '직접' 수정하지 않고 전략이라고 부르는 '캡슐화한 알고리즘'을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴

    // java 
    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;
        }
        pulbic String 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);
            
            // pay by LUNACard
            cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
            
            // pay by KAKAOCard
            cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
        }
    }
    /*
    400 paid using LUNACard
    400 paid using KAKAOCard
    */

    * 컨텍스트 : 프로그래밍에서의 컨텍스트는 상황, 맥락, 문맥을 의미하며 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보를 말한다.

    passport의 전략 패턴

    - 전략 패턴을 활용한 라이브러리

    - passport는 Node.js에서 인증 모듈을 구현할 때 쓰는 미들웨어 라이브러리

    var passport = require('passport');
    var LocalStrategy = require('passport-local').Strategy;
    
    passport.use(new LocalStrategy(
        function(username, password, done) {
            User.findOne({ username: username }, fucntion(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.1.4 옵저버 패턴

    - 옵저버 패턴(observer pattern)은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴

    - 옵저버 패턴을 활용한 서비스로는 트위터가 있다

    - 옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며 MVC(Model - View - Controller) 패턴에도 사용된다.

    // java
    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();
    }
    
    // topic은 주체이자 객체
    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!!
    */

     

    * 자바: 상속과 구현

    - 상속

        ㆍ상속(extends)은 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며

             자식 클래스에서 추가 및 확장할 수 있는 것을 말한다.

        ㆍ재사용성, 중복성의 최소화

    - 구현

        ㆍ구현(implements)은 부모 인터페이스(interface)를 자식 클래스에서 재정의하여 구현하는 것을 말하며,

             상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야 합니다.

    - 상속과 구현의 차이

        ㆍ상속은 일반 클래스, abstract 클래스를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현

     

    자바스크립트에서의 옵저버 패턴

    - JS에서의 옵저버 패턴은 프록시 객체를 통해 구현가능

     

    * 프록시 객체

    - 프록시(proxy) 객체는 어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체를 뜻한다

    - 자바스크립트에서 프록시 객체는 두 개의 매개변수를 가진다

        ㆍtraget : 프록시할 대상

        ㆍhandler : 프록시 객체의 target 동작을 가로채서 정의할 동작들이 정해져 있는 함수

    // JS
    const handler = {
        get: fucntion(target, name) {
            return name === 'name' ? `${target.a} ${target.b}` : target[name]
        }
    }
    
    const p = new Proxy({a: 'KUNDOL', b: 'IS AUMUMU ZANGIN'}, handler);
    console.log(p.name);	// KUNDOL IS AUMUMU ZANGIN
    
    // new Proxy로 선언한 객체의 a와 b라는 속성에 특정 문자열을 담아서 handler에
    // "name이라는 속성에 접근할 때는 a와 b라는 것을 합쳐서 문자열을 만들어라."를 구현

    * 프록시 객체를 이용한 옵저버 패턴

    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.형규 = "커플"
    // 형규가 [솔로] >> [커플] 로 변경되었습니다.
    
    // 프록시 객체의 get() 함수는 속성과 함수에 대한 접근을 가로채며,
    // has() 함수는 in 연산자의 사용을 가로챕니다
    // set() 함수는 속성에 대한 접근을 가로챕니다.

     

    * DOM (Document Object Model)

    - 문서 객체 모델을 말하며, 웹 브라우저상의 화면을 이루고 있는 요소들을 지칭한다

    // vue.js 3.0의 옵저버 패턴
    function createReactiveObjec(
        target: Target,
        isReadonly: boolean,
        baseHandlers: ProxyHandler<any>,
        collectionHandlers: ProxyHandler<any>,
        proxyMap: WeakMap<Target, any>
    ) {
        if( !isObject(target) ) {
            if(__DEV__) {
                console.warn(`value cannot be made reactive: ${String(target)}`)
            }
            return target
        }
        // target is already a Proxy, return it.
        // exception: calling readonly() on a reactive object
        if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) {
            return target;
        }
        // target already has corresponding Proxy
        const existingProxy = proxyMap.get(target);
        if(existingProxy) {
            return existingProxy
        }
        //only a whitelist of value types can be observed
        const targetType = getTargetType(target);
        if (targetType === TargetType.INVALID) {
            return target;
        }
        const proxy = new Proxy(
            target,
            targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
        );
        proxyMap.set(target, proxy);
        return proxy;
    }

     

    1.1.5 프록시 패턴과 프록시 서버

    프록시 패턴

    - 프록시 패턴(proxy pattern)은 대상 객체(subject)에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴

    - 이를 통해 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용한다.

    - 프록시 객체로 쓰이기도 하지만 프록시 서버로도 활용된다

     

    * 프록시 서버에서의 캐싱

    - 캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고 캐시 안에 있는 데이터를 활용하는 것을 말한다.

    - 이를 통해 불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있다는 장점이 있다

     

    프록시 서버

    - 프록시 서버(proxy server)는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.

     

    * 프록시 서버로 쓰는 nginx

    - nginx는 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹서버이며,

        주로 Node.js 서버 앞단의 프록시 서버로 활용된다.

    - nginx를 프록시 서버로 둬서 실제 포트를 숨길 수 있고 정적 자원을 gzip 압축하거나,

        메인 서버 앞단에서의 로깅을 할 수 있다.

     

    * 버퍼 오버플로우

    - 버퍼는 보통 데이터가 저장하는 메모리 공간으로, 메모리 공간을 벗어나는 경우를 말한다

    - 이 때 사용되지 않아야 할 영역에 데이터가 덮어씌워져 주소, 값을 바꾸는 공격이 발생하기도 한다

    * gzip 압축

    - LZ77과 Huffman 코딩의 조합인 DEFLATE 알고리즘을 기반으로 한 압축 기술이다.

    - gzip 압축을 하면 데이터 전송량을 줄일 수 있지만,

       압축을 해제했을 때 서버에서의 CPU 오버헤드도 생각해서 gzip 압축 사용 유무를 결정해야 한다.

     

    * 프록시 서버로 쓰는 CloudFlare

    - CloudFlare는 전 세계적으로 분산된 서버가 있고

       이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스이다.

    - DDOS 공격 방어, HTTPS 구축의 이점이 있다

     

    * DDOS 공격 방어

    - DDOS는 짧은 기간 동안 네트워크에 많은 요청을 보내 네트워크를 마비시켜 웹 사이트의 가용성을 방해하는 사이버 공격 유형

    - CloudFlare는 의심스러운 트래픽, 특히 사용자가 접속하는 것이 아닌 시스템을 통해 오는 트래픽을 자동으로 차단해서 DDOS 공격으로부터 보호

    - CloudFlare의 거대한 네트워크 용량과 캐싱 전략으로 소규모 DDOS 공격은  쉽게 막아낼 수 있으며 이러한 공격에 대한 방화벽 대시보드도 제공한다

     

    * HTTPS 구축

    - 서버에서 HTTPS를 구축할 때 인증서를 기반으로 구축할 수도 있다.

    - 하지만 CloudFlare를 사용하면 별도의 인증서 설치 없이 좀 더 손쉽게 HTTPS를 구축할 수 있다.

     

    * CDN(Content Delivery Network)

    - 각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크를 말한다.

    - 이를 통해 사용자가 웹 서버로부터 콘텐츠를 다운로드하는 시간을 줄일 수 있다

     

    * CORS와 프런트엔드의 프록시 서버

    - CORS(Cross-Origin Resource Sharing)는 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘

    - 프런트엔드 개발 시 프런트엔드 서버를 만들어서 백엔드 서버와 통신할 때 주로 CORS 에러를 마주치는데,

       이를 해결하기 위해 프런트엔드에서 프록시 서버를 만들기도 한다.

    - 예를 들어 프론트엔드에서는 127.0.0.1:3000으로 테스팅을 하는데 백엔드 서버는 127.0.0.1:12010이라면 포트 번호가 다르기 때문에 CORS 에러 발생

    - 이때 프록시 서버를 둬서 프런트엔드 서버에서 요청되는 오리진을 127.0.0.1:12010으로 바꾸는 것이다.

    - 참고로 127.0.0.1 이란 루프백 IP로, 본인 PC의 IP를 뜻한다. localhost나 127.0.0.1을 입력하면 DNS를 타지 않고 바로 본인 PC로 연결

     

    * 오리진

    - 프로토콜과 호스트 이름, 포트의 조합을 말한다

    - 예를 들어 https://kundol.com:12010/test 라는 주소에서 오리진은https://kundol.com:12010을 뜻한다.

    1.1.6 이터레이터 패턴

    - 이터레이터 패턴(iterator pattern)은 이터레이터(iterator)를 사용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴

    - 이를 통해 순회할 수 있는 여러 가지 자료형의 구조와는 상관없이 이터레이터라는 하나의 인터페이스로 순회 가능

    // JS
    const mp = new Map();
    mp.set('a',1);
    mp.set('b',2);
    mp.set('c',3);
    const st = new Set();
    st.add(1);
    st.add(2);
    st.add(3);
    for (let a of mp) console.log(a);
    for (let a of st) console.log(a);
    /*
    ['a',1]
    ['b',2]
    ['c',3]
    1
    2
    3
    */
    
    // 분명히 다른 자료 구조인 set과 map임에도 똑같은 for a of b라는 이터레이터 프로토콜을 통해 순회함

    * 이터레이터 프로토콜

    - 이터러블한 객체들을 순회할 때 쓰이는 규칙

    * 이터러블한 객체

    - 반복 가능한 객체로 배열을 일반화한 객체

     

    1.1.7 노출모듈 패턴

    - 노출모듈 패턴(revealing module pattern)은 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴

    - 자바스크립트는 private, public 같은 접근 제어자가 존재하지 않고 전역 범위에서 스크립트가 실행되기에 노출모듈패턴 사용

    // JS
    const pukuba = (() => {
        const a = 1;
        const b = () => 2
        const public = {
            c:2,
            d:() => 3
        }
        return public;
    })()
    
    console.log(pukuba);
    console.log(pububa.a);
    // { c:2, d: [Function: d] }
    // undefined
    
    // a와 b는 다른 모듈에서 사용할 수 없는 변수나 함수이며 private 범위를 가진다.
    // c나 d는 다른 모듈에서 사용할 수 있는 변수나 함수이며 public 범위를 가진다.
    // 앞서 설명한 노출모듈 패턴을 기반으로 만든 자바스크립트 모듈 방식으로는 CJS(CommonJS)모듈방식도 있음

    * 접근 제어자

    public 클래스에 정의된 함수에서 접근 가능하며 자식 클래스와 외부 클래스에서 접근 가능한 범위
    protected 클래스에 정의된 함수에서 접근가능, 자식 클래스에서 접근 가능하지만 외부 클래스에서 접근 불가능한 범위
    private 클래스에 정의된 함수에서 접근 가능하지만 자식 클래스와 외부 클래스에서 접근 불가능한 범위
    즉시 실행 함수 함수를 정의하자마자 바로 호출하는 함수.
    초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용한다

     

    1.1.8 MVC 패턴

    - MVC 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴

    - 애플리케이션의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발

    - 재사용성과 확장성이 용이하다는 장점이 있고, 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해지는 단점이 있다

     

    모델

    - 모델(model)은 애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 뜻

    - 예를 들어 사각형 모양의 박스 안에 글자가 들어 있다면 그 사각형 모양의 박스 위치 정보, 글자 내용, 글자 위치, 글자 포맷(utf-8 등)에 관한 정보를 모두 가지고 있어야 한다

    - 뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신한다

     

    - 뷰(view)는 inputbox, checkbox, textarea 등 사용자 인터페이스 요소를 나타낸다

    - 즉, 모델을 기반으로 사용자가 볼 수 있는 화면을 뜻한다

    - 모델이 가지고 있는 정보를 따로 저장하지 않아야 하며 단순히 사각형 모양 등 화면에 표기하는 정보만 가지고 있어야 한다.

    - 또한, 변경이 일어나면 컨트롤러에 이를 전달

     

    컨트롤러

    - 컨트롤러(controller)는 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 하며 이벤트 등 메인 로직을 담당

    - 또한 모델과 뷰의 생명주기도 관리하며, 모델이나 뷰의 변경 통지를 받으며 이를 해석하여 각각의 구성 요소에 해당 내용에 대해 알려줌

     

    MVC 패턴의 예 리액트

    - MVC 패턴을 이용한 대표적인 라이브러리로는 리액트

    - 리액트는 유저 인터페이스를 구축하기 위한 라이브러리

    - '가상 DOM'을 통해 실제 DOM을 조작하는 것을 추상화해서 성능을 높였다

    - 대표적인 특성으로는 불변성(immutable)

        ㆍstate는 setState를 통해서만 수정 가능

        ㆍprops 기반으로 만들어지는 컴포넌트인 pureComponent

        ㆍ단방향 바인딩이 적용, 자유도가 높다

        ㆍ메타(페이스북)가 운영 / 넷플릭스, 트위터, 드롭박스, 우버, 페이팔, 마이크로소프트 등에 사용

     

    1.1.9 MVP 패턴

    - MVP 패턴은 MVC패턴으로부터 파생되었으며 MVC에서 C에 해당하는 컨트롤러가 프레젠터(presenter)로 교체된 패턴

    - 뷰와 프레젠터는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴

     

    1.1.10 MVVM 패턴

    - MVVM 패턴은 MVC의 C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴

    - 여기서 뷰모델은 뷰를 더 추상화한 계층

    - MVVM패턴은 MVC패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징

    - 뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고, 단위테스트가 쉽다.

     

    MVVM 패턴의 예: 뷰

    - MVVM 패턴을 가진 대표적인 프레임워크로는 뷰(Vue.js)가 있다.

    - Vue.js는 반응형(reactivity)이 특징인 프런트엔드 프레임워크

        ㆍwatch와 computed 등으로 쉽게 반응형적인 값들을 구축

        ㆍ함수를 사용하지 않고 값 대입만으로도 변수가 변경되며, 양방향 바인딩, html을 토대로 컴포넌트를 구축할 수 있다

        ㆍ재사용 가능한 컴포넌트 기반으로 UI를 구축할 수 있으며 BMW, 구글, 루이비통 등에서 사용

     

    * 커맨드

    - 여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법

    * 데이터 바인딩

    - 화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치시키는 기법으로, 뷰모델을 변경하면 뷰가 변경

     

    1.2 프로그래밍 패러다임

    - 프로그래밍 패러다임( programming paradigm ) : 프로그래머에게 프로그래밍의 관점을 갖게 해주는 역할을 하는 개발 방법론

    - 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호 작용하는 객체들의 집합으로 볼 수 있게 하는 반면에, 함수형 프로그래밍은 상태 값을 지니지 않는 함수 값들의 연속으로 생각할 수 있게 해준다

    - 어떤 언어는 특정한 패러다임을 지원하기도 하는데, jdk 1.8 이전의 자바는 객체 지향 프로그래밍을 지원하는 반면에, 하스켈은 함수형 프로그래밍을 지원한다.

    - 여러 패러다임을 지원하는 언어로는 C++, 파이썬, 자바스크립트가 있으며 자바의 경우 jdk 1.8부터 함수형 프로그래밍 패러다임을 지원하기 위해 람다식, 생성자 레퍼런스, 메서드 레퍼런스를 도입했고 선언형 프로그래밍을 위해 스트림(stream) 같은 표준 API 등도 추가

    - 프로그래밍 패러다임은 크게 선언형, 명령형으로 나누며, 선언형은 함수형이라는 하위 집합을 갖는다. 또한 명령형은 다시 객체지향, 절차지향으로 나눈다.

     

    1.2.1 선언형과 함수형 프로그래밍

    - 선언형 프로그래밍(declarative programming)이란 '무엇을' 풀어내는가에 집중하는 패러다임이며,

       "프로그램은 함수로 이루어진것이다."라는 명제가 담겨있는 패러다임이기도 하다.

    - 함수형 프로그래밍(functional programming)은 선언형 패러다임의 일종

    // 예를 들어 자연수로 이루어진 배열에서 최댓값을 찾아야 한다?
    const ret = [1,2,3,4,5,11,12]
    .reduce((max,num) => num>max ? num : max, 0)
    console.log(ret);	//12
    
    // reduce() 는 '배열'만 받아서 누적한 결괏값을 반환하는 순수 함수

    - 함수형 프로그래밍은 이와 같은 작은 '순수 함수'들을 블록처럼 쌓아 로직을 구현하고 '고차 함수'를 통해 재사용성을 높인 프로그래밍 패러다임이다.

    - 자바스크립트는 단순하고 유연한 언어이며, 함수가 일급 객체이기 때문에 객체지향 프로그래밍보다는 함수형 프로그래밍 방식이 선호

     

    순수함수

    - 출력이 입력에만 의존하는 것을 의미

    const pure = (a,b) => {
        return a+b;
    }
    
    // pure 함수는 들어오는 매개변수 a,b에만 영향을 받는다
    // 만약 a,b말고 다른 전역 변수 c 등이 이 출력에 영향을 주면 순수 함수가 아니다

     

    고차함수

    - 고차함수란 함수가 함수를 값처럼 매개변수로 받아 로직을 생성할 수 있는 것을 말한다

     

    * 일급 객체

    - 고차 함수를 쓰기 위해서는 해당 언어가 일급 객체라는 특징을 가져야 한다

        ㆍ변수나 메서드에 함수를 할당할 수 있다

        ㆍ함수 안에 함수를 매개변수로 담을 수 있다

        ㆍ함수가 함수를 반환할 수 있다.

     

    - 참고로 함수형 프로그래밍은 이외에도 커링, 불변성 등 많은 특징이 있다

     

    1.2.2 객체지향 프로그래밍

    - 객체지향 프로그래밍(OOP, Object-Oriented Programming)은 객체들의 집합으로 프로그램의 상호 작용을 표현하며

       데이터를 객체로 취급하여 객체 내부에 선언된 메서드를 활용하는 방식

    - 설계에 많은 시간이 소요되며 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느립니다

    // 예를 들어 자연수로 이루어진 배열에서 최댓값을 찾으라고 한다면 다음과 같은 로직을 구성
    const ret = [1,2,3,4,5,11,12]
    class List {
        constructor(list) {
            this.list = list;
            this.mx = list.reduce((max, num) => num > max ? num : max, 0);
        }
        getMax() {
            return this.mx;
        }
    }
    const a = new List(ret);
    console.log(a.getMax());	// 12
    
    // List라는 클래스를 만들고 a라는 객체를 만들 때 최댓값을 추출해내는 메서드를 만든 예제

     

    객체지향 프로그래밍의 특징

    - 객체 지향 프로그래밍은 추상화, 캡슐화, 상속성, 다형성이라는 특징 존재

     

    추상화

    - 추상화(abstraction)란 복잡한 시스템으로부터 핵심적인 개념 또는 기능을 추려내는 것을 의미

    - 예를 들어 필자의 후배 종화에게는 군인, 장교, 키180, 여친있음, 안경씀, 축구못함, 롤마스터티어 등의 특징이 있다.

        이러한 특징 중에서 코드로 나타낼 때 일부분의 특징인 군인, 장교만 뽑아내거나 조금 더 간추려서 나타내는 것을 말함

     

    캡슐화

    - 캡슐화(encapsulation)는 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는 것을 말함

     

    상속성

    - 상속성(inheritance)은 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것을 말한다

    - 코드의 재사용 측명, 계층적인 관계 생성, 유지 보수성 측면에서 중요

     

    다형성

    - 다형성(polymorphism)은 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것을 말한다

    - 대표적으로 오버로딩 오버라이딩이 있다

     

    오버로딩

    - 오버로딩(overloading)은 같은 이름을 가진 메서드를 여러 개 두는 것을 말한다.

    - 메서드의 타입, 매개변수의 유형, 개수 등으로 여러 개를 둘 수 있으며 컴파일 중에서 발생하는 '정적' 다형성이다.

    // java
    class Person {
        public void eat(String a) {
            System.out.println("I eat " + a);
        }
        
        public void eat(String a, String b) {
            System.out.println("I eat " + a + " and " + b);
        }
    }
    
    public class CalculateArea {
        public static void main(String[] args) {
             Person a = new Person();
             a.eat("apple");
             a.eat("tomato", "phodo");
        }
    }
    
    /*
    I eat apple
    I eat tomato and phodo
    */
    
    // 앞의 코드를 보면 매개변수의 개수에 따라 다른 함수가 호출되는 것을 알 수 있음

     

    오버라이딩

    - 오버라이딩(overriding)은 주로 메서드 오버라이딩(method overriding)을 말하며 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의하는 것을 의미

    - 이는 런타임 중에 발생하는 '동적' 다형성

    // java
    class Animal {
        public void bark() {
             System.out.println("mumu! mumu!");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void bark() {
            System.out.println("wal!! wal!!");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Dog d = new Dog();
            d.bark();
        }
    }
    
    /*
    wal!! wal!!
    */
    
    // 자식 클래스 기반으로 메서드가 재정의됨을 알 수 있음

     

    설계원칙

    - 객체지향 프로그래밍을 설계할 때는 SOLID 원칙을 지켜주어야 한다.

        ㆍS : 단일 책임 원칙

        ㆍO : 개방-폐쇠 원칙

        ㆍL : 리스코프 치환 원칙

        ㆍI : 인터페이스 분리 원칙

        ㆍD : 의존 역전 원칙

     

    단일 책임 원칙

    - 단일 책임 원칙(SRP, Single Responsibility Principle)은 모든 클래스는 각각 하나의 책임만 가져야 하는 원칙

    - 예를 들어 A라는 로직이 존재한다면 어떠한 클래스는 A에 관한 클래스여야 하고 이를 수정한다고 했을 때도 A와 관련된 수정이어야 함

     

    개방-폐쇄 원칙

    - 개방-폐쇄 원칙(OCP, Open Closed Principled)은 유지 보수 사항이 생긴다면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀  있어야 하는 원칙

    - 즉, 기존의 코드는 잘 변경하지 않으면서도 확장은 쉽게 할 수 있어야 한다

     

    리스코프 치환 원칙

    - 리스코프 치환 원칙(LSP, Liskov Substitution Principle)은 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 하는 것을 의미

    - 클래스는 상속이 되기 마련이고 부모, 자식이라는 계층 관계가 만들어진다.

    - 이때 부모 객체에 자식 객체를 넣어도 시스템이 문제없이 돌아가게 만드는 것을 말한다.

    - 즉, 범석 객체가 홍철 객체의 자식 계층일 때 범석 객체를 홍철 객체와 바꿔도 문제가 없어야하는 것을 말한다.

     

    인터페이스 분리 원칙

    - 인터페이스 분리 원칙(ISP, Interface Segregation Principle)은 하나의 일반적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 하는 원칙

     

    의존 역전 원칙

    - 의존 역전 원칙(DIP, Dependency Inversion Principle)은 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 원칙을 말한다.

    - 예를 들어 타이어를 갈아끼울 수 있는 틀을 만들어 놓은 후 다양한 타이어를 교체할 수 있어야 한다.

    - 즉, 상위 계층은 하위 계층의 변화에 대한 구현으로부터 독립해야 한다.

     

    1.2.3 절차형 프로그래밍

    - 절차형 프로그래밍은 로직이 수행되어야 할 연속적인 계산 과정으로 이루어져 있다

    - 일이 진행되는 방식으로 그저 코드를 구현하기만 하면 되기 때문에 코드의 가독성이 좋으며 실행 속도가 빠르다

    - 그렇기 때문에 계산이 많은 작업 등에 쓰인다.

    - 대표적으로 포트란(fortran)을 이용한 대기 과학 관련 연산 작업, 머신러닝의 배치 작업이 있다.

    - 단점으로는 모듈화하기가 어렵고 유지 보수성이 떨어진다는 점이 있다

    // 예를 들어 자연수로 이루어진 배열에서 최댓값을 찾으라고 한다면 다음과 같이 로직 구성
    const ret = [1,2,3,4,5,11,12];
    let a = 0;
    for (let i =0; i < ret.length ; i++) {
        a.Math.max(ret[i], a);
    }
    console.log(a);		//12

     

    1.2.4 패러다임의 혼합

    - 이렇게 여러 가지의 프로그래밍 패러다임을 알아보았다.

    - 그렇다면 어떠한 패러다임이 가장 좋을까? 답은 "그런 건 없다." 라는 것이다.

    - 비즈니스 로직이나 서비스의 특징을 고려해서 패러다임을 정하는 것이 좋다

    - 하나의 패러다임을 기반으로 통일하여 서비스를 구축하는 것도 좋은 생각이지만 여러 패러다임을 조합하여 상황과 맥락에 따라 패러다임 간의 장점만 취해 개발하는 것이 좋다

    - 예를 들어 백엔드에 머신러닝 파이프라인과 거래 관련 로직이 있다면 머신러닝 파이프라인은 절차지향형 패러다임, 거래관련 로직은 함수형 프로그래밍을 적용하는 것이 좋다.

     

     


    예상 질문 Q & A

    Q. 옵저버 패턴을 어떻게 구현하나요?

    A. 여러 가지 방법이 있지만 프록시 객체를 써서 하곤 합니다. 프록시 객체를 통해 객체의 속성이나 메서드 변화 등을 감지하고 이를 미리 설정해 놓은 옵저버들에게 전달하는 방법으로 구현한다

     

    Q. 프록시 서버를 설명하고 사용 사례에 대해 설명해보세요.

    A. 프록시 서버란 서버 앞단에 둬서 캐싱, 로깅, 데이터 분석을 서버보다 먼저 하는 서버를 말한다

        이를 통해 포트번호를 바꿔서 사용자가 실제 서버의 포트에 접근하지 못하게 할 수 있으며 공격자의 DDOS 공격을 차단하거나 CDN을 프록시 서버로 달아서 캐싱 처리를 용이하게 할 수 있다. nginx로 Node.js로 이루어진 서버의 앞단에 둬서 버퍼 오버플로우를 해결하거나 CloudFlare를 둬서 캐싱, 로그 분석 등을 하는 사용 사례가 있다

     

    Q. MVC 패턴을 설명하고 MVVM 패턴과의 차이는 무엇인지 설명해보세요.

    A. MVC패턴은 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴이다.

         앱의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다는 점과 재사용성과 확장성이 용이하다는 장점이 있고, 애플리케이션이 복잡해질수록 모델과 뷰의 관계 또한 복잡해지는 단점이 있다.

    MVVM 패턴은 MVC의 C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴이다.

    여기서 뷰모델은 뷰를 더 추상화한 계층이며, MVVM 패턴은 MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징이다.

    뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있다.

    'Books > 면접을 위한 CS 전공지식 노트' 카테고리의 다른 글

    5장 자료구조  (0) 2023.02.03
    4장 데이터베이스  (0) 2023.02.02
    3장 운영체제  (0) 2023.01.31
    2장 네트워크  (0) 2023.01.25

    댓글

Designed by Tistory.