인프런 커뮤니티 질문&답변
5-4 Sealed Interface는 주로 모든 케이스 검증이 필요할 때 사용하나요?
해결된 질문
작성
·
22
답변 1
0
바나나님 안녕하세요. 좋은 질문 해주셔서 감사합니다. 🙂
질문에서 말씀하신 것과 유사하게 sealed interface는 인터페이스를 구현할 수 있는 종류를 제한하고 싶은 경우에 사용합니다.
강의에서 만든 텍스트 어드벤처 예제에서는 사용자가 입력할 수 있는 명령어의 종류를 제한하기 위해 sealed interface를 사용하고 있습니다.
예제 코드를 보시면 Command 타입의 종류로 Move, Unknown, Look, Help, Quit, Inventory, Take, Drop, Destroy, Throw로만 제한한다는 것을 확인할 수 있습니다.
public sealed interface Command {
record Move(Direction direction) implements Command {}
record Unknown() implements Command {}
record Look() implements Command {}
record Help() implements Command {}
record Quit() implements Command {}
record Inventory() implements Command {}
record Take(String item) implements Command {}
record Drop(String item) implements Command {}
record Destroy(String item) implements Command {}
record Throw(String item) implements Command {}
}현재는 인터페이스 안에 record 들을 정의하고 있지만 아래 코드처럼 permits를 사용한 선언문의 형태로 변경하면 종류를 제한하는 sealed interface의 용도를 좀 더 명확하게 이해할 수 있습니다.
public sealed interface Command permits Move, Unknown, Look, Help, Quit, Inventory, Take, Drop, Destroy, Throw {}
record Move(Direction direction) implements Command {}
record Unknown() implements Command {}
record Look() implements Command {}
record Help() implements Command {}
record Quit() implements Command {}
record Inventory() implements Command {}
record Take(String item) implements Command {}
record Drop(String item) implements Command {}
record Destroy(String item) implements Command {}
record Throw(String item) implements Command {}
sealed interface를 사용하면 인터페이스를 구현할 수 있는 모든 타입을 컴파일러가 알 수 있기 때문에 특정 케이스에 대한 처리를 누락하는 문제를 방지할 수 있습니다.
Game 클래스의 executeCommand() 메서드를 보시면 switch 문에서 패턴 매칭(pattern matching)을 통해 Command의 종류를 누락하지 않고 저절히 처리하고 있는데 이 경우에 하나의 case문이라도 누락되면 컴파일 에러가 발생하게 됩니다.
참고로 default 케이스 없이 모든 구문을 명시적으로 체크할 수 있는 switch 문을 스마트 스위치 표현식(smart switch expression)이라고 부릅니다.
private void executeCommand(Command command) {
switch(command) {
case Command.Move move -> world.tryMove(move.direction());
case Command.Look() -> world.showRoom();
case Command.Help() -> showHelp();
case Command.Quit() -> quit();
case Command.Unknown() -> showUnknownCommand();
case Command.Inventory() -> world.showInventory();
case Command.Take take -> world.takeItem(take.item());
case Command.Drop drop -> world.dropItem(drop.item());
case Command.Destroy destroy -> world.destroyItem(destroy.item());
case Command.Throw aThrow -> world.throwItem(aThrow.item());
}
}위 코드를 아래의 Move, Take, Drop, Destory, Throw에서 알 수 있는 것처럼 Command.Move(Direction direction)의 형태로 내부 요소를 분해해서 처리할 수도 있습니다.
private void executeCommand(Command command) {
switch(command) {
case Command.Move(Direction direction) -> world.tryMove(direction);
case Command.Look() -> world.showRoom();
case Command.Help() -> showHelp();
case Command.Quit() -> quit();
case Command.Unknown() -> showUnknownCommand();
case Command.Inventory() -> world.showInventory();
case Command.Take(String item) -> world.takeItem(item);
case Command.Drop(String item) -> world.dropItem(item);
case Command.Destroy(String item) -> world.destroyItem(item);
case Command.Throw(String item)-> world.throwItem(item);
}
}
참고로 sealed interface와 record는 Java에서 대수적 자료형(Algebraric Data Type)을 구현하기 위해 사용됩니다.
대수적 자료형은 합 타입과 곱 타입을 조합해서 구현하며 자바의 경우 sealed interface는 OR로 조합할 수 있는 합 타입(Sum Type)을, record를 이용해서 다양한 상태를 조합할 수 있는 곱 타입(Product Type)을 구현하기 위해 사용됩니다.
강의에서는 특별히 언급하지 않았지만 데이터와 로직을 하나의 단위로 묶는 객체지향과 달리, 데이터와 로직을 별도로 나누는 패러다임을 데이터 지향 프로그래밍(DOP, Data-Oriented Programming)이라고 부릅니다.
자바에서 데이터 지향 프로그래밍은 sealed interface와 record, 패턴 매칭을 조합해서 구현하게 됩니다.
데이터는 sealed interface와 record를 이용해서 불변 데이터로 구현하며, 이 로직을 사용하는 로직은 executeCommand() 메서드와 같이 패턴 매칭을 이용해서 구현합니다.
정리하면 예제 코드는 복잡한 게임 플레이 로직은 객체지향 프로그래밍 방식을 따르고, 명령 처리 로직은 데이터 지향 프로그래밍 방식을 따르고 있습니다.
강의의 예제처럼 애플리케이션을 구현할 때는 하나의 패러다임만 적용하는게 아니라 여러 종류의 패러다임을 조합해서 애플리케이션을 구축하는 방식이 일반적입니다(따라서 자바는 멀티패러다임 언어라고 할 수 있습니다).
답변이 되었는지 모르겠네요. 😊
행복한 성탄절 보내시고 추가로 궁금한 부분이 있으면 질문 남겨주세요.




