Java 객체지향과 다형성

클래스·캡슐화·상속·인터페이스를 작은 명단과 역할 정책으로 익히기

이번 문서에서 만드는 것

2주는 회원의 이름 변경 규칙을 클래스로 묶고 3주는 인터페이스로 역할별 표시 정책을 바꿉니다. Java의 main·조건문·메서드에 익숙해야 합니다. 모든 코드는 별도 oop 폴더에서 작성하며 각 public 타입을 같은 이름의 파일에 둡니다.

2주: 상태와 규칙을 함께 두기

Member.java를 만듭니다.

Java
public class Member {    private final long id;    private String name;
    public Member(long id, String name) {        if (id <= 0) throw new IllegalArgumentException("ID는 양수여야 합니다.");        this.id = id;        rename(name);    }
    public long getId() { return id; }    public String getName() { return name; }
    public void rename(String name) {        if (name == null || name.isBlank() || name.strip().length() > 40) {            throw new IllegalArgumentException("이름은 1~40자여야 합니다.");        }        this.name = name.strip();    }}

private 필드는 외부 코드가 직접 바꾸지 못하게 합니다. 이름 변경을 rename으로 모으면 생성·수정에서 같은 검증을 거칩니다. final id는 생성 후 재할당하지 않습니다. final로 선언한 객체의 내부 값까지 자동으로 불변이 되는 것은 아닙니다.

Main.java를 작성합니다.

Java
public class Main {    public static void main(String[] args) {        Member first = new Member(1, "가람");        Member second = new Member(2, "나래");        first.rename("다온");        System.out.println(first.getName());        System.out.println(second.getName());        try {            first.rename(" ");            throw new AssertionError("공백 이름이 허용됨");        } catch (IllegalArgumentException expected) {            if (!first.getName().equals("다온")) throw new AssertionError("기존 이름 훼손");            System.out.println("잘못된 변경을 거절하고 기존 이름 보존");        }    }}
Shell
javac -encoding UTF-8 Member.java Main.javajava Main

각 인스턴스의 이름은 독립적입니다. this는 현재 객체를 가리킵니다. 모든 회원이 공유할 필요가 없는 name을 static 필드로 만들면 객체마다 다른 값을 유지할 수 없습니다.

2주 권장 90분: 클래스·생성자 20분, 캡슐화 25분, 검증 확장 25분, 리뷰 20분. 이름 40자·41자·null을 검사하고 검증 실패 때 기존 값이 그대로인지 확인합니다.

3주: 인터페이스와 다형성

역할마다 표시 문구가 달라지는 요구가 생겼다고 가정합니다. Member를 역할별로 여러 단계 상속하기 전에 바뀌는 정책을 분리합니다.

LabelPolicy.java:

Java
public interface LabelPolicy {    String label(Member member);}

BasicLabel.java:

Java
public class BasicLabel implements LabelPolicy {    @Override    public String label(Member member) { return member.getName(); }}

MentorLabel.java:

Java
public class MentorLabel implements LabelPolicy {    @Override    public String label(Member member) { return "멘토 · " + member.getName(); }}

Main.java의 main 끝에 추가합니다.

Java
LabelPolicy[] policies = {new BasicLabel(), new MentorLabel()};for (LabelPolicy policy : policies) {    System.out.println(policy.label(first));}
Shell
javac -encoding UTF-8 Member.java LabelPolicy.java BasicLabel.java MentorLabel.java Main.javajava Main

호출하는 쪽은 인터페이스를 알고 있고 실제 동작은 전달된 구현이 결정합니다. 이 방식이 다음 DI 문서에서 구현을 교체할 수 있는 바탕입니다.

상속과 조합의 차이

클래스 상속은 extends로 부모의 구현을 이어받습니다. 예를 들어 Mentor extends Member는 멘토가 모든 Member 사용처에서 자연스럽게 동작할 때 고려합니다. 역할 변경이 잦거나 표시 방식만 다른 경우에는 위처럼 정책을 조합하는 편이 단순합니다. “코드 몇 줄 재사용”만을 이유로 상속 관계를 만들지 않습니다.

오버라이딩은 부모·인터페이스의 메서드를 같은 계약으로 구현하는 것이고 오버로딩은 매개변수가 다른 같은 이름의 메서드를 정의하는 것입니다. @Override는 오타로 다른 메서드를 만드는 실수를 컴파일 때 잡아 줍니다.

3주 실습과 완료 기준

권장 90분: 상속·인터페이스 비교 20분, 정책 구현 25분, 새 구현 추가 25분, 설명 20분입니다. TrackLabel을 만들어 이름 옆에 학습 트랙을 표시하되, 기존 BasicLabel 동작이 바뀌지 않게 합니다. 실제로 표시할 트랙 값을 생성자로 받습니다.

완료 기준은 새로운 정책을 추가할 때 기존 반복문을 고치지 않고 출력이 달라지는 것을 확인하고 상속보다 조합을 선택한 이유를 말하는 것입니다. 제출물은 각 파일·실행 결과·객체 관계 그림입니다.

더 읽어보기

연결된 PBL 미션과 VOD

주차·미션참고 VOD 범위
2주 · 클래스와 캡슐화Java 첫걸음 11장
3주 · 상속·다형성Java 첫걸음 11장

강좌 안내: 참고 강좌 1. 이 문서는 영상 전체를 옮긴 전사 자료가 아닙니다. 세부 내용은 위 공식 문서에서 확인합니다.