Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
> 3. 특별한 훈련 없이도 개발자가 쉽게 읽고 이해해야 한다."
> - Robert C Matin -

# [2. 코드로 이해하는 객체지향 설계](https://github.com/ca1af/object/blob/main/src/chapter_two/chapter_two.md)
32 changes: 32 additions & 0 deletions object.iml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,38 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library" exported="" scope="TEST">
<library name="JUnit5.8.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="bb9fb149-f35d-425b-a727-414d085b4ec4" />
Expand Down
4 changes: 4 additions & 0 deletions src/chapter_two/Customer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package chapter_two;

public class Customer {
}
40 changes: 40 additions & 0 deletions src/chapter_two/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package chapter_two;

import java.math.BigDecimal;

/**
* 1 장에서는 금액을 구현하기 위해 Long 타입을 사용했다.
* Money 객체로 바꿈으로써 어떤 것을 얻었을까?
*/
// TODO: 12/6/23 바꿈으로써 얻는 이익을 내 언어로 설명하기
public class Money {
public static final Money ZERO = Money.wons(0);
private final BigDecimal amount;

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

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

public Money plus(Money amount){
return new Money(this.amount.add(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 isGreaterOrEqual(Money other){
return amount.compareTo(other.amount) >= 0;
}
}
30 changes: 30 additions & 0 deletions src/chapter_two/Movie.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package chapter_two;

import chapter_two.discount.policy.DiscountPolicy;

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.calculateDiscountPolicy(screening));
}
}
7 changes: 7 additions & 0 deletions src/chapter_two/Reservation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package chapter_two;

public class Reservation {
public Reservation(Customer customer, Screening screening, Money money, int audienceCount) {

}
}
37 changes: 37 additions & 0 deletions src/chapter_two/Screening.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package chapter_two;

import java.time.LocalDateTime;

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 getStartTime() {
return whenScreened;
}

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

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

public Reservation reserve(Customer customer, int audienceCount){
return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
}

private Money calculateFee(int audienceCount) {
return movie.calculateMovieFee(this).times(audienceCount);
}


}
139 changes: 139 additions & 0 deletions src/chapter_two/chapter_two.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# 객체 지향 프로그래밍

## 협력, 객체, 클래스

> 객체지향은 객체를 지향하는 것이다.

- 클래스를 먼저 고려하는 것이 아니라, 객체에 초점을 맞추는 것이 옳다. 이 방법은 아래와 같다.

1. 어떤 클래스가 필요한지 고민하기 전에, 어떤 객체들이 필요한지 고민하라.
- 클래스는 공통적인 상태/행동 을 공유하는 객체들을 __추상화__ 한 것이다.
- 따라서 __상태와 행동__을 가지는지를 먼저 결정하라.
- 이는 설계를 단순하고 깔끔하게 만든다.

2. 둘째, 객체를 협력하는 공동체의 일원으로 보라.
- 다른 객체에게 도움을 주거나, 의존하는 것이 객체이다.
- 객체를 공동체의 일원(협력체)으로 생각하면 유연하며, 확장 용이한 설계가 가능하다.

3. 객체들의 공통 특성과 상태를 종합하여 클래스를 구현해라. 훌륭한 클래스는 훌륭한 객체로부터 시작한다.


---

## 도메인의 구조를 따르는 프로그램 구조

### 도메인이란 무엇인가

> "문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야"
> -p41

- 도메인은 일종의 개념이고, 이 개념을 구현하기 위해 클래스를 사용하는 경우가 대부분이다.

예를 들어 영화 상영을 위해

1. 영화
2. 상영
3. 할인 정책

등이 있다고 하면, 각각

1. Movie class
2. Screening class
3. Discount Policy class(interface)

로 대응 가능하다.

여기서 중요한 것은 __클래스의 구조__가 __도메인의 구조__와 유사한 형태를 띄어야 한다는 것.

도메인이 -> 클래스로 구현된다는 사실이다.

---

### 자율적인 객체

1. 객체는 상태와 행동을 가진다.
2. 객체는 스스로 판단하고 행동하는 자율적인 존재이다.

이 두가지 사실은 깊이 연관되어 있다.

- 데이터와 기능ㅇ을 객체 내부로 함께 묶는 것을 __캡슐화__ 라고 한다.
- 상태와 행동을 캡슐화하는 것에서 한 걸음 더 나아가, 객체지향 언어는 __접근제어__ 메커니즘을 제공한다.
- 객체 내부에 대한 접근을 제어(통제)함으로써 객체는 더 자율적이 된다. 스스로 상태를 관리하고, 행동하는 객체가 되는 것을 돕는다.

위의 캡슐화와 접근제어는 객체를 두 부분으로 나누는 역할을 한다.

1. 외부에서 접근 가능한 부분, __Public Interface__ : 외부에 공개하는 public method
2. 오직 내부에서 접근 가능한 부분, __Implementattion(구현)__ : 공개하지 않는 그 모든 부분과, 객체의 속성(필드)

