본문 바로가기

추상 클래스와 인터페이스

공통 상태를 가진 추상 클래스와 구현 계약만 제공하는 인터페이스를 비교하고 게시글 발행 역할을 여러 구현에 적용합니다.

추상 클래스는 공통 상태와 구현을 물려주는 클래스 계층입니다.

인터페이스는 구현 클래스가 따라야 할 역할 계약을 정의하며 클래스 상속 관계와 별도로 적용할 수 있습니다.

둘 다 직접 인스턴스화할 수 없는 타입으로 다형적 참조에 사용되지만 목적과 제약이 다릅니다.


인터페이스 구현 의무

lab/IncompletePublishablePost.java
public final class IncompletePublishablePost {
    private interface Publishable {
        String title();
        void publish();
    }

    private static final class TextPost implements Publishable {
        @Override
        public void publish() {
            System.out.println("publish text post");
        }
    }
}
컴파일 실패 관찰
error: TextPost is not abstract and does not override abstract method title()

TextPost를 구체 클래스로 선언했으므로 Publishable의 모든 추상 메서드를 구현해야 합니다.

구현을 다음 자식에 맡기려면 TextPost도 abstract로 선언해야 합니다.

인터페이스는 구현 누락을 컴파일 경계에서 찾습니다.

인터페이스 메서드는 기본적으로 public abstract입니다.

구현 메서드의 접근 범위를 package-private으로 줄이면 public 계약을 만족하지 못합니다.

@Override public을 명시하면 외부 역할 구현이라는 사실이 드러납니다.


추상 클래스의 공통 구현

src/AbstractPostTemplate.java
public final class AbstractPostTemplate {
    public static void main(String[] args) {
        Post post = new CodePost("로그인 구현", 50);
        post.publish();
    }

    private abstract static class Post {
        private final String title;
        private final int viewCount;

        Post(String title, int viewCount) {
            this.title = title;
            this.viewCount = viewCount;
        }

        final void publish() {
            System.out.println("validate=" + title);
            renderBody();
            System.out.println("saved=" + viewCount);
        }

        protected abstract void renderBody();
    }

    private static final class CodePost extends Post {
        CodePost(String title, int viewCount) {
            super(title, viewCount);
        }

        @Override
        protected void renderBody() {
            System.out.println("body=code");
        }
    }
}
validate=로그인 구현
body=code
saved=50

부모 publish는 공통 발행 순서를 완성하고 중간 본문 렌더링만 추상 메서드로 자식에 맡깁니다.

publish를 final로 두어 자식이 이 메서드를 오버라이딩하지 못하게 했습니다.

validate=·saved=는 실행 순서를 관찰하는 콘솔 문자열입니다. 실제 입력 검증이나 DB·파일 저장은 없습니다. final은 메서드 재정의를 금지하며, renderBody()가 반드시 정상 반환하도록 만들지는 않습니다. 자식 호출이 예외로 끝나면 현재 메서드는 마지막 출력에 도달하지 못합니다.

추상 클래스는 생성자와 인스턴스 필드를 가질 수 있어 공통 상태 초기화에 적합합니다.

final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다

부모 final publish에서 validate 출력→자식 renderBody→부모 재개/saved 출력이 이어집니다. 자식 예외 시 마지막 출력에 미도달하며 실제 검증/저장이 없다는 조건을 유지한 호출 전환입니다.

final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다

AbstractPostTemplate의 실제 정상 실행에서 부모 publish의 두 println 사이에 CodePost.renderBody가 호출됩니다.

부모의 final publish 순서 안에서 자식 renderBody를 호출합니다

AbstractPostTemplate · Post post = new CodePost("로그인 구현", 50);에서 post.publish()를 한 번 호출합니다.

