안녕하세요 개발자 문문입니다.
오늘은 2장 객체지향 프로그래밍에 대해 공부하겠습니다.
- 협력, 객체, 클래스
- 객체지향 설계시에 클래스가 아닌 객체에 초점을 맞춰야 합니다.
- 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야합니다.
- 도메인의 구조를 따르는 프로그램 구조
- 도메인 : 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야
- 객체지향 패러다임의 강점은 초기 단계부터 마지막 단계까지 객체라는 동일한 추상화 기법을 사용할 수 있기 때문입니다.
- 그렇기 때문에 도메인을 구성하는 개념들이 객체와 클래스로 매끄럽게 연결될 수 있습니다.
ex) 영화 - Movie 클래스, 상영 - Screening 클래스
- 클래스 구현
- private와 public 같은 접근수정자를 적절하게 사용하여 클래스 내부와 외부를 구분해야 합니다.
- 내부와 외부를 구분하는 이유는 아래와 같습니다.
1. 경계의 명확성이 객체의 자율성을 보장해줍니다. - 객체의 상태는 숨기고 행동만 공개한다
2. 프로그래머에게 구현의 자유를 제공합니다.
- 프로그래머의 역할을 클래스 작성자 / 클라이언트 프로그래머로 구분
- 클래스 작성자 : 인터페이스를 바꾸지 않는 한 외부에 미치는 영향을 걱정하지 않고 내부 구현 변경가능
- 클라이언트 프로그래머 : 내부 구현은 무시한 채 인터페이스만 알고 있어도 클래스 사용가능
- 메시지 / 메서드
- 객체가 다른 객체와 상호작용을 하는 유일한 방법을 메시지 전송, 다른 객체의 요청이 도착할 때 해당 객체가 메시지를 수신했다고 합니다.
- 이 수신된 메시지를 처리하는 방법을 메서드라고 합니다.
- 컴파일 시간 의존성과 실행 시간 의존성
- 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다는 것 입니다.
Ex) 1. Movie라는 클래스는 요금 할인 정책 적용 시 DiscountPolicy 라는 클래스를 의존한다.
public Movie( DiscountPolicy discountPolicy ){ ... }
2. DiscountPolicy는 추상 클래스로 금액 할인 정책(AmountDiscountPolicy) , 비율 할인 정책(PercentDiscountPolicy) 를 자식 클래스로 갖고 있다. (상속)
3. Movie에 금액 할인 정책을 적용하기로 했다면 new Movie(new AmountDiscountPolicy());로 Movie 객체를 생성한다. 이런 경우에는 Movice 클래스는 AmountDiscountPolicy에 의존한다.
- 상속과 인터페이스
- 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문입니다.
- 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의합니다.
- 다형성
- 위의 예시에서 DiscountPolicy에 calcul()이라는 메서드가 정의되어 있고, 두 자식 클래스가 이를 오버라이딩 했다고 가정하겠습니다.
- Movie는 calcul()이라는 동일한 메시지를 전송하지만 메시지를 수신하는 클래스에 따라 수행되는 메서드가 달라집니다.
- 이를 다형성이라고 합니다.
- 지연 바인딩(lazy binding) / 동적 바인딩(dynamic binding) : 메시지와 메서드를 실행 시점에 바인딩 하는 것을 의미합니다.
- 추상화의 힘
- 추상화 계층만 따로 떼어 놓고 보면 요구사항의 정책을 높은 수준에서 서술 가능합니다.
→ 세부적인 내용을 무시한 채 상위 정책을 쉽고 간단하게 표현 가능합니다.
- 추상화를 이용하면 설계가 조금 더 유연해집니다.
→ 추상화를 이용해 상위 정책을 표현하면 기존 구조를 수정하지 않고도 새로운 기능을 쉽게 추가하고 확장할 수 있습니다.
- 상속의 문제
- 상속은 캡슐화를 위반합니다.
- 상속은 설계를 유연하지 못하게 합니다.
- 이 두 문제를 해결할 수 있는게 합성 방법입니다.
// 상속 방식
class Animal {
void makeSound() {
System.out.println("Some generic sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // 출력: Woof!
}
}
- 합성
- 합성은 인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법을 의미합니다.
- 인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있습니다.
- 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만듭니다.
// 인터페이스로 행동 정의
interface SoundBehavior {
void makeSound();
}
// 행동 구현 클래스
class BarkSound implements SoundBehavior {
public void makeSound() {
System.out.println("Woof!");
}
}
class MuteSound implements SoundBehavior {
public void makeSound() {
System.out.println("...");
}
}
// Animal은 SoundBehavior를 포함
class Animal {
private SoundBehavior soundBehavior;
public Animal(SoundBehavior soundBehavior) {
this.soundBehavior = soundBehavior;
}
public void makeSound() {
soundBehavior.makeSound();
}
// 동적으로 행동 변경 가능
public void setSoundBehavior(SoundBehavior soundBehavior) {
this.soundBehavior = soundBehavior;
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Animal(new BarkSound());
dog.makeSound(); // 출력: Woof!
dog.setSoundBehavior(new MuteSound());
dog.makeSound(); // 출력: ...
}
}
'Object' 카테고리의 다른 글
[Object] Chapter06. 메시지와 인터페이스 (0) | 2025.08.23 |
---|---|
[Object] Chapter05. 책임 할당하기 (6) | 2025.08.12 |
[Object] Chapter04. 설계 품질과 트레이드오프 (0) | 2025.07.26 |
[Object] Chapter03. 역할, 책임, 협력 (0) | 2025.07.22 |
[Object] Chapter01. 객체, 설계 (2) | 2025.07.14 |