본문 바로가기

Node

[드림코딩 앨리 강좌] 타입스크립트 + OOP 마스터 과정 - Chap 4. 객체지향 프로그래밍

Imperative and Procedural Programming

명령&절차적 프로그래밍

  • 모든 함수가 전역변수에 접근 가능
  • 정의된 순서대로 함수가 호출되는 형태

절차적 프로그래밍 단점

  • 전체적인 애플리케이션 동작 방식을 이해해야 됨
  • 하나를 코드 수정하면 → 사이드 이펙트 발생 확률 높아짐
  • 한 눈에 애플리케이션 이해하기도 어렵
  • 결국 유지보수, 확장성 떨어짐

Object Oriented Programming

객체 지향 프로그래밍

  • 프로그램을 객체로 정의해서 객체들끼리 서로 의사소통하도록 디자인
  • 객체 지향은 서로 관련있는 데이터와 함수를 Object 단위로 만들어나감
  • 학생, 은행, 에러, Exception 등 모든 명사들이 Object 가 될 수 있음

Class 와 Object 의 차이

  • class - 붕어빵 어떻게 생겼는 지를 묘사
  • object - 붕어빵 클래스를 이용해서 팥 붕어빵 인스턴스를 생성
Class Object
template instance of class
declare once created many times
no data in data in

 


코드로 구현해보자 (주제: 커피 기계)

절차적 vs 객체지향 코드 비교

  • 절차적은 그냥 커피 만드는 과정을 아래로 쭈욱.. 써내려가는 느낌 (코드 전체 흐름을 파악해야 함)
  • 객체지향은 커피 만드는 것 자체를 Object 로 만들어서 그 안에 properties 와 method 를 선언함
  • 상수는 static 으로 선언하자. 아니면 인스턴스 생성할 때마다 새로 생기므로 메모리 낭비가 될 수 있음 ⇒  class level
  • 보통의 지역변수 ⇒ instance(object) level
  • 예를 들어, `Math.abs()` 를 인스턴스 생성 없이 바로 사용할 수 있는 이유는, static으로 선언 된 class level 에 있는 함수이기 때문이다.

Encapsulation (캡슐화)

  • 정보를 은닉할 수 있는 방법
  • 클래스를 만들 때, 외부에서 접근할 수 있는 것은 무엇인지, 내부적으로만 가지고 있어야하는 데이터는 무엇인지 등을 결정. → 이런걸 잘 생각해서 클래스를 디자인 하는 것이 중요
  • 외부에서 클래스의 필드를 마음대로 바꿔버리면 안 됨 (예를들어 money 라는 필드를 음수로 바꿔버리면 X)
    • 필드를 private 으로 선언
    • 함수로만 해당 필드를 수정 가능하도록 변경

✋ 문제

static makeMachine(coffeeBeans: number): CoffeeMaker {
            return new CoffeeMaker(coffeeBeans)
 }
  • 위와 같이 ``static` 키워드를 이용해서 인스턴스를 생성할 수 있는 함수를 클래스의 내부에서 제공한다면 무엇을 유추할 수 있는가?
    • 이 의도는, 생성자를 이용해서 인스턴스 생성하는 것을 금지하기 위해 쓰는 경우가 많음
    • 생성자는 private 으로 만들어져 있을 것임.
    • 그리고 인스턴스는 이 static 붙은 함수로만 생성하게끔 유도하는 것이라고 유추 가능

getter 와 setter

  • 생성자를 이용해 멤버 변수로 바로 생성하기
// 접근제어자 붙여줘야 멤버 변수로 생성 가능
constructor(private firstName: string, private lastName: string) {}
  • get , set 키워드만 앞에 붙여주면 됨
get age(): number {
            return this.internalAge;
      }

set age(num: number) {
    this.internalAge = num
}

// 사용하는건 그냥 필드에 접근하듯이 사용
user.age = 6

Abstraction (추상화)

  • 외부에서 클래스를 바라봤을 때, 사용할 수 있는 함수가 너무 많아서 어떤 걸 이용할 지 모르겠어요..!
    • 추상화를 통해서 정말 필요한 인터페이스만 노출함으로서, 이 클래스를 사용하기 쉽게 만들자

추상화 방법 1) encapsulation 을 통해서 충분히 추상화 가능

  • private 을 이용하고, 정말 필요한 함수만 ``public` 으로 선언한다.