한 번의 publish 호출 안에서 실행되는 세 문장
순서 · 실행 구현수행 내용콘솔 출력
1 · Post.publishtitle 필드를 읽어 첫 문장을 출력validate=로그인 구현
2 · CodePost.renderBody부모의 renderBody() 호출이 자식 구현을 실행body=code
3 · Post.publish자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력saved=50
부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 같은Post.publish final부모앞부분validate→CodePost renderBody bodycode→정상반환뒤부모saved50;클래스구현소속영역.실제검증저장없고예외시끝출력보장없음. Post.publish · 부모 구현 CodePost · 자식 구현 부모의 final 메서드 validate=로그인 구현 renderBody() 호출 renderBody() body=code 정상 반환 자식 구현 선택 정상 반환 뒤 부모 실행 재개 saved=50 final은 재정의만 금지 validate/saved는 출력라벨 · 실제 검증·저장 없음 자식 호출이 예외로 끝나면 saved 출력에 도달하지 못함
부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 같은Post.publish final부모앞부분validate→CodePost renderBody bodycode→정상반환뒤부모saved50;클래스구현소속영역.실제검증저장없고예외시끝출력보장없음. Post.publish · 앞부분 부모의 final 메서드 validate=로그인 구현 renderBody() 호출 자식 구현 선택 CodePost.renderBody 자식 구현 body=code 정상 반환 정상 반환 뒤 Post.publish · 뒷부분 부모 실행 재개 saved=50 final은 재정의만 금지 출력라벨 ≠ 실제 저장 자식 예외면 뒷부분 실행 안 함
1 · Post.publish
수행 내용: title 필드를 읽어 첫 문장을 출력
콘솔 출력: validate=로그인 구현
2 · CodePost.renderBody
수행 내용: 부모의 renderBody() 호출이 자식 구현을 실행
콘솔 출력: body=code
3 · Post.publish
수행 내용: 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력
콘솔 출력: saved=50

final publish()는 자식이 이 메서드를 오버라이딩하지 못하게 합니다. renderBody()는 추상 메서드이며 이 예제의 CodePost가 구현합니다.

validate와 saved는 출력 문자열입니다. 실제 유효성 검사나 저장소 쓰기는 없으며, 자식 호출이 예외로 끝나도 마지막 출력이 보장된다는 뜻이 아닙니다.

이 예의 조건

  • final은 재정의 금지이며 자식 예외 때 saved 출력을 보장하지 않습니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2같은 Post.publish의 final 부모 앞부분 validate→CodePost의 renderBody body code→정상 반환 뒤 부모 saved 50이다. 영역은 클래스 구현의 소속을 나타낸다. 실제 검증·저장은 없고 예외 시 마지막 출력을 보장하지 않는다.
16validate/saved는 출력 라벨 · 실제 검증·저장 없음
원 도표의 설명과 주석
구분내용
설명같은 Post.publish의 final 부모 앞부분 validate→CodePost의 renderBody body code→정상 반환 뒤 부모 saved 50이다. 영역은 클래스 구현의 소속을 나타낸다. 실제 검증·저장은 없고 예외 시 마지막 출력을 보장하지 않는다.
전체 읽기 정보final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다 final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다 AbstractPostTemplate의 실제 정상 실행에서 부모 publish의 두 println 사이에 CodePost.renderBody가 호출됩니다. 부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 AbstractPostTemplate · Post post = new CodePost("로그인 구현", 50); 에서 post.publish() 를 한 번 호출합니다. 한 번의 publish 호출 안에서 실행되는 세 문장 순서 · 실행 구현 수행 내용 콘솔 출력 1 · Post.publish title 필드를 읽어 첫 문장을 출력 validate=로그인 구현 2 · CodePost.renderBody 부모의 renderBody() 호출이 자식 구현을 실행 body=code 3 · Post.publish 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력 saved=50 1 · Post.publish 수행 내용: title 필드를 읽어 첫 문장을 출력 콘솔 출력: validate=로그인 구현 2 · CodePost.renderBody 수행 내용: 부모의 renderBody() 호출이 자식 구현을 실행 콘솔 출력: body=code 3 · Post.publish 수행 내용: 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력 콘솔 출력: saved=50 final publish() 는 자식이 이 메서드를 오버라이딩하지 못하게 합니다. renderBody() 는 추상 메서드이며 이 예제의 CodePost 가 구현합니다. validate 와 saved 는 출력 문자열입니다. 실제 유효성 검사나 저장소 쓰기는 없으며, 자식 호출이 예외로 끝나도 마지막 출력이 보장된다는 뜻이 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
부모 시작

Post.publish가 validate=로그인 구현을 출력합니다.

  • 구현 소유: 부모 final
  • 다음: renderBody 호출
  • 부모 템플릿이 실행 순서를 정합니다.
자식 훅

CodePost.renderBody가 body=code를 출력합니다.

  • 구현 소유: 자식
  • 복귀: 정상 반환 전제
  • final 메서드 안에서도 자식의 다른 메서드를 선택합니다.
