Object

[Object] Chapter02. 객체지향 프로그래밍

개발자 문문 2025. 7. 15. 22:20

안녕하세요 개발자 문문입니다. 

오늘은 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에 의존한다.

 

  • 상속과 인터페이스

- 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문입니다.

- 인터페이스는 객체가 이해할 수 있는 메시지의 목록을 정의합니다.

 

  • 다형성

- 위의 예시에서 DiscountPolicycalcul()이라는 메서드가 정의되어 있고, 두 자식 클래스가 이를 오버라이딩 했다고 가정하겠습니다.

- Moviecalcul()이라는 동일한 메시지를 전송하지만 메시지를 수신하는 클래스에 따라 수행되는 메서드가 달라집니다.

- 이를 다형성이라고 합니다.

- 지연 바인딩(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();  // 출력: ...
    }
}