TypeScript 인터페이스와 클래스를 이용해 전략패턴 구현하기

이재영·2023년 9월 2일
0

TypeScript

목록 보기
2/3
post-thumbnail

Interface 란? 💡

객체의 구조를 정의하는 타입

interface IBlock{
    id : number
    title : string
    content : string
    data : number
    like : number
    // 옵셔널체이닝 가능
    hit? : number
}

const Block : IBlock = {
    // 자동완성도 뜬다. 뭐가 필요한지
    id : 0,
    title : "",
    content: "",
    data : 123,
    like :123,
    // 옵셔널 체이닝 사용하여 hit 는 생략해도 오류없음.
    hit : 123,
}

1. 가격을 조정 하는 전략패턴

// interface 객체의 구조를 정의
interface Discount {
    // 함수만 선언
    getDisCountPrice(price : number) :number
}

// 가격만 수정하는 할인
// interface의 구조를 사용할 때 implements 로 사용
class FlatDiscount implements Discount {
    private amount : number
    //생성자 메서드 
    constructor(amount : number){
        this.amount = amount;
    }
	// amount 값이 private 로 되어있기 때문에 get 메서드를 사용해서 값을 호출한다.
    getDisCountPrice(price: number): number {
        return price - this.amount
    }
}

// 할인으로 가격 수정

class PercentDiscount implements Discount {
    private amount : number;
    constructor(amount : number){
        this.amount = amount;
    }

    getDisCountPrice(price: number): number {
        return price * (1 - this.amount / 100)
    }
}

// 가격도 깍고 할인도 깍고
class FlatPercentDiscount implements Discount {
    private flatAmount : number
    private percent : number
    constructor(flatAmount : number, percent : number){
        this.flatAmount = flatAmount;
        this.percent = percent;
    }

    getDisCountPrice(price: number): number {
        const flatDiscountAmount = price - this.flatAmount;
        return flatDiscountAmount * (1 - this.percent /100);
    }
}


// 할인의 기능에 대한 유지보수가 좋아진다.
// 클래스 하나만 더 추가하면 되는것.

class Products{
    private name : string
    private price : number
    constructor(name : string, price : number){
        this.name = name;
        this.price = price;
    }
    getName() : string {
        return this.name
    }

    getPrice() : number{
        return this.price
    }
}

class ProductDiscount {
    private product : Products
    private discount : Discount
    constructor(product : Products, discount : Discount){
        this.product = product;
        this. discount = discount
    }

    getPrice() : void{
        console.log(this.discount);
        console.log(this.discount.getDisCountPrice(this.product.getPrice()))
    }
}

const _product = new Products("mac", 100000);
const _product2 = new Products("window", 2000);

// 10퍼 할인
const productDisCount = new PercentDiscount(10);

// 1000원 할인
const productDisCount2 = new FlatDiscount(1000);

// 1000원 빼고 10퍼할인
const productDisCount3 = new FlatPercentDiscount(1000,10);

const productWithDiscount = new ProductDiscount(_product, productDisCount);
productWithDiscount.getPrice();

동작 순서

productWithDiscount.getPrice(); 이 실행되면 
const productWithDiscount = new ProductDiscount(_product, productDisCount); 
로 생성된 클래스의 인스턴스 의 getPrice() 메서드가 실행된다. 
이 코드가 실행되는 순간 이미 필드의 값은 매개변수로 전달한 _product와 ProductDisCount 로 생성이 되어 

ProductDiscount {
  product: Products { name: 'mac', price: 100000 },
  discount: PercentDiscount { amount: 10 }
} 와 같이 생성된다.

console.log(this.discount.getDisCountPrice(this.product.getPrice())) 코드에서
this.product.getPrice())는 product의 가격을 가져오고,

this.discount 인 PercentDiscount의 인스턴스로 가서
getDisCountPrice의 메서드를 실행하고 값을 반환한다.

2. 로그인 전략패턴

Authent.ts

// 로그인 가입 관련된 작업
// 카카오, 네이버 구글

import {Strategy} from "./auth"

export interface AuthProps {
    email : string
    password : string
}

interface AuthenticationResponse{
    success : boolean
    message ? : string
}

interface Authenticator{
    // (검증에 대한 요청 처리)
    authenticate(credentials : AuthProps) : Promise<AuthenticationResponse>
}

// 이메일 로그인 로직 클래스
export class EmailAuthcenticator implements Authenticator {
    async authenticate(credentials: AuthProps): Promise<AuthenticationResponse> {
        // 로직은 없다 요청과 응답 코드가 들어갈 부분
        console.log("email 로그인")
        console.log(credentials);
        return {success : true}
    }
}

// 카카오 로그인 로직 클래스
export class KaKaoAuthenticator implements Authenticator{
    async authenticate(credentials: AuthProps): Promise<AuthenticationResponse> {
        // 카카오 로그인 로직 들어갈 부분
        console.log("kakao 로그인")
        console.log(credentials);
        return {success : true}
    }
}