부모 재개

정상 반환 뒤 saved=50을 출력합니다.

  • 구현 소유: 부모
  • 의미: 출력 라벨, 실제 저장 없음
  • 자식 호출 실패에서는 이후 마무리에 못 갑니다.

final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다

부모 final publish에서 validate 출력→자식 renderBody→부모 재개/saved 출력이 이어집니다. 자식 예외 시 마지막 출력에 미도달하며 실제 검증/저장이 없다는 조건을 유지한 호출 전환입니다.

final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다

AbstractPostTemplate의 실제 정상 실행에서 부모 publish의 두 println 사이에 CodePost.renderBody가 호출됩니다.

부모의 final publish 순서 안에서 자식 renderBody를 호출합니다

AbstractPostTemplate · Post post = new CodePost("로그인 구현", 50);에서 post.publish()를 한 번 호출합니다.

한 번의 publish 호출 안에서 실행되는 세 문장
순서 · 실행 구현수행 내용콘솔 출력
1 · Post.publishtitle 필드를 읽어 첫 문장을 출력validate=로그인 구현
2 · CodePost.renderBody부모의 renderBody() 호출이 자식 구현을 실행body=code
3 · Post.publish자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력saved=50
부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 같은Post.publish final부모앞부분validate→CodePost renderBody bodycode→정상반환뒤부모saved50;클래스구현소속영역.실제검증저장없고예외시끝출력보장없음. Post.publish · 부모 구현 CodePost · 자식 구현 부모의 final 메서드 validate=로그인 구현 renderBody() 호출 renderBody() body=code 정상 반환 자식 구현 선택 정상 반환 뒤 부모 실행 재개 saved=50 final은 재정의만 금지 validate/saved는 출력라벨 · 실제 검증·저장 없음 자식 호출이 예외로 끝나면 saved 출력에 도달하지 못함
부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 같은Post.publish final부모앞부분validate→CodePost renderBody bodycode→정상반환뒤부모saved50;클래스구현소속영역.실제검증저장없고예외시끝출력보장없음. Post.publish · 앞부분 부모의 final 메서드 validate=로그인 구현 renderBody() 호출 자식 구현 선택 CodePost.renderBody 자식 구현 body=code 정상 반환 정상 반환 뒤 Post.publish · 뒷부분 부모 실행 재개 saved=50 final은 재정의만 금지 출력라벨 ≠ 실제 저장 자식 예외면 뒷부분 실행 안 함
1 · Post.publish
수행 내용: title 필드를 읽어 첫 문장을 출력
콘솔 출력: validate=로그인 구현
2 · CodePost.renderBody
수행 내용: 부모의 renderBody() 호출이 자식 구현을 실행
콘솔 출력: body=code
3 · Post.publish
수행 내용: 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력
콘솔 출력: saved=50

final publish()는 자식이 이 메서드를 오버라이딩하지 못하게 합니다. renderBody()는 추상 메서드이며 이 예제의 CodePost가 구현합니다.

validate와 saved는 출력 문자열입니다. 실제 유효성 검사나 저장소 쓰기는 없으며, 자식 호출이 예외로 끝나도 마지막 출력이 보장된다는 뜻이 아닙니다.

이 예의 조건

  • final은 재정의 금지이며 자식 예외 때 saved 출력을 보장하지 않습니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2같은 Post.publish의 final 부모 앞부분 validate→CodePost의 renderBody body code→정상 반환 뒤 부모 saved 50이다. 영역은 클래스 구현의 소속을 나타낸다. 실제 검증·저장은 없고 예외 시 마지막 출력을 보장하지 않는다.
