3 분 소요

💡 객체 지향 프로그래밍 입문 시리즈

인프런의 객채 지향 프로그래밍 입문을 기반으로 작성한 게시물 입니다.
(본 시리즈의 모든 예시는 JavaScript(TypeScript)로 변환하여 작성하였습니다.)
객체 지향 프로그래밍 입문_1
객체 지향 프로그래밍 입문_2
객체 지향 프로그래밍 입문_3
객체 지향 프로그래밍 입문_4
객체 지향 프로그래밍 입문_5
객체 지향 프로그래밍 입문_6
객체 지향 프로그래밍 입문_7


“다형성과 추상화”

1. 다형성이란?

한 객체가 여러 타입을 갖는 것

  • 즉 한 객체가 여러 타입의 기능을 제공
  • 타입 상속으로 기능을 구현

예)

public class Timer {
    public start(): void {}
    public stop(): void {}
}

public interface Rechargerble() {
    charge(): void;
}

public class IotTimer extends Timer implements Rechargeable {
    public charge(): void {
        ...	
    }
}

const it: IotTimer = new IotTimer();
it.start();
it.stop();

const t:Timer = it;
t.start();
t.stop();

const r: Rechargeable = it;
r.charge();

IotTimerTimer 클래스와 Rechargeable 인터페이스를 상속하고 있기 때문에 Timer 클래스와 Rechargeable 모두에 할당이 가능합니다.

그렇다면 다형성을 왜 추구하는 것일까요.

추상화에 용이하기 때문입니다.

2. 추상화란?

추상화란 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미 있는 표현으로 정의하는 과정입니다.

추상화는 두가지 방식이 있습니다.

  1. 특정한 설징: 예) 사용자 테이블에서 아이디, 이름 이메일 등의 특정한 성질을 추상화 하는 것.
  2. 공통 성질(일반화): 예) 지포스와 라데온은 GPU라는 공통 성질로 추상화가 가능하다.

이 두가지 추상화 방식중 다형성은 공통 성질을 뽑아내는 방식과 관련이 있습니다.

예를 들어 아래와 같은 상황이 존재한다고 가정하겠습니다.

추상화이미지1

왼쪽의 세가지 기능이 결국은 푸시 발송 요청을 위한 기능이라고 할 때

이 세 구현 클래스를 대표하는 상위 타입을 도출하여 상속으로 연결하는 것이 추상화입니다.

추상화이미지2

:point_up: Notifier라는 인터페이스로 추상화를 진행하고 이후 타입 상속으로 연결해준다.


3. 추상 타입 사용

이렇게 구축된 추상 클래스를 이용하여 프로그래밍을 진행하면 되는데

여기서 주의해야 할 점은 추상 타입은 구현을 드러내지 않고 감추고 있어야 합니다.

const notifier: Notifier = getNotifier();
notifier.notiry(someNoti);

위 코드에서 getNotifier가 어떻게(어떤 클래스를 통해서) 통제하는지 알 수 없지만 기능 구현의 의도는 파악이 가능하게 됩니다.

4. 추상 타입 사용에 따른 이점

1. 유연함

추상화 사용 전 코드

// 1. 최초 요구사항
// 주문 취소시 sms 전송
private const smsSender: SmsSender;
public cancel(ono:string): void {
    ...주문 취소 코드
    smsSender.sendSms();
}
    
// 2. 추가 요구사항
// kakao Push가 가능하면 푸시 알림도 전송
public cancel(ono: string): void {
    ...주문 취소 코드
    if(pushEnabled) {
        kakaoPush.push();
    } else {
        smsSender.sendSms();
    }
}
    
// 3. 또다른 요구사항
// 메일도 가능하다면 보내라.
public cancel(ono: string): void {
    ...주문 취소 코드
    if(pushEnabled) {
        kakaoPush.push();
    } else {
        smsSender.sendSms();
    }
        mailSvc.sendMail();
    }


위의 코드를 살펴보면 sms 전송, 카카오 푸시 그리고 메일 전송은 주문 취소의 본질적인 기능과 전혀 상관이 없는 기능입니다.

하지만 요구사항이 추가됨에 따라 주문 취소 코드 자체가 변화하게 됩니다.

추상 타입을 이용한 코드

public cancel(ono:string): void {
    ...주문 취소 코드
    const notifier: Notifier = getNotifier(...);
    notifier.notify(...);
}
    
private getNotifier(...): Notifier {
    if(pushEnabled) return new KakaoNotifier();
    return new SmsNotifier();
}
    
// 한번 더 추상화
    
public cancel(ono:string): void {
    ...주문 취소 코드
    const notifier: Notifier = NotifierFactory.instance().getNotifier(...);
    notifier.notify(...);
}
    
public interface NotifierFactory {
    getNorifier(...): Notifier
    	
    static instance(): NotifierFactory {
        return new DefaultNotifierFactory();
    }
}
    
public class DefaultNotifierFactory implements NotifierFactory {
    public getNotifier(...): Notifier {
        if(pushEnabled) return new KakaoNotifier();
        return new SmsNotifier();
    }
}

위 코드의 장점은 향후 추가적인 알림 방식이 추가되어도 DefaultNorifierFactory 의 코드만 변경하면 되고 실제 주문 취소를 하는 기능은 변경하지 않아도 된다는 것입니다.

5. 추상화 하는 시점

추상화는 무턱대고 하는 것이 아니라 의존 대상이 변경하는 시점에 해주는 것이 좋습니다.

즉, 아직 존재하지도 않는 기능에 대해서 미리 추상화를 해버린다면 잘못된 추상화가 될 가능성도 있을 뿐만 아니라 추상 타입의 증가로 인한 코드의 복잡성이 늘어나게 됩니다.

따라서 추상화는 이미 구현된 기능에 한해서 변경이나 확장이 발생할 때 시도하는 것이 좋습니다.

예를 들어

public class OrderService{
    private mailSender: MailSender ;

    public order(...): void {
        ...
        sender.send(message);		
    }
}
// 위의 단계에서는 추상화가 필요 없다.
// 왜나하면 현재 주문 기능에서는 다른 전송 방식이 필요하지 않고
// 이메일 전송 기능만 필요로 하기 때문이다.
// 하지만 향후 다른 전송 방식이 필요하게 될 경우 추상화를 시도할 시점이 된다.

댓글남기기