인터페이스 다중 구현
클래스 다중 상속의 모호함을 피하고 여러 인터페이스 역할을 게시글에 조합해 확장 가능한 게시판을 완성합니다.
자바 클래스는 부모 클래스를 하나만 상속하지만 인터페이스는 여러 개 구현할 수 있습니다.
공통 상태 계층은 단일 클래스 상속으로 유지하고, 서로 독립적인 능력은 작은 인터페이스 역할로 조합합니다.
게시글에 검수·내보내기·발행 역할을 선택적으로 붙여 종합합니다.
다중 클래스 상속 제한
public final class MultipleClassInheritance {
private static class Timed {}
private static class Named {}
private static final class Post extends Timed, Named {}
}error: '{' expected자바의 extends 뒤에는 부모 클래스 하나만 올 수 있습니다.
두 부모가 같은 이름의 필드나 메서드를 제공할 때 선택 규칙이 복잡해지는 다이아몬드 문제를 피하고 클래스 계층을 단순하게 유지합니다.
공통 상태가 필요하면 하나의 부모에 모으고, 다른 능력은 인터페이스로 표현합니다.
final class CodePost extends Post implements Moderatable, Exportable {
}쉼표로 여러 인터페이스를 구현할 수 있습니다.
각 인터페이스의 추상 메서드 계약을 모두 만족해야 합니다.
다중 인터페이스 구현
public final class MultipleInterfaceRoles {
public static void main(String[] args) {
CodePost codePost = new CodePost("로그인 구현", 50);
publish(codePost);
moderate(codePost);
export(codePost);
}
private static void publish(Publishable post) {
post.publish();
}
private static void moderate(Moderatable value) {
System.out.println("priority=" + value.priority());
}
private static void export(Exportable value) {
System.out.println(value.exportText());
}
private interface Publishable { void publish(); }
private interface Moderatable { int priority(); }
private interface Exportable { String exportText(); }
private static final class CodePost implements Publishable, Moderatable, Exportable {
private final String title;
private final int viewCount;
CodePost(String title, int viewCount) {
this.title = title;
this.viewCount = viewCount;
}
@Override public void publish() { System.out.println("publish=" + title); }
@Override public int priority() { return 100; }
@Override public String exportText() { return title + "," + viewCount; }
}
}publish=로그인 구현
priority=100
로그인 구현,50같은 객체 참조를 Publishable, Moderatable, Exportable 세 타입 관점으로 전달했습니다.
각 클라이언트는 자신에게 필요한 역할만 압니다.
export 함수는 발행이나 검수 우선순위에 의존하지 않습니다.
역할이 작을수록 구현 클래스와 클라이언트가 불필요한 메서드에 결합하지 않습니다.
세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다
같은 CodePost를 세 작은 역할 클라이언트에 순서대로 전달하여 publish·priority·export 결과를 얻습니다. 객체는 고정하고 현재 역할/반환값만 바꾸면 각 클라이언트의 좁은 의존을 설명할 수 있습니다.
MultipleInterfaceRoles는 CodePost 한 객체를 Publishable, Moderatable, Exportable 매개변수로 차례로 전달하고 역할마다 다른 메서드와 출력을 사용합니다.
한 객체를 세 역할로 전달하고 필요한 메서드만 호출합니다
MultipleInterfaceRoles · 세 호출 모두 처음 만든 같은 codePost를 전달합니다. 객체를 역할마다 새로 만들지 않습니다.
| 호출 순서 | 매개변수 타입 | 사용하는 메서드 | 콘솔 출력 |
|---|---|---|---|
| 1 · publish(codePost) | Publishable | post.publish() | publish=로그인 구현 |
| 2 · moderate(codePost) | Moderatable | value.priority() | priority=100 |
| 3 · export(codePost) | Exportable | value.exportText() | 로그인 구현,50 |
- 1 · publish(codePost)
- 매개변수 타입:
Publishable사용하는 메서드:post.publish()콘솔 출력:publish=로그인 구현 - 2 · moderate(codePost)
- 매개변수 타입:
Moderatable사용하는 메서드:value.priority()콘솔 출력:priority=100 - 3 · export(codePost)
- 매개변수 타입:
Exportable사용하는 메서드:value.exportText()콘솔 출력:로그인 구현,50
priority()는 정수 100을, exportText()는 문자열을 반환하며 해당 클라이언트가 출력합니다. publish()는 구현 안에서 직접 출력합니다.
세 매개변수는 역할별로 사용할 수 있는 메서드를 제한합니다. 이 예제의 export 클라이언트는 발행이나 우선순위 메서드를 호출하지 않습니다.
이 예의 조건
- main은 동일 CodePost 참조를 publish→moderate→export에 전달합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 동일한 CodePost 객체 참조를 main의 publish/moderate/export에 3개 role 매개변수로 전달한다. Publishable.publish는 직접 출력하고 priority 100과 exportText의 로그인 구현 50은 호출자가 출력한다. 각 클라이언트는 필요한 계약만 사용한다. |
| 구분 | 내용 |
|---|---|
| 설명 | 동일한 CodePost 객체 참조를 main의 publish/moderate/export에 3개 role 매개변수로 전달한다. Publishable.publish는 직접 출력하고 priority 100과 exportText의 로그인 구현 50은 호출자가 출력한다. 각 클라이언트는 필요한 계약만 사용한다. |
| 전체 읽기 정보 | 세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다 세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다 MultipleInterfaceRoles는 CodePost 한 객체를 Publishable, Moderatable, Exportable 매개변수로 차례로 전달하고 역할마다 다른 메서드와 출력을 사용합니다. 한 객체를 세 역할로 전달하고 필요한 메서드만 호출합니다 MultipleInterfaceRoles · 세 호출 모두 처음 만든 같은 codePost 를 전달합니다. 객체를 역할마다 새로 만들지 않습니다. main의 호출 순서와 클라이언트가 사용하는 계약 호출 순서 매개변수 타입 사용하는 메서드 콘솔 출력 1 · publish(codePost) Publishable post.publish() publish=로그인 구현 2 · moderate(codePost) Moderatable value.priority() priority=100 3 · export(codePost) Exportable value.exportText() 로그인 구현,50 1 · publish(codePost) 매개변수 타입: Publishable 사용하는 메서드: post.publish() 콘솔 출력: publish=로그인 구현 2 · moderate(codePost) 매개변수 타입: Moderatable 사용하는 메서드: value.priority() 콘솔 출력: priority=100 3 · export(codePost) 매개변수 타입: Exportable 사용하는 메서드: value.exportText() 콘솔 출력: 로그인 구현,50 priority() 는 정수 100 을, exportText() 는 문자열을 반환하며 해당 클라이언트가 출력합니다. publish() 는 구현 안에서 직접 출력합니다. 세 매개변수는 역할별로 사용할 수 있는 메서드를 제한합니다. 이 예제의 export 클라이언트는 발행이나 우선순위 메서드를 호출하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 발행 역할 Publishable 매개변수가 publish를 호출합니다. |
|
|
| 조정 역할 Moderatable 매개변수가 priority를 읽습니다. |
|
|
| 내보내기 역할 Exportable 매개변수가 exportText를 읽습니다. |
|
|
세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다
같은 CodePost를 세 작은 역할 클라이언트에 순서대로 전달하여 publish·priority·export 결과를 얻습니다. 객체는 고정하고 현재 역할/반환값만 바꾸면 각 클라이언트의 좁은 의존을 설명할 수 있습니다.
MultipleInterfaceRoles는 CodePost 한 객체를 Publishable, Moderatable, Exportable 매개변수로 차례로 전달하고 역할마다 다른 메서드와 출력을 사용합니다.
한 객체를 세 역할로 전달하고 필요한 메서드만 호출합니다
MultipleInterfaceRoles · 세 호출 모두 처음 만든 같은 codePost를 전달합니다. 객체를 역할마다 새로 만들지 않습니다.
| 호출 순서 | 매개변수 타입 | 사용하는 메서드 | 콘솔 출력 |
|---|---|---|---|
| 1 · publish(codePost) | Publishable | post.publish() | publish=로그인 구현 |
| 2 · moderate(codePost) | Moderatable | value.priority() | priority=100 |
| 3 · export(codePost) | Exportable | value.exportText() | 로그인 구현,50 |
- 1 · publish(codePost)
- 매개변수 타입:
Publishable사용하는 메서드:post.publish()콘솔 출력:publish=로그인 구현 - 2 · moderate(codePost)
- 매개변수 타입:
Moderatable사용하는 메서드:value.priority()콘솔 출력:priority=100 - 3 · export(codePost)
- 매개변수 타입:
Exportable사용하는 메서드:value.exportText()콘솔 출력:로그인 구현,50
priority()는 정수 100을, exportText()는 문자열을 반환하며 해당 클라이언트가 출력합니다. publish()는 구현 안에서 직접 출력합니다.
세 매개변수는 역할별로 사용할 수 있는 메서드를 제한합니다. 이 예제의 export 클라이언트는 발행이나 우선순위 메서드를 호출하지 않습니다.
이 예의 조건
- main은 동일 CodePost 참조를 publish→moderate→export에 전달합니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | 동일한 CodePost 객체 참조를 main의 publish/moderate/export에 3개 role 매개변수로 전달한다. Publishable.publish는 직접 출력하고 priority 100과 exportText의 로그인 구현 50은 호출자가 출력한다. 각 클라이언트는 필요한 계약만 사용한다. |
| 구분 | 내용 |
|---|---|
| 설명 | 동일한 CodePost 객체 참조를 main의 publish/moderate/export에 3개 role 매개변수로 전달한다. Publishable.publish는 직접 출력하고 priority 100과 exportText의 로그인 구현 50은 호출자가 출력한다. 각 클라이언트는 필요한 계약만 사용한다. |
| 전체 읽기 정보 | 세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다 세 클라이언트는 같은 객체에서 자기 역할의 메서드만 호출한다 MultipleInterfaceRoles는 CodePost 한 객체를 Publishable, Moderatable, Exportable 매개변수로 차례로 전달하고 역할마다 다른 메서드와 출력을 사용합니다. 한 객체를 세 역할로 전달하고 필요한 메서드만 호출합니다 MultipleInterfaceRoles · 세 호출 모두 처음 만든 같은 codePost 를 전달합니다. 객체를 역할마다 새로 만들지 않습니다. main의 호출 순서와 클라이언트가 사용하는 계약 호출 순서 매개변수 타입 사용하는 메서드 콘솔 출력 1 · publish(codePost) Publishable post.publish() publish=로그인 구현 2 · moderate(codePost) Moderatable value.priority() priority=100 3 · export(codePost) Exportable value.exportText() 로그인 구현,50 1 · publish(codePost) 매개변수 타입: Publishable 사용하는 메서드: post.publish() 콘솔 출력: publish=로그인 구현 2 · moderate(codePost) 매개변수 타입: Moderatable 사용하는 메서드: value.priority() 콘솔 출력: priority=100 3 · export(codePost) 매개변수 타입: Exportable 사용하는 메서드: value.exportText() 콘솔 출력: 로그인 구현,50 priority() 는 정수 100 을, exportText() 는 문자열을 반환하며 해당 클라이언트가 출력합니다. publish() 는 구현 안에서 직접 출력합니다. 세 매개변수는 역할별로 사용할 수 있는 메서드를 제한합니다. 이 예제의 export 클라이언트는 발행이나 우선순위 메서드를 호출하지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 발행 역할 Publishable 매개변수가 publish를 호출합니다. |
|
|
| 조정 역할 Moderatable 매개변수가 priority를 읽습니다. |
|
|
| 내보내기 역할 Exportable 매개변수가 exportText를 읽습니다. |
|
|
상속과 인터페이스 조합
public final class ClassAndInterfaces {
public static void main(String[] args) {
CodePost post = new CodePost("로그인 구현", 50, "feature/login");
Post base = post;
Moderatable moderated = post;
Exportable exported = post;
base.publish();
System.out.println("priority=" + moderated.priority());
System.out.println("export=" + exported.exportText());
}
private interface Moderatable { int priority(); }
private interface Exportable { String exportText(); }
private abstract static class Post {
private final String title;
private final int viewCount;
Post(String title, int viewCount) {
this.title = title;
this.viewCount = viewCount;
}
protected String title() { return title; }
protected int viewCount() { return viewCount; }
abstract void publish();
}
private static final class CodePost extends Post implements Moderatable, Exportable {
private final String branch;
CodePost(String title, int viewCount, String branch) {
super(title, viewCount);
this.branch = branch;
}
@Override void publish() { System.out.println("code=" + branch); }
@Override public int priority() { return 100; }
@Override public String exportText() { return title() + ":" + viewCount(); }
}
}code=feature/login
priority=100
export=로그인 구현:50Post는 title·viewCount 공통 상태와 publish 계층을 제공합니다.
Moderatable과 Exportable은 계층과 독립적인 역할을 더합니다.
CodePost 객체 하나가 네 타입(CodePost 포함)으로 사용됩니다.
서로 관련 없는 두 인터페이스에서 같은 서명의 default 구현을 물려받고 우선할 클래스 구현도 없다면, 구현 클래스가 해당 메서드를 오버라이딩해 충돌을 해결해야 합니다. 이름이 같아도 매개변수가 다른 오버로드는 이 경우와 다릅니다. 인터페이스 상속 관계에서 더 구체적인 재정의가 기존 구현을 대체하는 경우도 있으므로 이름 중복만으로 실패를 판단하지 않습니다. 클래스의 메서드 상속 규칙을 함께 확인합니다.
클래스의 구체 메서드와 인터페이스 default가 충돌하면 클래스 구현이 우선하지만, 역할 설계 단계에서 이름과 의미가 같은지 검토합니다.
예시
다음은 앞의 역할 예제와 독립적인 프로그램입니다. 두 인터페이스의 같은 label() 계약에 어떤 구현을 사용할지 클래스가 명시합니다. 이 클래스의 label()을 제거하면 관련 없는 두 default 구현을 그대로 물려받을 수 없어 컴파일이 실패합니다.
public final class DefaultRoleChoice {
public static void main(String[] args) {
System.out.println(new CodePost().label());
}
private interface Exportable {
default String label() { return "export"; }
}
private interface Previewable {
default String label() { return "preview"; }
}
private static final class CodePost implements Exportable, Previewable {
@Override public String label() {
return Exportable.super.label() + "+" + Previewable.super.label();
}
}
}export+preview두 역할의 기본 구현을 호출해 문자열을 합치는 정책은 CodePost가 선택했습니다. 인터페이스를 구현했다는 사실만으로 이 결합 정책이 자동 생성되는 것은 아닙니다.
ClassAndInterfaces에서 CodePost는 Post만 상속하고 Moderatable과 Exportable을 구현합니다. 타입 관계를 객체 세 개의 참조 관계와 혼동하지 않습니다.
부모 클래스 하나와 독립적인 인터페이스 역할 둘을 조합합니다
ClassAndInterfaces의 타입 선언 관계입니다. 화살표는 객체 참조나 실행 순서가 아니라 extends·implements를 뜻합니다.
- CodePost extends Post
- 부모 클래스는 하나입니다. 추상
Post는private title·viewCount와 추상publish()를 선언합니다. - CodePost implements Moderatable
int priority()역할을 구현합니다. 인터페이스는 별도 부모 객체를 만드는 것이 아닙니다.- CodePost implements Exportable
String exportText()역할을 구현합니다.- CodePost가 추가하는 것
private branch필드와publish()·priority()·exportText()의 구현을 제공합니다.
post·base·moderated·exported는 모두 같은 CodePost 객체를 가리킵니다. 부모에서 선언한 상태와 자식의 branch는 별도 객체가 아닙니다.
CodePost.exportText()는 부모의 protected title()·viewCount()를 통해 값을 읽습니다. 부모의 private 필드를 직접 접근하는 것은 아닙니다.
역할 기반 다형성
public final class RoleBasedBoard {
public static void main(String[] args) {
Board board = new Board(3);
board.add(new TextPost("가입 인사", 40));
board.add(new CodePost("로그인 구현", 50));
board.publishAll();
board.exportAll();
System.out.println(board.summary());
}
private static final class Board {
private final Post[] posts;
private int size;
Board(int capacity) { posts = new Post[capacity]; }
void add(Post post) {
if (size == posts.length) throw new IllegalStateException("full");
posts[size++] = post;
}
void publishAll() {
for (int index = 0; index < size; index++) {
posts[index].publish();
}
}
void exportAll() {
for (int index = 0; index < size; index++) {
Post post = posts[index];
if (post instanceof Exportable exported) {
System.out.println("export=" + exported.exportText());
}
}
}
String summary() {
int total = 0;
for (int index = 0; index < size; index++) {
total += posts[index].viewCount();
}
return "count=" + size + ", total=" + total;
}
}
private interface Exportable { String exportText(); }
private abstract static class Post {
private final String title;
private final int viewCount;
Post(String title, int viewCount) { this.title = title; this.viewCount = viewCount; }
String title() { return title; }
int viewCount() { return viewCount; }
abstract void publish();
}
private static final class TextPost extends Post {
TextPost(String title, int viewCount) { super(title, viewCount); }
@Override void publish() { System.out.println("publish=text:" + title()); }
}
private static final class CodePost extends Post implements Exportable {
CodePost(String title, int viewCount) { super(title, viewCount); }
@Override void publish() { System.out.println("publish=code:" + title()); }
@Override public String exportText() { return title() + "," + viewCount(); }
}
}publish=text:가입 인사
publish=code:로그인 구현
export=로그인 구현,50
count=2, total=90모든 게시글은 공통 publish와 viewCount를 제공하고 CodePost만 Exportable 역할을 추가합니다.
선택 역할이 필요한 경계에서만 패턴 매칭을 사용합니다.
새로운 Exportable 게시글을 추가해도 exportAll은 인터페이스에 정의된 기능으로 처리합니다.
독립 연습: 내보내지 않는 세 번째 게시글
위 Board의 마지막 빈 칸에 new TextPost("두 번째 인사", 30)을 추가하고 같은 main 순서로 발행·내보내기·요약을 실행합니다. 발행 출력, 내보내기 출력의 개수와 마지막 count·total을 각각 예측하세요. 내보내지 않는 객체의 조회 수도 합계에 들어가나요?
풀이: 발행 출력은 3개, 내보내기 출력은 기존 CodePost의 1개입니다. 요약은 count=3, total=120입니다. 세 번째 TextPost는 Exportable을 구현하지 않아 내보내기만 건너뛰며, 공통 발행·조회 수 합산에는 참여합니다. 역할 존재 여부와 공통 부모 계약은 독립적으로 판단합니다.
Board는 모든 게시글을 발행하고 Exportable만 내보낸다
발행 전체 순회 뒤 exportAll이 TextPost를 건너뛰고 CodePost만 내보낸 다음 summary가 두 원소를 합산합니다. 사용 size=2와 선택 역할의 통과 여부를 실제 순회로 추적할 수 있습니다.
RoleBasedBoard의 size는2, 배열 길이는3입니다. index0 TextPost와 index1 CodePost만 순회하고 CodePost만 Exportable 분기에 들어갑니다.
모든 게시글을 발행하고 Exportable 역할만 내보냅니다
RoleBasedBoard · 두 번의 add 직후 posts.length = 3, size = 2입니다.
| 배열 칸 · 객체 | publishAll() 출력 | exportAll() 판정과 출력 | summary() 합산 |
|---|---|---|---|
| 0 · TextPost | publish=text:가입 인사 | instanceof Exportable은 false. 내보내기 출력 없음. | viewCount() = 40 |
| 1 · CodePost | publish=code:로그인 구현 | instanceof Exportable은 true.
| viewCount() = 50 |
| 2 · null | 순회하지 않음 | 검사하지 않음 | 합산하지 않음 |
- 0 · TextPost
- publishAll() 출력:
publish=text:가입 인사exportAll() 판정과 출력:instanceof Exportable은 false. 내보내기 출력 없음.summary() 합산:viewCount() = 40 - 1 · CodePost
- publishAll() 출력:
publish=code:로그인 구현exportAll() 판정과 출력:instanceof Exportable은 true.export=로그인 구현,50summary() 합산:viewCount() = 50 - 2 · null
- publishAll() 출력: 순회하지 않음exportAll() 판정과 출력: 검사하지 않음summary() 합산: 합산하지 않음
세 반복문 모두 index < size를 사용합니다. 비어 있는 2번 칸은 실제 검사나 메서드 호출 대상이 아닙니다.
main은 publishAll(), exportAll(), 요약 출력 순으로 실행합니다. 마지막 출력은 count=2, total=90이며, 내보내기 가능 여부와 관계없이 두 게시글의 조회 수를 합합니다.
이 예의 조건
- size 2, length 3이며 모든 순회는 미사용 slot 2를 검사하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | publishAll에서 두 게시글 공통 발행→exportAll의 index 0 Text false/skip, index 1 Code true/export 로그인 구현 50→summary에서 둘의 조회 수 40+50=90, count 2이다. size 2/length 3의 slot 2는 null이며 각 순회에서 검사하지 않는다. |
| 4 | TextPost |
| 5 | CodePost |
| 6 | exportAll · index 0 검사 뒤 index 1 검사 |
| 17 | 세 순회 모두 index < size · slot 2 null을 검사하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | publishAll에서 두 게시글 공통 발행→exportAll의 index 0 Text false/skip, index 1 Code true/export 로그인 구현 50→summary에서 둘의 조회 수 40+50=90, count 2이다. size 2/length 3의 slot 2는 null이며 각 순회에서 검사하지 않는다. |
| 전체 읽기 정보 | Board는 모든 게시글을 발행하고 Exportable만 내보낸다 Board는 모든 게시글을 발행하고 Exportable만 내보낸다 RoleBasedBoard의 size는 2, 배열 길이는 3입니다. index 0 TextPost와 index 1 CodePost만 순회하고 CodePost만 Exportable 분기에 들어갑니다. 모든 게시글을 발행하고 Exportable 역할만 내보냅니다 RoleBasedBoard · 두 번의 add 직후 posts.length = 3 , size = 2 입니다. 각 배열 칸에서 공통 처리와 선택 처리가 갈리는 지점 배열 칸 · 객체 publishAll() 출력 exportAll() 판정과 출력 summary() 합산 0 · TextPost publish=text:가입 인사 instanceof Exportable 은 false . 내보내기 출력 없음. viewCount() = 40 1 · CodePost publish=code:로그인 구현 instanceof Exportable 은 true . export=로그인 구현,50 viewCount() = 50 2 · null 순회하지 않음 검사하지 않음 합산하지 않음 0 · TextPost publishAll() 출력: publish=text:가입 인사 exportAll() 판정과 출력: instanceof Exportable 은 false . 내보내기 출력 없음. summary() 합산: viewCount() = 40 1 · CodePost publishAll() 출력: publish=code:로그인 구현 exportAll() 판정과 출력: instanceof Exportable 은 true . export=로그인 구현,50 summary() 합산: viewCount() = 50 2 · null publishAll() 출력: 순회하지 않음 exportAll() 판정과 출력: 검사하지 않음 summary() 합산: 합산하지 않음 세 반복문 모두 index < size 를 사용합니다. 비어 있는 2번 칸은 실제 검사나 메서드 호출 대상이 아닙니다. main 은 publishAll() , exportAll() , 요약 출력 순으로 실행합니다. 마지막 출력은 count=2, total=90 이며, 내보내기 가능 여부와 관계없이 두 게시글의 조회 수를 합합니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 공통 발행 publishAll이 Text와 Code 두 원소를 발행합니다. |
|
|
| 선택 역할 exportAll이 Text false를 건너뛰고 Code true를 내보냅니다. |
|
|
| 전체 집계 summary는 두 원소 조회 수를 모읍니다. |
|
|
Board는 모든 게시글을 발행하고 Exportable만 내보낸다
발행 전체 순회 뒤 exportAll이 TextPost를 건너뛰고 CodePost만 내보낸 다음 summary가 두 원소를 합산합니다. 사용 size=2와 선택 역할의 통과 여부를 실제 순회로 추적할 수 있습니다.
RoleBasedBoard의 size는2, 배열 길이는3입니다. index0 TextPost와 index1 CodePost만 순회하고 CodePost만 Exportable 분기에 들어갑니다.
모든 게시글을 발행하고 Exportable 역할만 내보냅니다
RoleBasedBoard · 두 번의 add 직후 posts.length = 3, size = 2입니다.
| 배열 칸 · 객체 | publishAll() 출력 | exportAll() 판정과 출력 | summary() 합산 |
|---|---|---|---|
| 0 · TextPost | publish=text:가입 인사 | instanceof Exportable은 false. 내보내기 출력 없음. | viewCount() = 40 |
| 1 · CodePost | publish=code:로그인 구현 | instanceof Exportable은 true.
| viewCount() = 50 |
| 2 · null | 순회하지 않음 | 검사하지 않음 | 합산하지 않음 |
- 0 · TextPost
- publishAll() 출력:
publish=text:가입 인사exportAll() 판정과 출력:instanceof Exportable은 false. 내보내기 출력 없음.summary() 합산:viewCount() = 40 - 1 · CodePost
- publishAll() 출력:
publish=code:로그인 구현exportAll() 판정과 출력:instanceof Exportable은 true.export=로그인 구현,50summary() 합산:viewCount() = 50 - 2 · null
- publishAll() 출력: 순회하지 않음exportAll() 판정과 출력: 검사하지 않음summary() 합산: 합산하지 않음
세 반복문 모두 index < size를 사용합니다. 비어 있는 2번 칸은 실제 검사나 메서드 호출 대상이 아닙니다.
main은 publishAll(), exportAll(), 요약 출력 순으로 실행합니다. 마지막 출력은 count=2, total=90이며, 내보내기 가능 여부와 관계없이 두 게시글의 조회 수를 합합니다.
이 예의 조건
- size 2, length 3이며 모든 순회는 미사용 slot 2를 검사하지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 2 | publishAll에서 두 게시글 공통 발행→exportAll의 index 0 Text false/skip, index 1 Code true/export 로그인 구현 50→summary에서 둘의 조회 수 40+50=90, count 2이다. size 2/length 3의 slot 2는 null이며 각 순회에서 검사하지 않는다. |
| 4 | TextPost |
| 5 | CodePost |
| 6 | exportAll · index 0 검사 뒤 index 1 검사 |
| 17 | 세 순회 모두 index < size · slot 2 null을 검사하지 않음 |
| 구분 | 내용 |
|---|---|
| 설명 | publishAll에서 두 게시글 공통 발행→exportAll의 index 0 Text false/skip, index 1 Code true/export 로그인 구현 50→summary에서 둘의 조회 수 40+50=90, count 2이다. size 2/length 3의 slot 2는 null이며 각 순회에서 검사하지 않는다. |
| 전체 읽기 정보 | Board는 모든 게시글을 발행하고 Exportable만 내보낸다 Board는 모든 게시글을 발행하고 Exportable만 내보낸다 RoleBasedBoard의 size는 2, 배열 길이는 3입니다. index 0 TextPost와 index 1 CodePost만 순회하고 CodePost만 Exportable 분기에 들어갑니다. 모든 게시글을 발행하고 Exportable 역할만 내보냅니다 RoleBasedBoard · 두 번의 add 직후 posts.length = 3 , size = 2 입니다. 각 배열 칸에서 공통 처리와 선택 처리가 갈리는 지점 배열 칸 · 객체 publishAll() 출력 exportAll() 판정과 출력 summary() 합산 0 · TextPost publish=text:가입 인사 instanceof Exportable 은 false . 내보내기 출력 없음. viewCount() = 40 1 · CodePost publish=code:로그인 구현 instanceof Exportable 은 true . export=로그인 구현,50 viewCount() = 50 2 · null 순회하지 않음 검사하지 않음 합산하지 않음 0 · TextPost publishAll() 출력: publish=text:가입 인사 exportAll() 판정과 출력: instanceof Exportable 은 false . 내보내기 출력 없음. summary() 합산: viewCount() = 40 1 · CodePost publishAll() 출력: publish=code:로그인 구현 exportAll() 판정과 출력: instanceof Exportable 은 true . export=로그인 구현,50 summary() 합산: viewCount() = 50 2 · null publishAll() 출력: 순회하지 않음 exportAll() 판정과 출력: 검사하지 않음 summary() 합산: 합산하지 않음 세 반복문 모두 index < size 를 사용합니다. 비어 있는 2번 칸은 실제 검사나 메서드 호출 대상이 아닙니다. main 은 publishAll() , exportAll() , 요약 출력 순으로 실행합니다. 마지막 출력은 count=2, total=90 이며, 내보내기 가능 여부와 관계없이 두 게시글의 조회 수를 합합니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 공통 발행 publishAll이 Text와 Code 두 원소를 발행합니다. |
|
|
| 선택 역할 exportAll이 Text false를 건너뛰고 Code true를 내보냅니다. |
|
|
| 전체 집계 summary는 두 원소 조회 수를 모읍니다. |
|
|
계층과 역할의 평가
- 모든 자식이 부모 의미를 지키는가?
- 공통 상태가 실제로 모든 자식에 필요한가?
- 인터페이스 메서드를 모든 구현이 자연스럽게 수행하는가?
- 클라이언트가 필요하지 않은 큰 인터페이스에 의존하지 않는가?
- 새 구현 추가 때 타입별
switch가 늘어나지 않는가? - 상속보다 구성으로 객체를 가지는 편이 관계를 더 정확히 표현하지 않는가?
상속·추상 클래스·인터페이스는 중복 제거 도구이기 전에 타입 관계와 변경 경계를 표현합니다.
다음 장에서는 역할과 구현을 분리해 클라이언트 수정 없이 구현을 교체하는 설계로 확장합니다.
연습 문제
Moderatable과 Exportable 인터페이스를 만들고 PublishedPost가 둘 다 구현하게 하세요.
moderate와 export 함수가 각각 필요한 역할 타입만 받도록 합니다.
해설 보기
public final class CombinedPostRoles {
public static void main(String[] args) {
PublishedPost post = new PublishedPost("가입 인사");
moderate(post);
export(post);
}
private static void moderate(Moderatable value) {
System.out.println("priority=" + value.priority());
}
private static void export(Exportable value) {
System.out.println("export=" + value.exportText());
}
private interface Moderatable { int priority(); }
private interface Exportable { String exportText(); }
private static final class PublishedPost implements Moderatable, Exportable {
private final String title;
PublishedPost(String title) { this.title = title; }
@Override public int priority() { return 80; }
@Override public String exportText() { return title; }
}
}priority=80
export=가입 인사두 함수는 같은 구체 클래스가 아니라 자기 역할만 압니다.
구현 객체는 필요에 따라 독립 역할을 조합합니다.