16validate/saved는 출력 라벨 · 실제 검증·저장 없음
원 도표의 설명과 주석
구분내용
설명같은 Post.publish의 final 부모 앞부분 validate→CodePost의 renderBody body code→정상 반환 뒤 부모 saved 50이다. 영역은 클래스 구현의 소속을 나타낸다. 실제 검증·저장은 없고 예외 시 마지막 출력을 보장하지 않는다.
전체 읽기 정보final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다 final publish는 공통 순서를, 자식은 한 호출의 구현을 정한다 AbstractPostTemplate의 실제 정상 실행에서 부모 publish의 두 println 사이에 CodePost.renderBody가 호출됩니다. 부모의 final publish 순서 안에서 자식 renderBody를 호출합니다 AbstractPostTemplate · Post post = new CodePost("로그인 구현", 50); 에서 post.publish() 를 한 번 호출합니다. 한 번의 publish 호출 안에서 실행되는 세 문장 순서 · 실행 구현 수행 내용 콘솔 출력 1 · Post.publish title 필드를 읽어 첫 문장을 출력 validate=로그인 구현 2 · CodePost.renderBody 부모의 renderBody() 호출이 자식 구현을 실행 body=code 3 · Post.publish 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력 saved=50 1 · Post.publish 수행 내용: title 필드를 읽어 첫 문장을 출력 콘솔 출력: validate=로그인 구현 2 · CodePost.renderBody 수행 내용: 부모의 renderBody() 호출이 자식 구현을 실행 콘솔 출력: body=code 3 · Post.publish 수행 내용: 자식 호출이 정상 반환된 뒤 viewCount 필드를 읽어 출력 콘솔 출력: saved=50 final publish() 는 자식이 이 메서드를 오버라이딩하지 못하게 합니다. renderBody() 는 추상 메서드이며 이 예제의 CodePost 가 구현합니다. validate 와 saved 는 출력 문자열입니다. 실제 유효성 검사나 저장소 쓰기는 없으며, 자식 호출이 예외로 끝나도 마지막 출력이 보장된다는 뜻이 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
부모 시작

Post.publish가 validate=로그인 구현을 출력합니다.

  • 구현 소유: 부모 final
  • 다음: renderBody 호출
  • 부모 템플릿이 실행 순서를 정합니다.
자식 훅

CodePost.renderBody가 body=code를 출력합니다.

  • 구현 소유: 자식
  • 복귀: 정상 반환 전제
  • final 메서드 안에서도 자식의 다른 메서드를 선택합니다.
부모 재개

정상 반환 뒤 saved=50을 출력합니다.

  • 구현 소유: 부모
  • 의미: 출력 라벨, 실제 저장 없음
  • 자식 호출 실패에서는 이후 마무리에 못 갑니다.

인터페이스의 역할 부여

src/PublishableInterface.java
public final class PublishableInterface {
    public static void main(String[] args) {
        Publishable[] posts = {
                new TextPost("가입 인사"),
                new CodePost("로그인 구현")
        };

        for (Publishable post : posts) {
            System.out.println("title=" + post.title());
            post.publish();
        }
    }

    private interface Publishable {
        String title();
        void publish();
    }

    private static final class TextPost implements Publishable {
        private final String title;
        TextPost(String title) { this.title = title; }
        @Override public String title() { return title; }
        @Override public void publish() { System.out.println("text=" + title); }
    }

    private static final class CodePost implements Publishable {
        private final String title;
        CodePost(String title) { this.title = title; }
        @Override public String title() { return title; }
        @Override public void publish() { System.out.println("code=" + title); }
    }
}
title=가입 인사
text=가입 인사
title=로그인 구현
code=로그인 구현

두 클래스는 필드 구조와 상속 부모가 달라도 Publishable 역할을 구현할 수 있습니다.

클라이언트는 구체 클래스가 아니라 title과 publish에 정의된 기능만 사용합니다.

클래스가 인터페이스를 구현할 때는 implements를 사용합니다.

인터페이스의 필드는 모두 public static final 상수입니다.

인스턴스 상태를 저장하지 않습니다.

메서드는 추상 메서드 외에도 default, static, private 메서드를 가질 수 있지만 인터페이스의 중심은 역할 계약입니다.


추상 클래스와 인터페이스

질문추상 클래스인터페이스
공통 인스턴스 상태가 필요한가적합상태 필드 없음
공통 생성 과정이 필요한가생성자 제공생성자 없음
단일 계층인가클래스 상속 하나여러 역할 구현 가능
관련 없는 타입도 계약을 가져야 하는가부자연스러울 수 있음적합
공통 구현이 필요한가일반 메서드default 제한적 사용

하나를 무조건 고르는 대신 함께 사용할 수 있습니다.

AbstractPost가 공통 상태를 제공하고 Exportable 인터페이스가 내보내기 역할을 추가하는 식입니다.

클래스 상속은 하나만 가능하지만 인터페이스는 여러 개 구현할 수 있습니다.

인터페이스 이름은 구현 기술보다 역할을 표현합니다.

