ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체지향 프로그래밍 입문 강의 (2) - 다형성과 추상화
    et cetera/TIL 2021. 12. 29. 03:48
    반응형

    다형성(Polymorphism)

    - 여러 모습을 갖는 것

    - 객체 지향에서는 한 객체가 여러 타입을 갖는 것

    - 타입 상속으로 구현

    → 타입을 상속했다 = 하위 타입은 상위 타입도 된다 = 한 객체가 여러 타입을 가질 수 있다.

    public class Timer {
        public void start() { ... }
        public void stop() { ... }
    }
    
    public interface Rechargeable {
        void charge();
    }
    
    public class IotTimer extends Timer implements Rechargeable {
        public void charge() { ... }
    }

    IotTimer 객체는 Timer 타입도 되고, Rechargeable 타입도 된다.

    그래서 다음과 같이 IotTimer는 Timer 타입에 할당할 수도, Rechargeable 타입에 할당할 수도 있다.

    IotTimer it = new IotTimer();
    it.start();
    it.stop();
    
    Timer t = it;
    t.start();
    t.stop();
    
    Rechargeable r = it;
    r.charge();

     

     

     추상화(Abstraction)

    - 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미 있는 표현으로 정의하는 과정

    - 두 가지 방식의 추상화

        - 특정한 성질을 뽑아내는 방식

           ex) 사용자에게서 id, name, email을 뽑아내서 user 테이블로 추상화

        - 공통 성질을 뽑아내는 방식(일반화)

           ex) HP M*** 모델과 Samsung SL-M2*** 모델의 공통점을 뽑아내서 '프린터'로 추상화

    - 여기서 '다형성'은 '공통 성질을 뽑아내는 추상화 방식'과 관련이 있다.

        - 서로 다른 구현의 공통 특징을 뽑아서 추상화시키는 것.

     

    타입 추상화

    - 여러 구현 클래스를 대표하는 상위 타입 도출

        - 흔히 인터페이스 타입으로 추상화

        - 추상화 타입과 구현은 타입 상속으로 연결

    ex) EmailNotifier, SMSNotifier, KakaoNotifier 라는 콘크리트(concrete) 클래스를 추상화한 Notifier 인터페이스

    (인터페이스는 기능에 대한 의미를 제공. 구현은 제공하지 않음. 어떻게 구현할 지도 알 수 없다.)

     

    콘크리트 클래스 = 구현을 제공하는 클래스

     

     추상 타입을 이용한 프로그래밍

    Notifier notifier = getNotifier(...);
    notifier.notify(someNotification);

    getNotifier 메서드가 어떤 타입의 객체를 return 하는지에 상관없이 이 코드는

    Notifier라는 추상화한 타입을 사용해서 notify 기능을 수행할 수 있다.

    실제 notify 메서드가 어떻게 구현되어있는지는 모르지만, notify를 한다는 의도는 잘 드러난다.

     

    - 추상 타입은 구현을 감춘다.

        - 기능의 구현이 아닌, 의도를 더 잘 드러낸다.

     

     추상 타입 사용에 따른 이점: 유연함

    - 콘크리트 클래스를 직접 사용하면?

    private SmsSender smsSender;
    private KakaoPush kakaoPush;
    private MailService mailSvc;
    
    public void cancel(String ono) {
        // ... 주문 취소 처리
        
        if (pushEnabled) {
            kakaoPush.push(...);
        } else {
            smsSender.sendSms(...);
        }
        mailSvc.sendMail(...);
    }

    처음에는 주문 취소를 하면 문자를 보내는 서비스를 구현했다고 하자.

    그런데, 요구사항이 추가되어 카카오톡으로 푸시가 가능하면 카카오 푸시를 하도록 했다.

    또, 하다보니 이메일로도 주문 취소를 알려줘야할 필요성이 느껴져서 메일도 추가했다.

     

    '주문 취소'라는 본질과는 크게 상관없는 문자, 카카오톡, 메일 같은 것들때문에

    주문 취소 기능을 구현한 cancel 메서드의 구현이 덩달아 계속해서 바뀌어야 하는 상황이다.

    위에 주석으로 표현된 '주문 취소 처리 로직' 자체는 하나도 바뀐 게 없는데도 말이다.

     

    그러면 이제, 문자, 카카오톡, 메일 세 가지의 공통점을 도출하여 추상화해보자.

    이들은 모두 주문 취소를 '통지하는 방식' 이라는 공통점을 가진다.

    이는 Notifier라는 인터페이스로 추상화할 수 있겠다.

    private Notifier getNotifier(...) {
        if (pushEnabled) 
            return new KakaoNotifier();
        else 
            return new SmsNotifier();       
    }
    
    public void cancel(String ono) {
        // ...주문 취소 처리
        
        Notifier notifier = getNotifier(...);
        notifier.notify(...);    
    }

    여기서 Notifier를 실제로 생성하는 코드인 getNotifier,

    즉 객체를 생성하는 기능 자체도 NotifierFactory로 추상화를 해보자.

    (사용할 대상 접근도 추상화)

    public interface NotifierFactory {
        Notifier getNotifier(...);
        
        static NotifierFactory instance() {
            return new DefaultNotifierFactory();
        }
    }
    
    public class DefaultNotifierFactory implements NotifierFactory {
        public Notifier getNotifier(...) {
            if (pushEnabled) return new KakaoNotifier();
            else return new SmsNotifier();
        }
    }
    
    public void cancel(String ono) {
        // ...주문 취소 처리
        Notifier notifier = NotifierFactory.instance().getNoitifier(...);
        notifier.notify(...);
    }

    만약 여기서 notify 방식을 변경하라는 요구사항이 들어왔을 때는 DefaultNotifierFactory의 코드만 바꿔주면 된다.

    혹은 새로운 NotifierFactory 구현 코드를 만들어서

    NotifierFactory interface의 instance 메서드가 그걸 return하도록 만들어줘도 되고.

    중요한 사실은, notify하는 방식이 바뀌더라도 cancel 메서드는 바뀌지 않는다는 것.

    주문 취소 로직이 바뀌지 않는다는 것이다.

     

     추상화를 하는 시점

    그렇다고 해서, 무턱대로 추상화를 하면 안된다.

    '추상화를 한다 = 새로운 추상 타입이 생긴다 = 프로그래밍 복잡도가 증가한다' 이기 때문.

    - 아직 존재하지 않는 기능에 대한 이른 추상화는 주의: 잘못된 추상화 가능성, 복잡도만 증가

    - 실제 변경/확장이 발생할 때 추상화 시도

     

     추상화를 잘 하려면

    - 구현을 한 이유가 무엇인지를 잘 생각해야 한다.

     

    OCP(Open-Closed Principle)

    개방-폐쇄 원칙.

    확장에는 열려있어야 하고, 변경에는 닫혀있어야 한다는 원칙이다.

    기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 변경하지 않아야 한다는 원칙.

    변경이나 확장 비용을 낮춰줄 수 있게 된다.

    반응형

    댓글

Designed by Tistory.