Java 객체지향과 다형성
클래스·캡슐화·상속·인터페이스를 작은 명단과 역할 정책으로 익히기
이번 문서에서 만드는 것
2주는 회원의 이름 변경 규칙을 클래스로 묶고 3주는 인터페이스로 역할별 표시 정책을 바꿉니다. Java의 main·조건문·메서드에 익숙해야 합니다. 모든 코드는 별도 oop 폴더에서 작성하며 각 public 타입을 같은 이름의 파일에 둡니다.
2주: 상태와 규칙을 함께 두기
Member.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를 작성합니다.
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("잘못된 변경을 거절하고 기존 이름 보존"); } }}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:
public interface LabelPolicy { String label(Member member);}BasicLabel.java:
public class BasicLabel implements LabelPolicy { @Override public String label(Member member) { return member.getName(); }}MentorLabel.java:
public class MentorLabel implements LabelPolicy { @Override public String label(Member member) { return "멘토 · " + member.getName(); }}Main.java의 main 끝에 추가합니다.
LabelPolicy[] policies = {new BasicLabel(), new MentorLabel()};for (LabelPolicy policy : policies) { System.out.println(policy.label(first));}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. 이 문서는 영상 전체를 옮긴 전사 자료가 아닙니다. 세부 내용은 위 공식 문서에서 확인합니다.