JsonPost처럼 특정 형식을 고정하기보다 Exportable, Moderatable, Publishable처럼 클라이언트가 기대하는 능력을 나타냅니다.


발행기 인터페이스

src/PublicationRunner.java
public final class PublicationRunner {
    public static void main(String[] args) {
        Publisher publisher = new Publisher();
        publisher.publish(new TextPost());
        publisher.publish(new CodePost());
    }

    private static final class Publisher {
        void publish(Publishable post) {
            System.out.println("publisher:validate");
            PublishResult result = post.publish();
            System.out.println("publisher:saved=" + result.viewCount());
        }
    }

    private interface Publishable {
        PublishResult publish();
    }

    private record PublishResult(String title, int viewCount) {}

    private static final class TextPost implements Publishable {
        @Override
        public PublishResult publish() {
            System.out.println("publish text");
            return new PublishResult("가입 인사", 40);
        }
    }

    private static final class CodePost implements Publishable {
        @Override
        public PublishResult publish() {
            System.out.println("publish code");
            return new PublishResult("로그인 구현", 50);
        }
    }
}
publisher:validate
publish text
publisher:saved=40
publisher:validate
publish code
publisher:saved=50

Publisher는 게시글 종류를 묻지 않고 publish 결과만 받습니다.

이 Publisher는 post.publish()가 null이 아닌 PublishResult를 정상 반환한다고 기대합니다. 반환 타입이 PublishResult라는 선언만으로 null이 금지되는 것은 아닙니다. 이 예제의 두 구현은 실제 결과 객체를 반환하며, 반환된 title은 Publisher에서 읽지 않고 viewCount만 읽습니다. publisher:validate·publisher:saved도 실제 검증·저장이 아닌 관찰용 출력입니다.

새 게시글은 Publishable을 구현해 기존 Publisher에 전달합니다.

구체 구현 생성은 main 같은 조립점이 담당합니다.

반환 조건 연습: 구현이 예외 또는 null로 끝난다면?

TextPost를 전달하는 첫 Publisher 호출을 따로 실행한다고 가정합니다. 구현 A는 publish text를 출력한 다음 예외를 던지고, 구현 B는 같은 문자열을 출력한 다음 null을 반환합니다. 예외 처리는 추가하지 않습니다. 어느 경우에 publisher:saved=40을 출력하나요? 최초 publisher:validate 출력은 남나요?

풀이: 어느 경우도 saved=40을 출력하지 않습니다. A는 호출 자체가 예외로 끝나 결과 대입 이후 문장에 도달하지 못합니다. B는 result에 null을 대입한 뒤 result.viewCount() 평가에서 NullPointerException이 납니다. 두 경우 모두 호출 전에 실행한 publisher:validate와 구현의 publish text 출력은 이미 남았습니다. 역할 계약에는 반환값의 의미와 실패 조건도 필요합니다.

Publisher는 두 구현의 반환값에서 viewCount를 읽는다

Publisher가 각 구현을 호출해 별도 PublishResult를 받고 viewCount를 읽어 마무리 출력합니다. 반환 객체와 호출자 재개를 나누어 예외/null일 때 다음 단계가 실패하는 경계를 보여 줄 수 있습니다.

Publisher는 두 구현의 반환값에서 viewCount를 읽는다

PublicationRunner의 Publisher는 Publishable.publish를 호출하고 TextPost와 CodePost가 반환한 PublishResult의 viewCount를 출력합니다.

Publisher는 구현의 반환 결과에서 조회 수를 읽습니다

PublicationRunner · main은 같은 Publisher에 TextPost, CodePost를 차례로 전달합니다. 아래 행은 서로 다른 두 호출입니다.

입력 구현과 PublishResult, 호출자 출력의 대응
전달한 구현구현의 출력과 반환값Publisher가 이어서 출력
첫 호출 · TextPostpublish text

new PublishResult("가입 인사", 40) 반환

publisher:saved=40
둘째 호출 · CodePostpublish code

new PublishResult("로그인 구현", 50) 반환

