기존의 절차지향 프로그래밍은 단점이 많이 있습니다.
프로젝트에 신규로 투입이 되었다면 함수가 여러 가지가 얽혀있어서 다른 곳에 업데이트가 될 때 하나를 수정하기 힘들고 수정하였을 때 다른 사이드 이펙트가 발생할 확률이 높다.
반면 OOP는 데이터와 함수를 여러가지 오브젝트로 정의하여 프로그래밍 해나가기 때문에 관련 오브젝트만 수정해주면 되고 반복되어 사용될 수 도 있기 때문에 재사용성도 높다.
type CoffeeCup = {
shots: number;
};
class CoffeeMaker {
static BEANS_GRAMM_PER_SHOT: number = 7; // class level
// 오브젝트마다 새로 만들어줘야하는 데이터가 있다면 멤버변수를 사용하고
// 클래스에서 공유되어 사용되는 변수들은 중복적으로 데이터가 생성되고
// 메모리에 낭비가 될 수 있다. 그렇기 때문에 앞에 static을 붙여주어야 한다.
// 단 this.를 제외하고 클래스의 이름을 붙여주어야한다.
coffeBeans: number = 0; //instance (object) level
constructor(coffeBeans: number) {
this.coffeBeans = coffeBeans;
}
// constructor를 이용하지 않고 커피를 만들고 싶을때
// 클래스 내부에 있는 어떠한 속성 값도 가지지 않기 때문에 static 사용가능
static makeMachine(coffeeBeans: number): CoffeeMaker{
return new CoffeeMaker(coffeeBeans)
}
makeCoffe(shots: number): CoffeeCup {
if (this.coffeBeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT) {
throw new Error('Not enough coffee beans');
}
this.coffeBeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT;
return {
shots,
};
}
}
const maker = new CoffeeMaker(40); //new:클래스의 인스턴스를 만든다.
// 단 static을 쓰지 않으면 이렇게 사용할 수 없다.
const maker2 = CoffeeMaker.makeMachine(40)
클래스마다 새로 만들어줘야하는 데이터가 있다면 멤버변수(const, let)를 사용하고 그렇지 않다면 static을 사용해주는 것이 좋다.
Encapsulation(캡슐화)
중요한 정보들은 은닉화 해주는 것
서로 관련있는 기능들을 캡슐화 하는 것
type CoffeeCup = {
shots: number
}
// public 따로 저장하지 않으면 public으로 저장 됩니다.
// private
// protected: 외부에서 접근할 수 없지만 이 클래스르르 상속한
// 자식 클래스에서만 접근이 가능
class CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7 //
protected coffeBeans: number = 0
protected constructor(coffeBeans: number) {
this.coffeBeans = coffeBeans
}
static makeMachine(coffeBeans: number): CoffeeMaker {
return new CoffeeMaker(coffeBeans)
}
fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error('커피콩이 없습니다.')
}
}
makeCoffe(shots: number): CoffeeCup {
if (this.coffeBeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT) {
throw new Error('Not enough coffee beans')
}
this.coffeBeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT
return {
shots,
}
}
}
const maker = CoffeeMaker.makeMachine(40) //new:클래스의 인스턴스를 만든다.
maker.fillCoffeeBeans(40)
Abstraction(추상화)
내부의 복잡한 기능을 이해하지 않고 외부에서 간단한 인터페이스를 통해서 쓸 수 있는 것을 의미
추상화를 하는 방법 두가지
- 정보 은닉화 사용자가 사용하기 편하게 정말 필요한 함수들만 public으로 노출 시키고 나머지는 private이나 protected로 보호한다.
- interface를 사용합니다. interface는 규격으로 반드시 interface의 조건을 만족해야한다.
class CoffeeMachine {
fillCoffeeBeans(beans:number){
if(beans < 0){
throw new Error('커피콩이 없습니다.');
}
}
grindBeans(shots: number){
console.log(`grinding beans for ${shots}`)
if(this.coffeeBeans < shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT){
throw new Error('Not enough coffee beans!')
}
this.coffeeBeans -= shots * CoffeeMaker.BEANS_GRAMM_PER_SHOT
}
preheat():void{
console.log('heating up')
}
extract(shots: number): CoffeeCup{
console.log(`Pulling ${shots}`)
return{
shots,
hasMilk:false
}
}
makeCoffee(shots:number):CoffeeCup{
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
clean(){
console.log('cleaning the machine...');
}
}
커피를 만들어주는 함수를 만들어주었고 maker를 호출하면 많은 함수가 나오게 됩니다.
하지만 우리가 maker를 만들어준 이유는 커피를 만들어주기 위함이고 makeCoffee 함수를 호출하게 되면 다른 함수들도 호출하는 것이기 때문에 다른 함수들을 사용할 수 없게 추상화해주어야 합니다.
타입스크립트에서는 접근제어자(private, protected)도 가능하지만 interface도 추상화하는데에 사용되어집니다.
// 예열하는 함수
private preheat(): void {
console.log('heating up...');
}
// 추출하는 함수
private extract(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return { shots };
}
type CoffeeCup = {
shots: number;
};
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
fillCoffeeBeans(beans: number): void;
} //지켜야하는 규칙을 명시
interface CommercialCoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
const maker: CoffeeMachine = CoffeeMachine.makeMachine(40); //new:클래스의 인스턴스를 만든다.
maker.fillCoffeeBeans(40);
maker.makeCoffee(2);
const maker2: CommercialCoffeeMaker = CoffeeMachine.makeMachine(40); //new:클래스의 인스턴스를 만든다.
maker2.fillCoffeeBeans(40);
maker2.makeCoffee(2);
CommercialCoffeeMaker라는 인터페이스를 만들어주고 maker의 타입은CoffeeMachine으로 maker2의 타입은 CommercialCoffeeMaker을 지정해주게 되면 CommercialCoffeeMaker에는 makeCoffee라는 규격 밖에 없기 때문에 fillCoffeeBeans 함수를 사용할 수 없게 됩니다.
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
fillCoffeeBeans(beans: number): void;
} //지켜야하는 규칙을 명시
interface CommercialCoffeeMaker {
// 커피를 만들어주는 함수
makeCoffee(shots: number): CoffeeCup;
// 커피콩은 만드는 함수
fillCoffeeBeans(beans: number): void;
// 청소하는 함수
clean(): void;
}
class AmateurUser {
constructor(private machine: CoffeeMaker) {}
makeCoffee() {
const coffee = this.machine.makeCoffee(2);
console.log(coffee);
}
}
class ProBarista {
constructor(private machine: CommercialCoffeeMaker) {}
makeCoffee() {
const coffee = this.machine.makeCoffee(2);
console.log(coffee);
this.machine.fillCoffeeBeans(45);
this.machine.clean();
}
}
이제 AmateurUser와 ProBarista라는 클래스를 만들어주어 생성자의 타입을 CoffeeMaker와CommercialCoffeeMaker로 해준다면 AmateurUser의 machine은 makeCoffee 함수만 사용할 수 있고 ProBarista의 machine 은 makeCoffee, fillCoffeeBeans, clean함수를 사용할 수 있습니다.
'프론트엔드 > Typescript' 카테고리의 다른 글
4.5 번역 (0) | 2022.05.05 |
---|---|
TypeScript란? (0) | 2021.08.29 |