본문 바로가기
Programming Language 이해하기/Java 이해하기

객체지향의 재사용성과 다이나믹 디스패치, 더블 디스패치에 관한 이야기

by simplify-len 2021. 3. 6.

토비의 스프링 저자 이일민님의 유튜브 강의 내용입니다.

Dependency 란 무엇인가?

- 의존 관계(dependency relationship)
- Supplier의 변화가 Client에 영향을 주는 경우

우리는 어떤 것을 보고 의존관계라고 말할 수 있을까?

- Supplier가 Client의 빌드일때,
- Supplier가 Client 메소드의 파라미터
- Supplier가 Client 로컬 변수
- Supplier로 메세지를 보낼때


Dependency는 재사용가능한 객체지향 설계/개발에 대해서
- Client는 재사용이 어렵다.
- Client는 컴포넌트/서비스가 될 수 없다.

"컴포넌트란 이를 만든 개발자의 손이 미치지 않는 곳에서도 아무 변경 없이, 필요에 따라 확장해서 사용될 수 있는 소프트웨어 덩어리다."

그림1 - GoF에서 말하는 오브젝트 관계

 

디자인 패턴에서 말하는 디팬던시는, 컴파일 타임이 아니라 런타임시에 결정/구성되는 오브젝트 의존 관계

1. 구현 대신 인터페이스를 사용한다.
- 클래스(구현) 의존 관계 제거
- 클래스에 대한 의존성은 생성 패턴처럼 3자에게 위임

2. 오브젝트 합성(composition) 사용
- 재사용성을 확보하기 위한 방법의 한 가지(상속의 대안)
- 인터페이스 사용이 전제(블랙 박스 재사용)
- 새롭고 복잡한 기능을 얻기 위해서 오브젝트를 조합/합성
- 런타임시에 다른 오브젝트에 대한 레퍼런스 획득
- 각 클래스가 캡슐화되고 자신의 역할에 충실하게 도와줌


Inversion of Control? 

인터페이스로서 의존성을 가질 수 있다.

그림2 Inversion of Control 는 프레임워크이다.

이제 본격적으로 강의에 대한 내용을 말합니다.

Static Dispatch

public class Dispatch {

    static class Service {
        void run() {
            System.out.println("run()");
        }
    }
    public static void main(String[] args) {
        new Service().run();
    }
}

위 코드에서 컴파일러는 시작되는 시점에 run이라는 코드가 동작되는 것을 알고 있습니다. 그래서 byte 코드안에 남아 있다. 

아래 코드 또한 정적 디스패치이다.

public class Dispatch {

    static class Service {
        void run(int number) {
            System.out.println("run("+ number +")");
        }

        void run(String msg) {
            System.out.println("run("+ msg + ")");
        }
    }
    public static void main(String[] args) {
        new Service().run(1);
        new Service().run("Dispatch");
    }
}

 

Dynamic Dispath

package com.like.len.springbootstudy;

public class Dispatch {

    static abstract class Service {
        abstract void run();
    }

    static class MyService1 extends Service {
        @Override
        void run() {
            System.out.println("run1");
        }
    }
    static class MyService2 extends Service {
        @Override
        void run() {
            System.out.println("run2");
        }
    }


    public static void main(String[] args) {
        Service sev = new MyService1();
        
		sev.run(); // receiver parameter
        
        List<Service> services = Arrays.asList(new MyService1(), new MyService2());
        services.forEach(Service::run);
    }
}

 

컴파일 시점에서는 어떤 서비스가 실행될지 결정하지 못합니다. 런타임시점에서 sev 가 어떤 클래스에 의해 구현되었는지 알수 있습니다.

 

(번외)

Method Signature VS Method Type

Method Signature은 이름, 파라미터 타입까지만 일치하면 메서드 시그니처라고 합니다. 리턴타입은 중요한게 아닙니다. Method Type은 이름이 없고, 리턴 타입과 method parameter type 그리고 method argument types, exception 일치할 때, 메서드 레퍼런스를 쓸 수 있다.

[참고자료]

www.logicbig.com/tutorials/core-java-tutorial/java-8-enhancements/explicit_receiver_parameters.html

 

Java 8 - What are explicit receiver parameters in Java 8?

Explicit Receiver Parameters [Last Updated: Feb 16, 2016]

www.logicbig.com

 

다이나믹 디스패치?

package com.like.len.springbootstudy;

import java.util.Arrays;
import java.util.List;

public class Dispatch {

    interface Post {
        void postOn(SNS sns);
    }

    static class Text implements Post {

        @Override
        public void postOn(SNS sns) {
            System.out.println("text -> " + sns.getClass().getSimpleName());
        }
    }

    static class Picture implements Post {

        @Override
        public void postOn(SNS sns) {
            System.out.println("Picture -> " + sns.getClass().getSimpleName());
        }
    }

    interface SNS {
    }

