Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
3. 대상이 비록 실세계에서는 생명이 없는 수동적인 존재라고 하더라도 객체지향의 세계로 넘어오는 순간 그들은 생명과 지능을 가진 싱싱한 존재로 다시 태어난다.
4. 훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다. 세상에 엮인 것이 많은 사람일수록 변하기 어려운 것처럼 객체가 실행되는 주변
환경에 강하게 결합될 수록 변경하기 어려워진다.
5. 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.
객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.
6. 책임의 위치를 결정하기 위해 조건문을 사용하는 것은 협력의 설계 측면에서 대부분의 경우 좋지 않은 선택이다. 항상 예외 케이스를 최소화하고 일관성을 유지할 수 있는 방법을 선택하라.

## ⦿ 목차
* [1. 객체, 설계]( #1.-객체,-설계 )
Expand All @@ -31,4 +34,27 @@
사실 관람객이 가방을 가지고 있다는 사실과 판매원이 매표소에서 티켓을 판매한다는 사실을 Theater가 알아야할 필요가 없다.
Theater가 원하는 것은 관람객이 소극장에 입장하는 것뿐이다. 따라서 관람객이 스스로 가방 안의 현금과 초대장을 처리하고 판매원이
스스로 매표소의 티켓과 판매 요금을 다루게 한다면 이 모든 문제를 한 번에 해결할 수 있을 것 이다.
다시 말해서 관람객과 판매원을 **자율적인 존재**로 만들면 되는 것이다.
다시 말해서 관람객과 판매원을 **자율적인 존재**로 만들면 되는 것이다.

<br/>

## 2. 객체지향 프로그래밍
* [학습 코드](https://github.com/orchsik/study-object/pull/2)
* 클래스를 구현하거나 다른 개발자에 의해 개발된 클래스를 사용할 때 가장 중요한 것은 클래스의 경계를 구분 짓는 것이다. 클래스는 내부와 외부로 구분되며 훌륭한 클래스를
설계하기 위한 핵심은 어떤 부분을 외부에 공개하고 어떤 부분을 감출지를 결정하는 것이다. 클래스의 내부와 외부를 구분(구현 은닉)해야 하는 이유는 무엇일까?
* 경계의 명확성이 객체의 자율성을 보장하기 때문이다.
* 프로그래머에게 구현의 자유를 제공하기 때문이다. 클라이언트 프로그래머는 내부의 구현은 무시한 채 인터페이스만 알고 있어도 클래스를 사용할 수 있다.
클래스 작성자는 인터페이스를 바꾸지 않는 한 외부에 미치는 영향을 걱정하지 않고도 내부 구현을 마음대로 변경할 수 있다.
* 협력(Collaboration)
* 객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 **메시지를 전송(send a message)** 하는 것뿐이다. 다른 객채에게 요청이 도착할 때 해당 객체가
**메시지를 수신(receive a message)** 했다고 이야기 한다. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정한다.
이 처럼 수신된 메시지를 처리하기 위한 자신만의 방법을 **메서드(method)** 라고 부른다.
* 다형성
* 다형성은 메세지에 응답하기 위해 실행될 메서드를 컴파일 시점이 아닌 실행 시점에 결정한다는 공통점이 있다.
* 이를 **지연 바인딩(lazy binding)** 또는 동적 **바인딩(dynamic binding)** 이라고 부른다. <-> 초기 바인딩 또는 정적 바인딩.
* 부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식에게 위임하는 디자인 패턴을 TEMPLATE METHOD 패턴이라고 부른다.
* DiscountPolicy 클래스의 calculateDiscountAmount 메서드, getDiscountAmount 메서드 구현을 위임하고 있다.
* 헙성(Composition)
* 합성은 상속이 가지는 두 가지 문제점을 모두 해결한다. 인터페이스에 정의된 메시지를 통해서만 재사용이 가능하기 때문에 구현을 효과적으로 캡슐화할 수 있다.
또한 의존하는 인스턴스를 교체하는 것이 비교적 쉽기 때문에 설계를 유연하게 만든다. 상속은 클래스를 통해 강하게 결합되는 데 비해 합성은 메시지를 통해 느슨
하게 결합된다. 따라서 코드 재사용을 위해서는 상속보다는 합성을 선호하는 거싱 더 좋은 방법이다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.orchsik.object._02_movie;

public class AmountDiscountPolicy extends DefaultDiscountPolicy {
private Money discountAmount;

public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) {
super(conditions);
this.discountAmount = discountAmount;
}

@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}

}
4 changes: 4 additions & 0 deletions app/src/main/java/com/orchsik/object/_02_movie/Customer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.orchsik.object._02_movie;

