JavaScript & TypeScript

[TS] OOP

반응형

 

Class

타입스크립트에서는 타입을 기본적으로 설정을 해주어야 하는데, OOP를 만족하면서 코드를 작성해 주기 위해서는 결과물에 대한 타입을 먼저 설정해 주어야 합니다.

  type Smartphone = {
    color: string;
    brand: apple;
  };

 

 

1. Encapsulation(캡슐화)

연관있는 데이터들을 하나의 오브젝트 안에 모아 두는 것이 캡슐화라고 합니다. 그리고 외부에서 보일 필요 없는 내부 데이터를 숨기기도 합니다. 그러므로 내부에 상태를 변경하지 않고 어떤 외부의 데이터가 들어오느냐에 따라 상태가 변하도록 만듭니다.

 

static

class 안에서 함수나 변수 앞에 static을 사용하면 class 레벨로 설정이 되며, 쉽게 말해서 만들고 있는 클래스에 고정된 데이터가 되는 것입니다. 그렇기 때문에 예를 들어, Smartphone 이라는 클래스에 battery 라는 함수앞에 static을 붙이게 된다면 battery라는 함수를 사용하기 위해서는 Smartphone.battery로 사용을 해주어야 합니다.

static은 공유의 목적으로도 존재하며, static을 사용하지 않았다면 instance 레벨로 외부의 값을 주입함에 따라 변경이 가능합니다.

static은 클래스를 따로 만들지 않고 함수를 바로 호출할 수도 있습니다.

https://wikidocs.net/228

 

 

private, protected

public은 기본적으로 함수나 변수앞에 붙이지만 생략이 자동으로 됩니다. 기본적으로 그 데이터에 접근을 할 수 있다는 뜻입니다.

반대로 private와 protected는 단어에서 풍기는 느낌만으로도 데이터를 보호하고, 바꿀 수 없게 만듭니다.

 

private는 외부에서 (참조되는 값을) 보이지 않게, 접근하지 못하게 만드는 것입니다.

삼성폰을 만드는데 당연히 안드로이드가 들어가는게 기본임으로, 안드로이드를 private를 사용하여 고정적으로 외부의 값에 의해 변하지 않도록 합니다. 

 

protected는 private보다는 조금 더 허용해줍니다.

어떤 클래스가 있을때 private와 동일한 역할을 해주지만, 그 클래스를 상속하여 사용하는 경우에는 상속받은 자식 클래스에서는 그 값에 접근 할 수 있도록 하는 것 입니다.

 

public -어디서든 

protected = 상속관계만

private = 해당 클래스에서만

 

만약 private static을 동시에 사용해주면, 해당 클래스 내부에서만 사용할 수 있게 됩니다.

주로 고정된 값에 사용합니다.

 

 

Getter, Setter

보통 함수 앞에 getter는 get을 붙여 사용하고 setter은 set을 붙여 사용합니다.

getter는 읽기, setter는 쓰기라고 생각하면 편합니다. (getter 은 인스턴스 변수를 반환하고 setter 은 인스턴스 변수를 대입하거나 수정합니다.)

게터와 세터를 쓰는 이유는 값을 받을 때, 원하지 않는 불필요한 값을 제어하기 위해 존재 합니다.

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // 너무 짧은 이름을 할당하려 함

https://ko.javascript.info/property-accessors

 

 

2. Abstraction(추상화)

어떠한 오브젝트에 대한 내부 구조를 알 필요없이 사용하도록 만드는 것.

스마트폰을 사용하면서 스마트폰 안에있는 회로도, 구조를 알 필요 없는 것과 같다고 생각합니다.

 

Interface - implements

클래스에서 코드를 작성하다보면 많은 함수를 생성하는데 다른사람이 그 코드를 바라볼 때 어떤 함수를 써야하는지 굉장히 혼란스러울 때 interface를 사용하여 추상화를 구상할 수 있습니다.

interface의 단어 뜻은 접속하다이며, 특정 함수를 선택하여 그것만 사용하도록 하는것입니다.

흔히 코스요리라 생각하면 쉬운데, 어떤 음식점에서 다양한 메뉴가 있어 고르기 어려울 수가 있는데 어려움을 생각하지 않게 하기 위해 코스요리를 선택하곤 합니다. 보통 코스요리는 자신이 원하는 음식 메뉴를 넣기 보단 짜여진 틀 그대로 이용하기 때문에 추상화랑 유사하다고 생각합니다.

(interface는 class 내부에서 선언하는 것이 아닌 외부에서 선언해줍니다.)

 

implements는 선언한 클래스 옆에 선언한 interface 이름을 적어주면 됩니다. 

아래 코드는 사람 한명당 사과나, 커피를 3을 제공합니다.

// 타입선언
type Dish = {
  food: number;
};

// 인터페이스 선언
interface Cooking {
  makeFood(apple: number): Dish;
}

// 클래스 선언
class Food implements Cooking {
  // 상수를 생성하기 위한 private static
  private static apple: number = 3; 
  private static coffee: number = 3;
  private people: number = 0;
	
  protected constructor(people: number) {
    this.people = people;
  }

// static으로 클래스를 따로 만들지 않고 함수를 바로 호출할 수도 있습니다.
  static makeMachine(people: number): Food {
    return new Food(people);
  }