---

### 프로그래머의 자유

프로그래머의 역할을 두 개로 분리하는 것이 유리하다.

1. 클래스 작성자(class creator) - 내부 구현 및 외부 인터페이스를 정의한다
2. 클라이언트 프로그래머(client programmer) - 정의된 외부 인터페이스를 통해 기능을 작성한다.

객체의 내부와 외부를 분리함으로써

1. 클라이언트는 외부 인터페이스만을 알아도 된다(내부 구현은 신경 쓰지 않아도 된다)
2. 클래스 작성자는 외부에 미치는 영향을 걱정하지 않아도 된다.

---

### 메시지와 매서드

1. 객체가 다른 객체와 상호작용 할 수 있는 유일한 방법은 메시지를 전송하는 것이다.
2. 메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메싲리르 처리할 방법을 결정한다.
3. 이 수신된 메시지를 처리하기 위한 방법을 __매서드(method)__ 라고 부른다.

- 메시지와 매서드를 구분함으로써 "다형성" 개념이 출발한다.
- Screening 이 Movie 에게 "영화 가격을 계산하라" 는 메시지를 보내면 -> Movie 는 스스로의 매서드(calculateMovieFee)로 처리 후 응답한다.
- Movie 스스로가 메시지에 대한 응답을 선택 가능하다(이 매서드의 내부 구현은 Movie 에게 책임이 있다.)

---

## 질문 할 것 - 1 Override vs Overload?

## 상속과 다형성

![img.png](images%2Fimg.png)

위 클래스 다이어그램을 보면

1. Movie 는 DiscountPolicy 를 의존한다.
2. 그러나 실제 계산은 DiscountPolicy 의 하위 구현체인 Amount || Percent 가 필요하다.

> 하지만 코드 수준에서 Movie 클래스는 구현체에 의존하지 않는다.

그렇다면 Movie 인스턴스가 코드 작성시점에는 존재조차 몰랐던 실제 구현체를 실행 시점에 협력 가능한 이유가 무엇일까?

class 의 관점이 아닌 인스턴스의 관점, 즉 코드 상이 아닌 실행 시점상에서 생각해보자 -> movie Test

---

### 코드의 의존성과 실행시점의 의존성은 서로 다를 수 있다.

- Movie 클래스는 DiscountPolicy 라는 추상을 상속하는 어떤 녀석이든, 메세지를 잘 전달받아서 매서드를 수행하는 녀석이라면 신경쓰지 않는다.
- 따라서 Movie 클래스는 DiscountPolicy 측이 변경되든, 확장되든 같은 메시지를 보내며, 기대하는 응답 또한 받을 수 있을 것이다

그리고 이와 같은 구조는 유연하지만, 대신에 실제 객체(DiscountPolicy 의 구현체) 가 하는 일을 실제로 알기 위해서는 Movie 인스턴스의 생성부를 살펴봐야 한다는 단점이 있다. 즉, 복잡하다.

즉, 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다.

> 따라서 유연성과 가독성 사이에서 항상 고민해야 한다. 완벽한 정답은 없다.

---

### 질문 2 - 상속을 언제 사용하나요?

### + 전략 패턴? 합성?(Composition)

합성의 정의

> __인터페이스에 정의된 메시지를 통해서만 코드를 재사용하는 방법__



### 추상 클래스인 DiscountPolicy 를 Interface 로 바꾸며, NoneDiscountPolicy 추가해보기

11 changes: 11 additions & 0 deletions src/chapter_two/composition/Heritor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package chapter_two.composition;

public class Heritor extends SuperHeir {
public Heritor(int foo) {
super(foo);
}

public void iGotFooTo(){
this.printFoo();
}
}
12 changes: 12 additions & 0 deletions src/chapter_two/composition/SuperHeir.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package chapter_two.composition;

public class SuperHeir {
private final int foo;

public void printFoo(){
System.out.println(foo + "는 내건데?");
}
public SuperHeir(int foo) {
this.foo = foo;
}
}
7 changes: 7 additions & 0 deletions src/chapter_two/discount/condition/DiscountCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package chapter_two.discount.condition;

import chapter_two.Screening;

public interface DiscountCondition {
boolean isSatisfiedBy(Screening screening);
}
25 changes: 25 additions & 0 deletions src/chapter_two/discount/condition/PeriodCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package chapter_two.discount.condition;

import chapter_two.Screening;

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

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;
}

@Override
public boolean isSatisfiedBy(Screening screening) {
return screening.getStartTime().getDayOfWeek().equals(dayOfWeek) &&
startTime.compareTo(screening.getStartTime().toLocalTime()) <= 0 &&
endTime.compareTo(screening.getStartTime().toLocalTime()) >= 0;
}
}
19 changes: 19 additions & 0 deletions src/chapter_two/discount/condition/SequenceCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package chapter_two.discount.condition;

import chapter_two.Screening;

/**
* 상영 순서(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