public class Customer {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.orchsik.object._02_movie;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* AmountPolicy, PercentPolicy로 두 가지 할인 정책 구현
* 두 클래스는 대부분의 코드가 유사하고 할인 요금을 계산하는 방식만 조금 다르므로, DiscountPolicy 라는 부모 클래스 사용
* 실제 애플리케이션에서는 DiscountPolicy의 인스턴스를 생성할 필요가 없기 때문에 추상 클래스로 구현
*/
public abstract class DefaultDiscountPolicy implements DiscountPolicy {
private List<DiscountCondition> conditions = new ArrayList<>();

public DefaultDiscountPolicy(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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.orchsik.object._02_movie;

public interface DiscountCondition {

public boolean isSatisfiedBy(Screening screening);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.orchsik.object._02_movie;

public interface DiscountPolicy {
public Money calculateDiscountAmount(Screening screening);
}
45 changes: 45 additions & 0 deletions app/src/main/java/com/orchsik/object/_02_movie/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.orchsik.object._02_movie;

import java.math.BigDecimal;

/**
* 금액과 관련된 다양한 계산을 구현하는 간단한 클래스
*/
public class Money {
public static final Money ZERO = Money.wons(0);

private final BigDecimal amount;

public static Money wons(long amount) {
return new Money(BigDecimal.valueOf(amount));
}

public static Money wons(double amount) {
return new Money(BigDecimal.valueOf(amount));
}

Money(BigDecimal amount) {
this.amount = amount;
}

public Money plus(Money amount) {
return new Money(this.amount.add(amount.amount));
}

public Money minus(Money amount) {
return new Money(this.amount.subtract(amount.amount));
}

public Money times(double percent) {
return new Money(this.amount.multiply(BigDecimal.valueOf(percent)));
}

public boolean isLessThan(Money other) {
return amount.compareTo(other.amount) < 0;
}

public boolean isGreaterThanOrEqual(Money other) {
return amount.compareTo(other.amount) >= 0;
}

}
33 changes: 33 additions & 0 deletions app/src/main/java/com/orchsik/object/_02_movie/Movie.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.orchsik.object._02_movie;

import java.time.Duration;

public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private DiscountPolicy discountPolicy;

public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
this.title = title;
this.runningTime = runningTime;
this.fee = fee;
this.discountPolicy = discountPolicy;
}

public Money getFee() {
return fee;
}

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

/**
* 실행 시점에 할인 정책을 간단하게 변경할 수 있다
*/
public void changeDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.orchsik.object._02_movie;

public class NoneDiscountPolicy implements DiscountPolicy {

@Override
public Money calculateDiscountAmount(Screening screening) {
return Money.ZERO;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.orchsik.object._02_movie;

public class PercentDiscountPolicy extends DefaultDiscountPolicy {
private double percent;

public PercentDiscountPolicy(double percent, DiscountCondition... conditions) {
super(conditions);
this.percent = percent;
}

@Override
protected Money getDiscountAmount(Screening screening) {
return screening.getMovieFee().times(percent);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.orchsik.object._02_movie;

import java.time.DayOfWeek;
import java.time.LocalTime;

/**
* @dayOfWeek - 요일
* @startTime - 시작 시간
* @endTime - 종료 시간
*/
public class PeriodCondition implements DiscountCondition {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;

public PeriodCondition(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
this.dayOfWeek = dayOfWeek;
this.startTime = startTime;
this.endTime = endTime;
}

/**
* 상영 요일이 dayOfWeek와 같고 상영 시작 시간이 startTime과 endTime 사이에 있을 경우 true
*/
@Override
public boolean isSatisfiedBy(Screening screening) {
return screening.getStarTime().getDayOfWeek().equals(dayOfWeek)
&& startTime.compareTo(screening.getStarTime().toLocalTime()) <= 0
&& endTime.compareTo(screening.getStarTime().toLocalTime()) >= 0;
}

}
16 changes: 16 additions & 0 deletions app/src/main/java/com/orchsik/object/_02_movie/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.orchsik.object._02_movie;

public class Reservation {
private Customer customer;
private Screening screening;
private Money fee;
private int audienceCount;

public Reservation(Customer customer, Screening screening, Money fee, int audienceCount) {
this.customer = customer;
this.screening = screening;
this.fee = fee;
this.audienceCount = audienceCount;
}

}
49 changes: 49 additions & 0 deletions app/src/main/java/com/orchsik/object/_02_movie/Screening.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.orchsik.object._02_movie;

import java.time.LocalDateTime;

/**
* 사용자들이 예매하는 대상인 '상영'을 구현한다.
* 상영할 영화(movie), 순번(sequence), 상영시작시간(whenScreened) 를 인스턴스 변수로 포함.
*/
public class Screening {
private Movie movie;
private int sequence;
private LocalDateTime whenScreened;

public Screening(Movie movie, int sequence, LocalDateTime whenScreened) {
this.movie = movie;
this.sequence = sequence;
this.whenScreened = whenScreened;
}

public LocalDateTime getStarTime() {
return whenScreened;
}

public boolean isSequence(int sequence) {
return this.sequence == sequence;
}

public Money getMovieFee() {
return movie.getFee();
}

/**
* @param customer - 예매자 정보
* @param audienceCount - 인원수
* @return - 영화를 예매한 후 예매 정보를 담고 있는 Reservation의 인스턴스를 생성해서 반환
*/
public Reservation reserve(Customer customer, int audienceCount) {
return new Reservation(customer, this, calcuateFee(audienceCount), audienceCount);
}

/**
* @param audienceCount
* @return - 1인당 예매 요금
*/
private Money calcuateFee(int audienceCount) {
return movie.calculateMovieFee(this).times(audienceCount);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.orchsik.object._02_movie;

/**
* 할인 여부를 판단하기 위해 사용할 순번(sequence)을 인스턴스 변수로 포함
*/
public class SequenceCondition implements DiscountCondition {
private int sequence;

public SequenceCondition(int sequence) {
this.sequence = sequence;
}

@Override
public boolean isSatisfiedBy(Screening screening) {
return screening.isSequence(sequence);
}

}
Loading