    static class Facebook implements SNS {
    }

    static class Twitter implements SNS {
    }


    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(p::postOn));

    }
}

로직에 따른 조합이 코드에서는 2가지 밖에 없지만, 생각해볼 수 있는 조합은 총 4가지가 있습니다.

가장 Raw한 형태의 코드는 다음과 같다.

import java.util.Arrays;
import java.util.List;

public class Dispatch {

    interface Post {
        void postOn(SNS sns);
    }

    static class Text implements Post {

        @Override
        public void postOn(SNS sns) {
            if (sns instanceof Facebook) {
                System.out.println("text -> " + "facebook");
            }

            if (sns instanceof Twitter) {
                System.out.println("text -> " + "Twitter");
            }
        }
    }

    static class Picture implements Post {

        @Override
        public void postOn(SNS sns) {
            if (sns instanceof Facebook) {
                System.out.println("Picture -> " + "facebook");
            }

            if (sns instanceof Twitter) {
                System.out.println("Picture -> " + "Twitter");
            }
        }
    }

    interface SNS {
    }

    static class Facebook implements SNS {
    }

    static class Twitter implements SNS {
    }


    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(p::postOn));

    }
}

 

여기서 문제는 If 문 사용하므로 발생하는 문제점은 만약 GooglePlus 를 추가한다면 어떻게 될까?

if( sns instanceof GooglePlus) {...} 코드가 추가되게 됩니다.

그렇다면 이렇게 코드를 짜야될까요? 그러면 정적 컴파일러가 오류가 났다고 말합니다.

package com.like.len.springbootstudy;

import java.util.Arrays;
import java.util.List;

public class Dispatch {

    interface Post {
        void postOn(Facebook sns);

        void postOn(Twitter sns);
    }

    static class Text implements Post {

        @Override
        public void postOn(Facebook sns) {
            System.out.println("text -> " + "facebook");
        }

        @Override
        public void postOn(Twitter sns) {
            System.out.println("text -> " + "Twitter");
        }
    }

    static class Picture implements Post {

        @Override
        public void postOn(Facebook sns) {
            System.out.println("Picture -> " + "facebook");
        }

        @Override
        public void postOn(Twitter sns) {
            System.out.println("Picture -> " + "Twitter");
        }
    }

    interface SNS {
    }

    static class Facebook implements SNS {
    }

    static class Twitter implements SNS {
    }

    static class GooglePlus implements SNS {
    }


    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(p::postOn));

    }
}

 

그래서 더블 디스패처를 사용합니다.

import java.util.Arrays;
import java.util.List;

public class Dispatch {

    interface Post {
        void postOn(SNS sns);
    }

    static class Text implements Post {

        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    static class Picture implements Post {

        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    interface SNS {
        void post(Text post);
        void post(Picture post);
    }

    static class Facebook implements SNS {
        @Override
        public void post(Text post) {
            System.out.println("text -> facebook");
        }

        @Override
        public void post(Picture post) {
            System.out.println("Picture -> facebook");
        }
    }

    static class Twitter implements SNS {
        @Override
        public void post(Text post) {
            System.out.println("text -> Twitter");
        }

        @Override
        public void post(Picture post) {
            System.out.println("Picture -> Twitter");

        }
    }


    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());

        posts.forEach(p -> sns.forEach(p::postOn));
    }
}

 

그럼 이걸 왜 써야하는지에 대한 이유는 아까와 같이 만약 GooglePlus가 추가된다면?

package com.like.len.springbootstudy;

import java.util.Arrays;
import java.util.List;

public class Dispatch {

    interface Post {
        void postOn(SNS sns);
    }

    static class Text implements Post {

        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    static class Picture implements Post {

        @Override
        public void postOn(SNS sns) {
            sns.post(this);
        }
    }

    interface SNS {
        void post(Text post);
        void post(Picture post);
    }

    static class Facebook implements SNS {
        @Override
        public void post(Text post) {
            System.out.println("text -> facebook");
        }

        @Override
        public void post(Picture post) {
            System.out.println("Picture -> facebook");
        }
    }

    static class Twitter implements SNS {
        @Override
        public void post(Text post) {
            System.out.println("text -> Twitter");
        }

        @Override
        public void post(Picture post) {
            System.out.println("Picture -> Twitter");

        }
    }
    static class GooglePlus implements SNS {
        @Override
        public void post(Text post) {
            System.out.println("text -> Gp");
        }

        @Override
        public void post(Picture post) {
            System.out.println("Picture -> Gp");

        }
    }


    public static void main(String[] args) {
        List<Post> posts = Arrays.asList(new Text(), new Picture());
        List<SNS> sns = Arrays.asList(new Facebook(), new Twitter(), new GooglePlus());

        posts.forEach(p -> sns.forEach(p::postOn));
    }
}

 

의존하고 있는 코드에 영향을 미치지 않습니다.

댓글