추상화 방법 2) 인터페이스이용

  • 추상화를 좀 더 극대화해서 사용 가능
  • 인터페이스란?
    • 야, 나랑 의사소통하려면, 나는 이런이런 규약을 가지고 있어, 이런 행동을 할 수 있어! 라고 명시해 놓는 계약서 같은 아이 (너는 이러이러한 API 만 써)
    // 다양한 API 작성
    interface CommercialCoffeeMaker {
            makeCoffee(shots: number): CoffeeCup;
            fillCoffeeBeans(beans: number): void;
            clean(): void;
        }
    

Inheritance (상속)

  • 부모 클래스의 공통적인 기능은 그대로 재사용을 하면서 이 자식 클래스에서만 조금 더 자식 클래스에 특화된 기능들을 추가하는 패턴
  • ``super` 키워드를 이용해서 부모 클래스에 있는 함수들을 호출하거나 접근 가능
  • 자식 클래스에서 생성자를 따로 만들어주려면 반드시 생성자 내부에 ``super()` 로 부모 생성자도 호출해줘야 함. 부모 생성자가 매개변수를 건네받는다면 매개변수도 넣어줄 것
constructor(beans: number, public readonly serialNumber: string) {
			 // 부모 생성자
       super(beans);
 }

Polymorphism(다형성)

  • 다형성을 이용하면 한 가지의 클래스나 한 가지의 인터페이스를 통해서 다른 방식으로 구현한 클래스를 만들 수 있다.
  • 장점 - 내부적으로 구현된 다양한 클래스들이 한 가지의 인터페이스를 구현하거나, 동일한 부모 클래스를 상속했을 때, 동일한 함수를 어떤 클래스인지 구분하지 않고 공통된 api를 호출할 수 있다는 것이 큰 장점
  • // makeCoffee() 를 동일하게 호출 machines.forEach(machine => { machine.makeCoffee(1); })
  • 이처럼 다형성이란, 하나의 인터페이스 또는 부모의 클래스를 상속한 자식 클래스들이 인터페이스와 부모클래스에 있는 함수들을 다른 방식으로 다양하게 구성함으로써 조금 더 다양성을 만들어 볼 수 있는 것을 말한다.

Composition

Favor Composition, over inheritance

  • 상속의 문제점
    • 상속의 깊이가 깊어질수록, 서로의 관계가 복잡해짐
    • 부모클래스가 수정되면, 자식 클래스들에 다 영향을 줌
    • TypeScript 에서는 두 가지 이상의 부모 클래스를 상속할 수 없음 (extends)
  • 각각의 기능별로 클래스를 따로 만들어둠으로써, 필요한 곳에서 가져다가 쓰는 composition 형태로 구성하자.
    • 이것의 문제점 - 클래스들간에 너무 타이트하게 연결되어 있음

강력한 Interface

  • 클래스와 클래스간에 서로 잘 알고 지내는 것은 좋지 않다.

디커플링의 원칙

  • 클래스들 간에 서로 의사소통이 발생하는 경우에는 클래스 자신을 노출하는 것이 아니라 계약서를 통해서만 의사소통 해야함. 계약서 = interface!

요약

composition 을 이용해서 조금 더 필요한 기능들을 조립해서 확장이 가능하고, 재사용성이 높고, 유지보수가 쉽고, 조금 더 퀄리티 높은 코드를 만들기 위해서 고민하는 것이 중요

But, 우리가 타이트한 일정 내에 어떤 기능을 구현해야 되는데 코드 개선 및 확장성만 너무 고려해서 코드를 복잡하게 디자인 할 필요는 없다. 어느 정도의 중간점을 잘 지키면서 코딩해 나가는 것도 개발자의 센스

Imperative and Procedural Programming

명령&절차적 프로그래밍

  • 모든 함수가 전역변수에 접근 가능
  • 정의된 순서대로 함수가 호출되는 형태

