[오브젝트 02] 객체지향 프로그래밍

초보개발·2022년 12월 10일
0

오브젝트

목록 보기
6/6

협력하는 객체들의 공동체

객체의 내부상태는 외부에서 접근하지 못하도록 감추고 외부에 공개하는 public interface를 통해 내부 상태에 접근할 수 있도록 허용함

  • 객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청
  • 요청을 받은 객체는 자율적인 방법에 따라 요청을 처리 후 응답

객체가 다른 객체와 상호작용할 수 있는 유일한 방법 - 메시지 전송
다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신
메시지를 받은 객체는 자신이 알아서 메시지를 처리하며, 이처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 함

메시지와 메서드를 구별하는 것도 중요함 -> 다형성의 개념 시작

template method 패턴

부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에게 위임하는 패턴

할인 정책: 금액 할인 정책(Amount), 비율 할인 정책(Percent)

  • 두 클래스의 대부분 코드가 중복되고 할인 요금을 계산하는 방식만 조금 다르므로 공통 코드를 보관할 장소인 DiscountPolicy 추상 클래스를 만들고 상속받도록 변경
    DiscountPolicy는 할인 여부, 요금 계산에 필요한 전체적인 흐름은 정의하지만 요금 계산하는 부분은 추상 메서드인 getDiscountAmount() 메서드에게 위임함, 상속받은 자식클래스에서 오버라이딩한 메서드가 실행되어 처리
public abstract class DiscountPolicy {
	private List<DiscountCondition> conditions = new ArrayList<>();
    
    public DiscountPolicy(DiscountCondition ... conditions) {
    	this.conditions = Arrays.asList(conditions);
    }
	
    public Money calculateDiscountAmount(Screening screening) {
    	for(DiscountCondition each : conditions) {
        	if (each.isSatisfiedBy(screening)) {
            	return getDiscountAmount(screening);
            }
        }
        return Money.ZERO;
    }
    
    abstract protected Money getDiscountAmount(Screening screening);
}

overriding, overloading

오버라이딩은 부모 클래스에 정의된 같은 이름, 파라미터 목록을 가진 메서드를 자식 클래스에서 재정의
오버로딩은 메서드의 이름만 같고 제공되는 파라미터의 목록이 다름

상속과 다형성

컴파일 시간 의존성과 실행 시간 의존성

어떤 클래스가 다른 클래스에 접근할 수 있는 경로를 갖거나 해당 클래스의 객체의 메서드를 호출할 경우 두 클래스 사이에 의존성이 존재한다고 말한다.

Movie 클래스는 DiscountPolicy 추상 클래스와 연결되어 있음

  • BUT, 영화 요금 계산을 위해선 추상클래스 DiscountPolicy가 아니라 AmountDiscountPolicy와 PercentDiscountPolicy의 인스턴스가 필요함
  • Movie의 인스턴스는 실행시에 해당 인스턴스에 의존해야 하지만 코드 수준에서 Movie 클래스는 두 클래스 중 어떤 것에도 의존하지 않음 (오직 추상 클래스만 의존)

Movie가 AmountDiscountPolicy와 PercentDiscountPolicy의 인스턴스와 실행 시점에 협력 가능한 이유

  • Movie 인스턴스를 생성하는 코드
Movie avatar = new Movie("아바타", Duration.ofMinutes(120),
				Money.wons(10000), new AmountDiscountPolicy(Money.wons(800), ...));

영화 요금 계산에 금액 할인 정책을 적용하고 싶다면 Movie 인스턴스를 생성할 때 인자로 AmountDiscountPolicy의 인스턴스를 전달하기만 하면 됨
-> 실행시에 Movie 인스턴스는 AmountDiscountPolicy 클래스의 인스턴스에 의존하게 됨

비율 할인 정책을 적용하고 싶다면 PercentDiscountPolicy의 인스턴스 전달

Movie avatar = new Movie("아바타", Duration.ofMinutes(120),
				Money.wons(10000), new PercentDiscountPolicy(0.1, ...));

여기서 말하고 싶은건, 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다는 것
즉, 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있음

  • 유연하고 쉽게 재사용할 수 있고 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다름!

차이에 의한 프로그래밍

가정: 클래스를 하나 추가하고 싶은데, 이미 존재하는 클래스와 매우 유사할 경우

  • 그 클래스 코드를 전혀 수정하지 않고도 재사용하는 것이 베스트 -> 이를 가능하게 해주는 것이 상속

상속을 이용하면 클래스 사이에 관계를 설정하는 것만으로 기존 클래스가 갖고있는 모든 속성과 행동을 새로운 클래스에 포함시킬 수 있음

  • 코드 중복 제거, 여러 클래스에서 동일한 코드를 공유 받음

이처럼 부모 클래스와 다른 부분만 추가해 새로운 클래스를 쉽고 빠르게 만드는 방법을 차이에 의한 프로그래밍(programming by difference)라고 함

상속과 인터페이스

상속이 가치가 있는 이유: 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문
자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있어서 외부 객체는 자식 클래스를 부모 클래스와 동일한 타입으로 간주 가능

public class Movie {
	public Money calculateMovieFee(Screening screening) {
    	return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}

Movie가 DiscountPolicy에 정의된 calculateDiscountAmount 메시지를 전송하고 있음

  • Movie는 협력 객체가 calculateDiscountAmount 메시지를 이해할 수만 있다면 그 객체가 어떤 클래스의 인스턴스인지는 상관하지 않음
  • calculateDiscountAmount 메시지를 수신할 수 있는 AmountDiscountPolicy, PercentDiscountPolicy 모두 DiscountPolicy를 대신해서 Movie와 협력 가능

자식 클래스는 상속을 통해 부모 클래스의 인터페이스를 물려받으므로 부모 클래스 대신 사용 가능
컴파일러는 코드 상에서 부모 클래스가 나오는 모든 장소에서 자식 클래스를 사용하는 것을 허용

  • Upcasting: 자식클래스가 부모클래스를 대신하는 것

0개의 댓글