publisher:saved=50
Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 Text와Code 2호출은순차독립;Publisher validate→실제구현출력및PublishResult(title가입인사/로그인구현,40/50)반환→caller viewCount읽어saved40/50,title사용없음/실제저장없음. 첫 호출 · TextPost Publisher publisher:validate post.publish() 호출 TextPost.publish() publish text 결과 객체 반환 PublishResult title="가입 인사" viewCount=40 Publisher로 복귀 result.viewCount() publisher:saved=40 다음 호출 · CodePost Publisher publisher:validate post.publish() 호출 CodePost.publish() publish code 결과 객체 반환 PublishResult title="로그인 구현" viewCount=50 Publisher로 복귀 result.viewCount() publisher:saved=50 호출마다 다른 결과 객체 호출자는 title 읽지 않음
Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 Text와Code 2호출은순차독립;Publisher validate→실제구현출력및PublishResult(title가입인사/로그인구현,40/50)반환→caller viewCount읽어saved40/50,title사용없음/실제저장없음. 첫 호출 · TextPost Publisher publisher:validate post.publish() 호출 TextPost.publish() publish text 결과 객체 반환 PublishResult title="가입 인사" viewCount=40 Publisher로 복귀 result.viewCount() publisher:saved=40 다음 호출 · CodePost Publisher publisher:validate post.publish() 호출 CodePost.publish() publish code 결과 객체 반환 PublishResult title="로그인 구현" viewCount=50 Publisher로 복귀 result.viewCount() publisher:saved=50 호출마다 다른 결과 객체 호출자는 title 읽지 않음
첫 호출 · TextPost
구현의 출력과 반환값: publish text

new PublishResult("가입 인사", 40) 반환

Publisher가 이어서 출력: publisher:saved=40
둘째 호출 · CodePost
구현의 출력과 반환값: publish code

new PublishResult("로그인 구현", 50) 반환

Publisher가 이어서 출력: publisher:saved=50

각 호출은 먼저 publisher:validate를 출력한 뒤 post.publish()를 실행합니다. 호출자는 반환된 result.viewCount()만 읽으며 title은 사용하지 않습니다.

구현 선택을 위한 종류 분기는 없습니다. validate·saved는 콘솔 라벨이며, 이 프로그램에는 실제 검증이나 데이터 저장 코드가 없습니다.

이 예의 조건

  • 두 호출은 서로 독립된 객체를 대상으로 순차적으로 실행하며 title은 호출자가 읽지 않습니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2Text와 Code의 2개 호출은 순차적으로 수행되며 서로 독립적이다. Publisher의 validate→실제 구현 출력 및 PublishResult(title 가입 인사/로그인 구현,40/50) 반환→caller가 viewCount를 읽어 saved 40/50을 출력한다. title 사용은 없으며 실제 저장도 없다.
원 도표의 설명과 주석
구분내용
설명Text와 Code의 2개 호출은 순차적으로 수행되며 서로 독립적이다. Publisher의 validate→실제 구현 출력 및 PublishResult(title 가입 인사/로그인 구현,40/50) 반환→caller가 viewCount를 읽어 saved 40/50을 출력한다. title 사용은 없으며 실제 저장도 없다.
전체 읽기 정보Publisher는 두 구현의 반환값에서 viewCount를 읽는다 Publisher는 두 구현의 반환값에서 viewCount를 읽는다 PublicationRunner의 Publisher는 Publishable.publish를 호출하고 TextPost와 CodePost가 반환한 PublishResult의 viewCount를 출력합니다. Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 PublicationRunner · main 은 같은 Publisher 에 TextPost , CodePost 를 차례로 전달합니다. 아래 행은 서로 다른 두 호출입니다. 입력 구현과 PublishResult, 호출자 출력의 대응 전달한 구현 구현의 출력과 반환값 Publisher가 이어서 출력 첫 호출 · TextPost publish text new PublishResult("가입 인사", 40) 반환 publisher:saved=40 둘째 호출 · CodePost publish code new PublishResult("로그인 구현", 50) 반환 publisher:saved=50 첫 호출 · TextPost 구현의 출력과 반환값: publish text new PublishResult("가입 인사", 40) 반환 Publisher가 이어서 출력: publisher:saved=40 둘째 호출 · CodePost 구현의 출력과 반환값: publish code new PublishResult("로그인 구현", 50) 반환 Publisher가 이어서 출력: publisher:saved=50 각 호출은 먼저 publisher:validate 를 출력한 뒤 post.publish() 를 실행합니다. 호출자는 반환된 result.viewCount() 만 읽으며 title 은 사용하지 않습니다. 구현 선택을 위한 종류 분기는 없습니다. validate · saved 는 콘솔 라벨이며, 이 프로그램에는 실제 검증이나 데이터 저장 코드가 없습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Publisher 시작