  makeFood(apple: number): Dish {
    return {
      food: this.people * Food.apple,
    };
  }

  makeCoffee(coffee: number): Dish {
    return {
      food: this.people * Food.coffee,
    };
  }
}

// Cooking interface를 타입으로 지정해 주면
// interface의 값대로 makeFood 이외에 함수를 사용할 수 없다.
const maker: Cooking = Food.makeMachine(3)
maker.makeFood
// maker.makeCoffee 는 사용할 수 없다.

만약 함수가 엄청 다양하게 존재한다면,

인터페이스로 타입을 제한하여 특정 상황에서 필요한 함수만 사용하도록 하여 추상화에 근접할 수 있도록 만들 수 있습니다.

 

abstract

abstract는 추상적인이라는 뜻으로, 클래스와 달리 인스턴스를 생성을 못합니다. 생성 구문을 사용하면 오류가 발생

class 앞에 abstract라고 표기합니다. 추상하고자하는 메서드를 정의할 때도 abstract를 메서드 이름 앞에 붙입니다.

추상 메소드는 정의만 하고 실질적 구현은 자식 클래스에서 동작되는 함수를 정의해야 합니다.

 

쉽게 말해서 부모클래스에 abstract를 사용하면, 직접적으로 인스턴스를 만들지 못하고

기본적인 자주사용하는 메서드들은 그대로 구현을 하여 사용할 수 있고, 각 자식 클래스 마다 구현이 변경되어야 하는 메서드들을 abstract 선언하여 자식 클래스에서 기능을 만들어 주어야 합니다.

참고 : https://yamoo9.gitbook.io/typescript/classes/abstract-class

 

 

3. Inheritance(상속)

스마트폰을 만드는 공장(오브젝트)이 있다면, 다양한 종류의 스마트폰을 만들기 위해서는 원하고자 하는 기능과 재료를 넣어 다양한 스마트폰을 만들 수 있게 하여 또 다른 공장을 짓지 않고 기존 공장(오브젝트)를 활용하는 것이 상속입니다. 그렇기 때문에 코드 재사용성을 높히기도 합니다.

 

extends

상속을 위해 사용합니다. 위에서 사용한 클래스 Food를 이용하여 재사용성을 높이려고 하면 아래 코드와 같이 사용할 수 있습니다.

생성자 constructor은 부모 클래스에서 private를 사용하면 상속이 되지 않으니 protected나 public으로 바꿔주어야 합니다.

 

super

Desert 클래스를 사용하면 Food에 함수를 사용할 수도 있습니다. 그러기 위해서 필요한 것이 super 입니다.

자식 클래스에서 super.makeFood를 통해 가져와 사용할 수 있습니다.

그리고 생성자를 추가적으로 사용하려거나, 부모 클래스의 생성자를 사용하려면 super을 통해 선언해주어야 합니다.

class Desert extends Food {
  constructor(people: number, public readonly cake: number) {
    super(people); // 부모생성자 전달
  }
  const food = super.makeFood(1)
    // ... Code
    
}

 

 

 

4. Polymorphism(다양성)

스마트폰을 만드는 공장(오브젝트)를 통해서 다양한 스마트폰을 생성하는 것을 다양성이라고 합니다. 위에 나온 3가지의 방법을 통해서 다양성이 생긴다고 생각합니다.

 

Compostion

 타입스크립트에서는 클래스에서 오직 하나의 클래스만 상속 할 수가 있습니다. (자식에 자식 생성불가)

그러므로 계속해서 상속을 사용하면 굉장히 복잡해지고 수정이 어렵기 때문에 class에 class를 의존성 주입(Dependency injection) 이용하여 값을 설정하면 되지만, 직접적으로 하게되면 강한 결합이 생기므로(class-class)

constructor에 interface를 대입하여 (class -> (interface) -> class)를 사용하여 디커플링 되도록 합니다.

 

interface PokemonBall{
  gotcha():void;
}

class MonsterBall implements PokemonBall{
  gotcha(){
    console.log("monsterBall");
  }
};
class SuperBall implements PokemonBall {
  gotcha(){
    console.log("superBall");
  }
};

class PokemonMaster {
    private pokemonBall: PokemonBall; // MonsterBall 클래스에 의존성이 생긴다.

    constructor(pokemonBall:PokemonBall) {
        this.pokemonBall = pokemonBall; 
    }

    public throwMonsterBall() {
        this.pokemonBall.gotcha(); //잡았다
    }
}

const Red = new PokemonMaster(new MonsterBall());
const Blue = new PokemonMaster(new SuperBall());

Red.throwMonsterBall();
Blue.throwMonsterBall();

출처 : https://zakelstorm.tistory.com/71

 

위 코드에서는 어떠한 몬스터볼을 추가하더라도 손쉽게 추가하여 몬스터를 잡을 수가 있다.

 

 

반응형

'JavaScript & TypeScript' 카테고리의 다른 글

[JS] ProtoType  (0) 2022.03.15
[TS] 제네릭(Generics)  (0) 2022.03.14
[TS] Type  (0) 2022.03.07
[TS] Type Script  (0) 2022.03.06
[node.js] TDD / Jest(관련링크)  (0) 2022.03.05