절차적 프로그래밍 단점

  • 전체적인 애플리케이션 동작 방식을 이해해야 됨
  • 하나를 코드 수정하면 → 사이드 이펙트 발생 확률 높아짐
  • 한 눈에 애플리케이션 이해하기도 어렵
  • 결국 유지보수, 확장성 떨어짐

Object Oriented Programming

객체 지향 프로그래밍

  • 프로그램을 객체로 정의해서 객체들끼리 서로 의사소통하도록 디자인
  • 객체 지향은 서로 관련있는 데이터와 함수를 Object 단위로 만들어나감
  • 학생, 은행, 에러, Exception 등 모든 명사들이 Object 가 될 수 있음

Class 와 Object 의 차이

  • class - 붕어빵 어떻게 생겼는 지를 묘사
  • object - 붕어빵 클래스를 이용해서 팥 붕어빵 인스턴스를 생성

ClassObject

template instance of class
declare once created many times
no data in data in

코드로 구현해보자 (주제: 커피 기계)

절차적 vs 객체지향 코드 비교

  • 절차적은 그냥 커피 만드는 과정을 아래로 쭈욱.. 써내려가는 느낌 (코드 전체 흐름을 파악해야 함)
  • 객체지향은 커피 만드는 것 자체를 Object 로 만들어서 그 안에 properties 와 method 를 선언함
		// ts에 object 만드는 법
    type CoffeeCup = {
        shots: number;
        hasMilk: boolean;
    }

    const BEANS_GRAMM_PER_SHOT: number = 7
    let coffeeBeans: number = 0; // 가지고 있는 커피콩 그람수

    function makeCoffee(shots: number): CoffeeCup {
        if (coffeeBeans < shots * BEANS_GRAMM_PER_SHOT) {
            throw new Error('Not enough coffee beans!');
        }
        coffeeBeans -= shots * BEANS_GRAMM_PER_SHOT;
        return {
            shots,
            hasMilk: false
        }
    }
  • 상수는 static 으로 선언하자. 아니면 인스턴스 생성할 때마다 새로 생기므로 메모리 낭비가 될 수 있음 ⇒  class level
  • 보통의 지역변수 ⇒ instance(object) level
  • 예를 들어, `Math.abs()` 를 인스턴스 생성 없이 바로 사용할 수 있는 이유는, static으로 선언 된 class level 에 있는 함수이기 때문이다.

Encapsulation (캡슐화)

  • 정보를 은닉할 수 있는 방법
  • 클래스를 만들 때, 외부에서 접근할 수 있는 것은 무엇인지, 내부적으로만 가지고 있어야하는 데이터는 무엇인지 등을 결정. → 이런걸 잘 생각해서 클래스를 디자인 하는 것이 중요
  • 외부에서 클래스의 필드를 마음대로 바꿔버리면 안 됨 (예를들어 money 라는 필드를 음수로 바꿔버리면 X)
    • 필드를 private 으로 선언
    • 함수로만 해당 필드를 수정 가능하도록 변경

✋ 문제

static makeMachine(coffeeBeans: number): CoffeeMaker {
            return new CoffeeMaker(coffeeBeans)
 }
  • 위와 같이 ``static` 키워드를 이용해서 인스턴스를 생성할 수 있는 함수를 클래스의 내부에서 제공한다면 무엇을 유추할 수 있는가?
    • 이 의도는, 생성자를 이용해서 인스턴스 생성하는 것을 금지하기 위해 쓰는 경우가 많음
    • 생성자는 private 으로 만들어져 있을 것임.
    • 그리고 인스턴스는 이 static 붙은 함수로만 생성하게끔 유도하는 것이라고 유추 가능

getter 와 setter

  • 생성자를 이용해 멤버 변수로 바로 생성하기
// 접근제어자 붙여줘야 멤버 변수로 생성 가능
constructor(private firstName: string, private lastName: string) {}
  • get , set 키워드만 앞에 붙여주면 됨
get age(): number {
            return this.internalAge;
      }

set age(num: number) {
    this.internalAge = num
}

// 사용하는건 그냥 필드에 접근하듯이 사용
user.age = 6

Abstraction (추상화)

  • 외부에서 클래스를 바라봤을 때, 사용할 수 있는 함수가 너무 많아서 어떤 걸 이용할 지 모르겠어요..!
    • 추상화를 통해서 정말 필요한 인터페이스만 노출함으로서, 이 클래스를 사용하기 쉽게 만들자

추상화 방법 1) encapsulation 을 통해서 충분히 추상화 가능

  • private 을 이용하고, 정말 필요한 함수만 ``public` 으로 선언한다