각 호출에서 publisher:validate를 출력합니다.

  • Text: 첫 호출
  • Code: 다음 호출
  • 두 구현이 동일한 호출자 경계를 사용합니다.
구현 결과

publish가 문자열을 출력하고 다른 결과 객체를 반환합니다.

  • Text: PublishResult 가입 인사,40
  • Code: PublishResult 로그인 구현,50
  • 값 반환과 콘솔 출력은 다른 산출 경계입니다.
반환값 조회

Publisher가 result.viewCount만 읽습니다.

  • 첫 출력: publisher:saved=40
  • 다음 출력: publisher:saved=50
  • saved 라벨은 실제 저장을 뜻하지 않으며 title 조회도 없습니다.

Publisher는 두 구현의 반환값에서 viewCount를 읽는다

Publisher가 각 구현을 호출해 별도 PublishResult를 받고 viewCount를 읽어 마무리 출력합니다. 반환 객체와 호출자 재개를 나누어 예외/null일 때 다음 단계가 실패하는 경계를 보여 줄 수 있습니다.

Publisher는 두 구현의 반환값에서 viewCount를 읽는다

PublicationRunner의 Publisher는 Publishable.publish를 호출하고 TextPost와 CodePost가 반환한 PublishResult의 viewCount를 출력합니다.

Publisher는 구현의 반환 결과에서 조회 수를 읽습니다

PublicationRunner · main은 같은 Publisher에 TextPost, CodePost를 차례로 전달합니다. 아래 행은 서로 다른 두 호출입니다.

입력 구현과 PublishResult, 호출자 출력의 대응
전달한 구현구현의 출력과 반환값Publisher가 이어서 출력
첫 호출 · TextPostpublish text

new PublishResult("가입 인사", 40) 반환

publisher:saved=40
둘째 호출 · CodePostpublish code

new PublishResult("로그인 구현", 50) 반환

publisher:saved=50
Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 Text와Code 2호출은순차독립;Publisher validate→실제구현출력및PublishResult(title가입인사/로그인구현,40/50)반환→caller viewCount읽어saved40/50,title사용없음/실제저장없음. 첫 호출 · TextPost Publisher publisher:validate post.publish() 호출 TextPost.publish() publish text 결과 객체 반환 PublishResult title="가입 인사" viewCount=40 Publisher로 복귀 result.viewCount() publisher:saved=40 다음 호출 · CodePost Publisher publisher:validate post.publish() 호출 CodePost.publish() publish code 결과 객체 반환 PublishResult title="로그인 구현" viewCount=50 Publisher로 복귀 result.viewCount() publisher:saved=50 호출마다 다른 결과 객체 호출자는 title 읽지 않음
Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 Text와Code 2호출은순차독립;Publisher validate→실제구현출력및PublishResult(title가입인사/로그인구현,40/50)반환→caller viewCount읽어saved40/50,title사용없음/실제저장없음. 첫 호출 · TextPost Publisher publisher:validate post.publish() 호출 TextPost.publish() publish text 결과 객체 반환 PublishResult title="가입 인사" viewCount=40 Publisher로 복귀 result.viewCount() publisher:saved=40 다음 호출 · CodePost Publisher publisher:validate post.publish() 호출 CodePost.publish() publish code 결과 객체 반환 PublishResult title="로그인 구현" viewCount=50 Publisher로 복귀 result.viewCount() publisher:saved=50 호출마다 다른 결과 객체 호출자는 title 읽지 않음
첫 호출 · TextPost
구현의 출력과 반환값: publish text

new PublishResult("가입 인사", 40) 반환

Publisher가 이어서 출력: publisher:saved=40
둘째 호출 · CodePost
구현의 출력과 반환값: publish code

new PublishResult("로그인 구현", 50) 반환

Publisher가 이어서 출력: publisher:saved=50

각 호출은 먼저 publisher:validate를 출력한 뒤 post.publish()를 실행합니다. 호출자는 반환된 result.viewCount()만 읽으며 title은 사용하지 않습니다.

구현 선택을 위한 종류 분기는 없습니다. validate·saved는 콘솔 라벨이며, 이 프로그램에는 실제 검증이나 데이터 저장 코드가 없습니다.

