Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10주차 3] Clean Code: 6장 객체와 자료구조 #114

Merged
merged 1 commit into from
Dec 16, 2024
Merged
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
234 changes: 234 additions & 0 deletions hong/clean-code/6. 객체와 자료구조.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# 6장: 객체와 자료구조

남들이 변수에 의존하지 않도록 변수는 비공개(private)로 정의한다.

그렇다면 어째서 조회(get) 함수와 설정(set) 함수를 당연하게 공개(public)해 변수를 외부에 노출할까?

> 객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다.
>

> 자료 구조는 별다른 동작 없이 자료를 노출한다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다.
>

## 자료 추상화

변수 사이에 함수라는 계층을 넣는다고 구현이 저절로 감춰지지는 않는다. 구현을 감추려면 추상화가 필요하다!

변수를 private으로 선언하더라도 각 값마다 get 함수와 set 함수를 제공한다면 구현을 외부로 노출하는 셈이다.

조회 함수와 설정 함수로 변수를 다룬다고 클래스가 되지는 않는다.

추상추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 클래스다.

- 구체적인 Point 클래스 (구현을 외부로 노출)

```java
public class Point {
private double x;
private double y;
}
```

- 추상적인 Point 클래스 (구현을 완전히 숨김)

```java
public interface Point {
double getX();
double getY();
void setCatesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
```

- 구체적인 Vehicle 클래스 (구현을 외부로 노출)

```java
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
```

- 추상적인 Vehicle 클래스 (구현을 완전히 숨김)

```java
public interface Vehicle {
double getPercentFuelRemaining();
}
```

**자료를 세세하게 공개하기보다는 추상적인 개념으로 표현해야 한다.**

**인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않는다.**

## 자료/객체 비대칭

- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
- 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.

### 절차적인 도형 클래스

```java
public class Square {
public Point topLeft;
public double side;
}

public class Rectangle {
public Point topLeft;
public double height;
public double width;
}

public class Circle {
public Point center;
public double radius;
public double width;
}

public class Geometry {
public final double PI = 3.141592;

public double area(Object shape) throws NoSuchShapeException {
if (shape instanceOf Square) {
Square s = (Spuare) shape;
return s.side * s.side;
} else if (shape instanceOf Rectangle) {
Rectangle r = (Rectangle) shape;
return r.height * r.width;
} else if (shape instanceOf Circle) {\
Circle c = (Circle) shape;
return PI * c.radius * c.radius;
}
}
}
```

Geometry 클래스는 세 가지 도형 클래스를 다룬다. 각 도형 클래스는 간단한 자료 구조다. 아무 메서드도 제공하지 않고, 도형이 동작하는 방식은 Geometry 클래스에서 구현한다. 만약 Geometry 클래스에 함수를 추가하고 싶다면 도형 클래스는 아무 영향도 받지 않는다.

새 도형을 추가하고 싶다면 Geometry 클래스에 속한 함수를 모두 고쳐야 한다.

### 객체 지향적인 도형 클래스

```java
public class Square implements Shape {
public Point topLeft;
public double side;

public double area() {
return side * side;
}
}

public class Rectangle implements Shape {
public Point topLeft;
public double height;
public double width;

public double area() {
return height * width;
}
}

public class Circle implements Shape {
public Point center;
public double radius;
public double width;

public double area() {
return PI * radius * radius;
}
}
```

`area()`는 다형 메서드다. Geometry 클래스는 필요 없다.

새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다.

반면 새 함수를 추가하고 싶다면 도현 클래스를 전부 고쳐야 한다.

> 자료 구조를 사용하는 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
>

> 절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다. 객체 지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.
>

즉, 객체 지향 코드에서 어려운 변경은 절차적인 코드에서 쉬우며, 반대는 객체 지향 코드에서 쉽다.

## 디미터 법칙

> 메소드가 반환하는 객체의 메소드를 사용하면 안 된다.
>

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다. 객체는 자료를 숨기고 함수를 공개한다. → 객체는 조회 함수로 내부를 공개하면 안된다는 의미다. 내부 구조를 숨기지 않고 노출하는 셈이다.

### 기차 충돌

여러 객체가 한 줄로 이어진 기차처럼 보이고 이런 구조는 조잡하다고 여겨진다.

```java
String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
```

→ 메소드가 반환하는 객체의 메소드를 사용해서 디미터 법칙 위반.

→ 아래 코드로 변환하는 것이 바람직함.

```java
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
String outputDir = scratchDir.getAbsolutePath();
```

→ ctxt, opts, scratchDir이 객체라면 디미터 법칙 위반

→ ctxt, opts, scratchDir이 자료 구조라면 내부 구조를 노출하므로 디미터 법칙이 적용되지 않음.

```java
final String outputDir = ctxt.opts.scratchDir.absolutePath;
```

코드를 위와 같이 구현했다면 디미터 법칙을 거론할 필요가 없어진다.

자료 구조는 무조건 함수 없이 공개 변수만 포함하고, 객체는 비공개 변수와 공개 함수를 포함한다면 된다.

### 잡종 구조

절반은 객체, 절반은 자료 구조인 잡종 구조.

- 공개 변수, 공개 함수, 주요 함수, getter, setter 모두 섞여 있는 구조.
- 클래스, 자료 구조 양쪽에서 단점만 모아 놓은 피해야 할 구조.
- 새로운 함수는 물론, 새로운 자료 구조도 추가하기 어렵다.

### 구조체 감추기

```java
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
```

위 코드에서 디렉토리 경로를 얻는 `목적`은 임시 파일 생성을 위함이다. 따라서 ctxt 객체가 최종 목적인 임시 파일을 생성하도록 명령하는 함수를 만들어서 호출한다면? ctxt 객체는 내부 구조를 드러내지 않으며, 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없게 된다.

## 자료 전달 객체 (DTO)

자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다. 이런 자료 구조를 DTO라고 한다.

- DTO: 공개 변수만 있고 함수가 없는 클래스.
- Bean: 비공개 변수와 getter, setter가 있는 클래스.
- 활성 레코드: 공개, 비공개 변수와 getter, setter, 그리고 탐색 함수가 있는 클래스.

## 결론

- 객체는 동작을 공객하고 자료를 숨긴다.
- 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉽다.
- 기존 객체에 새 동작을 추가하기는 어렵다.
- 자료 구조는 별다른 동작 없이 자료를 노출한다.
- 기존 자료 구조에 새 동작을 추가하기는 쉽다.
- 기존 함수에 새 자료 구조를 추가하기는 어렵다.
- 바람직한 구조
- 객체: 비공개 변수와 공개 함수만 포함
- 자료 구조: 함수 없이 공개 변수만 포함
- 적합한 쓰임
- 객체: 새로운 자료 타입 추가
- 자료 구조: 새로운 메소드 추가
Loading