// 로그인에 대한 서비시르르 처리할 클래스
export interface LoginService {
    // 로그인 로직에 대한 함수 구조
    login(type : string, credentials : AuthProps) : Promise<AuthenticationResponse>
}

// 로그인 클래스에 로그인 서비스 구조를 상속 받고
export class Login implements LoginService{
    // strategy 타입을 추가해줘야 함
    constructor(private readonly strategy : Strategy){}
    async login(type: "email" | "kakaoo", credentials: AuthProps): Promise<AuthenticationResponse> {
        // strategy 로그인 로직이 들어있는 객체
        // 여기에서 어떤 로그인 로직으로 처리할지 type 구분해서 
        console.log(this.strategy);
        console.log(this.strategy.email);

        const result = await this.strategy[type].authenticate(credentials)
        console.log("-------------",result);
        return result
    }
}

auth.ts

import {EmailAuthcenticator,KaKaoAuthenticator,AuthProps,Login,LoginService} from "./Authent"

// I 를 붙이는이유 인터페이스라고 표시하려고
interface IEmailSender {
    sendEmail(email : string) : void
}
class EmailSender implements IEmailSender {
    sendEmail(email: string): void {
    }
}

export interface Strategy{
    email : EmailAuthcenticator
    kakaoo : KaKaoAuthenticator
}

class Auth{
    // private 키워드가 붙어서 생성자에 넘겨받은 값이 객체의 키로 추가된다.
    constructor(
        private readonly authProps: AuthProps,
        private readonly emailSender: EmailSender,
        private readonly loginService : LoginService
    ){}
    
    // 로그인 로직
    public async login(){
        console.log(this);
        const aa= await this.loginService.login("kakaoo",this.authProps);
        console.log("---------aa",aa);
    }
    
    // 이메일 인증 처리 구조
    public register(): void {
        this.emailSender.sendEmail(this.authProps.email);
    }
}
// 유저의 email과 password 임시 객체
const authProps : AuthProps = {email: "soon@naver.com", password : "1234"}
const _emailSender = new EmailSender();

// email 로그인 로직 클래스 동적 할당
const _email = new EmailAuthcenticator()
// kakao 로그인 로직 클래스 동적 할당
const _kakao = new KaKaoAuthenticator()

// 로그인 서비스 로직을 가지고 있는 객체
const _strategy : Strategy = {
    email : _email,
    kakaoo : _kakao,

}

const _loginService = new Login(_strategy)
const auth = new Auth(authProps, _emailSender, _loginService);
console.log(auth);
auth.login();

동작순서

auth.login() 로 Auth 클래스의 login 함수가 실행되는데,
const auth = new Auth(authProps, _emailSender, _loginService) 로 
authProps 에는 {email: "soon@naver.com", password : "1234"},
_emailSender 에는 sendEmail(email: string): void {}
_loginService에는 {
    email : _email,
    kakaoo : _kakao,

} 생성자를 가진 Login 클래스의 새 인스턴스,
를 가진 Auth 클래스의 새 인스턴스가 생성된다.
login()의 함수 this.loginService.login("email",this.authProps)에 의해
Login 클래스의 login 함수가 실행되고 type은 "email" , credentials 에는 {email: "soon@naver.com", password : "1234"} 가 전달된다.
그리고 const result = await this.strategy[type].authenticate(credentials) 에 의해 EmailAuthcenticator의 authenticate() 함수가 실행된다.
그래서 console.log("email 로그인")와 console.log(credentials); 코드가 실행되어 
email 로그인
{ email: 'soon@naver.com', password: '1234' } 가 출력되고, result에 { success: true } 가 반환되고,
그게 또 aa로 반환되어 출력된다.

이해가 안됐던 코드 💢

Login 클래스에서 const result = await this.strategy[type].authenticate(credentials);
strategy의 값이 { email: EmailAuthcenticator {}, kakaoo: KaKaoAuthenticator {} } 이것인데,
호출할 때 this.strategy.type이 아닌 this.strategy[type]로 호출하는게 이해가 되지 않았다.
type은 console.log 로 출력해보면 email 또는 kakaoo 가 들어가고,
결국 this.strategy.email 이나 this.strategy.kakaoo 가 되는것인데 말이다.
하지만, this.strategy.type로 코드를 작성했을 경우 strategy에 속성명 type이 정의되어 있지 않다고 오류가 발생했고,
this.strategy.email을 console.log로 출력해보고 정상적으로 출력이 되는것을 보고 이해를 했다.
⭐⭐⭐⭐⭐⭐⭐
아 ~ 속성명을 그대로 입력하는 경우에는 .으로 접근이 가능하지만,
동적으로 어느 변수에 값을 담아서 접근하는경우에는 .이 아닌 []를 통해서 접근해야 한다는 것을...

profile
한걸음씩

0개의 댓글