이 예의 조건

  • 두 호출은 서로 독립된 객체를 대상으로 순차적으로 실행하며 title은 호출자가 읽지 않습니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2Text와 Code의 2개 호출은 순차적으로 수행되며 서로 독립적이다. Publisher의 validate→실제 구현 출력 및 PublishResult(title 가입 인사/로그인 구현,40/50) 반환→caller가 viewCount를 읽어 saved 40/50을 출력한다. title 사용은 없으며 실제 저장도 없다.
원 도표의 설명과 주석
구분내용
설명Text와 Code의 2개 호출은 순차적으로 수행되며 서로 독립적이다. Publisher의 validate→실제 구현 출력 및 PublishResult(title 가입 인사/로그인 구현,40/50) 반환→caller가 viewCount를 읽어 saved 40/50을 출력한다. title 사용은 없으며 실제 저장도 없다.
전체 읽기 정보Publisher는 두 구현의 반환값에서 viewCount를 읽는다 Publisher는 두 구현의 반환값에서 viewCount를 읽는다 PublicationRunner의 Publisher는 Publishable.publish를 호출하고 TextPost와 CodePost가 반환한 PublishResult의 viewCount를 출력합니다. Publisher는 구현의 반환 결과에서 조회 수를 읽습니다 PublicationRunner · main 은 같은 Publisher 에 TextPost , CodePost 를 차례로 전달합니다. 아래 행은 서로 다른 두 호출입니다. 입력 구현과 PublishResult, 호출자 출력의 대응 전달한 구현 구현의 출력과 반환값 Publisher가 이어서 출력 첫 호출 · TextPost publish text new PublishResult("가입 인사", 40) 반환 publisher:saved=40 둘째 호출 · CodePost publish code new PublishResult("로그인 구현", 50) 반환 publisher:saved=50 첫 호출 · TextPost 구현의 출력과 반환값: publish text new PublishResult("가입 인사", 40) 반환 Publisher가 이어서 출력: publisher:saved=40 둘째 호출 · CodePost 구현의 출력과 반환값: publish code new PublishResult("로그인 구현", 50) 반환 Publisher가 이어서 출력: publisher:saved=50 각 호출은 먼저 publisher:validate 를 출력한 뒤 post.publish() 를 실행합니다. 호출자는 반환된 result.viewCount() 만 읽으며 title 은 사용하지 않습니다. 구현 선택을 위한 종류 분기는 없습니다. validate · saved 는 콘솔 라벨이며, 이 프로그램에는 실제 검증이나 데이터 저장 코드가 없습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Publisher 시작

각 호출에서 publisher:validate를 출력합니다.

  • Text: 첫 호출
  • Code: 다음 호출
  • 두 구현이 동일한 호출자 경계를 사용합니다.
구현 결과

publish가 문자열을 출력하고 다른 결과 객체를 반환합니다.

  • Text: PublishResult 가입 인사,40
  • Code: PublishResult 로그인 구현,50
  • 값 반환과 콘솔 출력은 다른 산출 경계입니다.
반환값 조회

Publisher가 result.viewCount만 읽습니다.

  • 첫 출력: publisher:saved=40
  • 다음 출력: publisher:saved=50
  • saved 라벨은 실제 저장을 뜻하지 않으며 title 조회도 없습니다.

연습 문제

PostNotifier 인터페이스에 notify(String message)를 선언하고 ConsoleNotifier와 SilentNotifier를 구현하세요.

같은 send 메서드에 두 구현을 전달해 동작 차이를 확인합니다.

해설 보기
src/PostNotifierInterface.java
public final class PostNotifierInterface {
    public static void main(String[] args) {
        send(new ConsoleNotifier(), "post published");
        send(new SilentNotifier(), "post published");
    }

    private static void send(PostNotifier notifier, String message) {
        notifier.notify(message);
    }

    private interface PostNotifier {
        void notify(String message);
    }

    private static final class ConsoleNotifier implements PostNotifier {
        @Override public void notify(String message) {
            System.out.println("console=" + message);
        }
    }

    private static final class SilentNotifier implements PostNotifier {
        @Override public void notify(String message) {
            System.out.println("silent");
        }
    }
}
console=post published
silent

send는 알림 구현을 모르고 인터페이스 역할만 호출합니다.

SilentNotifier도 계약을 구현하므로 조건 분기 없이 교체됩니다.