추상화 방법 2) 인터페이스이용

  • 추상화를 좀 더 극대화해서 사용 가능
  • 인터페이스란?
    • 야, 나랑 의사소통하려면, 나는 이런이런 규약을 가지고 있어, 이런 행동을 할 수 있어! 라고 명시해 놓는 계약서 같은 아이 (너는 이러이러한 API 만 써)
    // 다양한 API 작성
    interface CommercialCoffeeMaker {
            makeCoffee(shots: number): CoffeeCup;
            fillCoffeeBeans(beans: number): void;
            clean(): void;
        }
    

Inheritance (상속)

  • 부모 클래스의 공통적인 기능은 그대로 재사용을 하면서 이 자식 클래스에서만 조금 더 자식 클래스에 특화된 기능들을 추가하는 패턴
  • ``super` 키워드를 이용해서 부모 클래스에 있는 함수들을 호출하거나 접근 가능
  • 자식 클래스에서 생성자를 따로 만들어주려면 반드시 생성자 내부에 ``super()` 로 부모 생성자도 호출해줘야 함. 부모 생성자가 매개변수를 건네받는다면 매개변수도 넣어줄 것
constructor(beans: number, public readonly serialNumber: string) {
	   // 부모 생성자
       super(beans);
 }

Polymorphism(다형성)

  • 다형성을 이용하면 한 가지의 클래스나 한 가지의 인터페이스를 통해서 다른 방식으로 구현한 클래스를 만들 수 있다.
  • 장점 - 내부적으로 구현된 다양한 클래스들이 한 가지의 인터페이스를 구현하거나, 동일한 부모 클래스를 상속했을 때, 동일한 함수를 어떤 클래스인지 구분하지 않고 공통된 api를 호출할 수 있다는 것이 큰 장점
  • // makeCoffee() 를 동일하게 호출 machines.forEach(machine => { machine.makeCoffee(1); })
  • 이처럼 다형성이란, 하나의 인터페이스 또는 부모의 클래스를 상속한 자식 클래스들이 인터페이스와 부모클래스에 있는 함수들을 다른 방식으로 다양하게 구성함으로써 조금 더 다양성을 만들어 볼 수 있는 것을 말한다.

Composition

Favor Composition, over inheritance

  • 상속의 문제점
    • 상속의 깊이가 깊어질수록, 서로의 관계가 복잡해짐
    • 부모클래스가 수정되면, 자식 클래스들에 다 영향을 줌
    • TypeScript 에서는 두 가지 이상의 부모 클래스를 상속할 수 없음 (extends)
  • 각각의 기능별로 클래스를 따로 만들어둠으로써, 필요한 곳에서 가져다가 쓰는 composition 형태로 구성하자.
    • 이것의 문제점 - 클래스들간에 너무 타이트하게 연결되어 있음

강력한 Interface

  • 클래스와 클래스간에 서로 잘 알고 지내는 것은 좋지 않다.

디커플링의 원칙

  • 클래스들 간에 서로 의사소통이 발생하는 경우에는 클래스 자신을 노출하는 것이 아니라 계약서를 통해서만 의사소통 해야함. 계약서 = interface!

요약

composition 을 이용해서 조금 더 필요한 기능들을 조립해서 확장이 가능하고, 재사용성이 높고, 유지보수가 쉽고, 조금 더 퀄리티 높은 코드를 만들기 위해서 고민하는 것이 중요

But, 우리가 타이트한 일정 내에 어떤 기능을 구현해야 되는데 코드 개선 및 확장성만 너무 고려해서 코드를 복잡하게 디자인 할 필요는 없다. 어느 정도의 중간점을 잘 지키면서 코딩해 나가는 것도 개발자의 센스