자바 스프링 개발자를 위한 실용주의 프로그래밍
객체지향
1장 절차지향과 비교하기
-
순차지향과 절차지향은 엄밀히 다른 개념임.
- 순차지향 프로그래밍 : 말 그대로 코드를 위에서 아래로 읽음.
- 절차지향 프로그래밍 : 함수 지향 프로그래밍.
-
절차지향은 책임을 프로시저로 나누고 프로시저에 할당.
-
객체지향은 책임을 객체로 나누고 객체에 할당.
C언어는 객체지향을 구현할 수 없는 이유
객체를 추상화한 역할에 책임을 할당함. 예를 들어서
public interface Calcuable {
void calc(int a, int b)
}
이런식으로 인터페이스를 만들어서 객체를 추상화하고, 이 인터페이스를 구현한 클래스를 만들어서 책임을 할당함.
그러나 C언어는 이런 추상화를 할 수 없으며, 추상화가 불가능하다는 특징 때문에 객체지향을 구현할 수 없음.
객체지향이란?
객체가 책임을 갖게 됐고 객체의 역할이 정해졌으며, 어떤 목표를 달성하기 위해서 서로 다른 객체와 협력을 함. 이게 객체지향의 본질임.
객체지향 사고방식, TDA
- Tell Dont Ask : 객체에게 물어보지 말고 시켜라 라는 원칙
예를 들어서, 아래와 같은 코드가 있다고 치면
public class Shop {
public void sell(Account account, Product product) {
if (account.getBalance() > product.getPrice()) {
...
}
}
}
이 코드는 객체에게 물어보는 코드임. 객체에게 물어보는 것이 아니라 객체에게 시키는 코드로 바꾸면 아래와 같음.
public class Shop {
public void sell(Account account, Product product) {
if(account.canBuy(product)) {
...
}
}
}
이러한 변경을 통해 아래와 같은 3가지 이점을 얻을 수 있음.
- 유연성 증가 : 각 객체가 자신의 상태와 동작을 관리하므로 외부에서 객체 내부의 상태를 직접 알 필요가 없음
- 결합도 감소 : 객체 내부의 세부 사항이 감춰지므로 다른 객체와의 상호 의존성을 줄일 수 있음.
- 재사용성 향상 : canBuy() 메서드를 다른 클래스에서도 사용할 수 있음.
2장 객체의 종류
VO
- VO의 특징
- 불변성 : 값이 변하지 않음.
- 불변성을 지키기 위해서 모든 필드를 final로 선언.
- 모든 값은 원시타입이어야 함 👉 참조 타입이 있다면 참조 타입 내의 값이 변경 가능할 수 있기 때문.
- 👆 위와 같은 경우 안됨.
- VO 안의 모든 함수는 순수함수여야 함. (항상 같은 값을 반환해야 하고 Random 이딴거 쓰면 안됨.)
- final class로 선언되야 함. (상속되면 안됨.)
- 동등성 : 값의 가치는 항상 같음.
- equals()와 hashCode() 메서드를 오버라이딩해서 동등성을 보장. (값이 같으면 동일한 객체로 취급)
- 자가검증 : 값은 그 자체로 올바름. 1은 사실 1.01이지 않을까? 같은 고민을 할 필요 X.
- 불변성 : 값이 변하지 않음.
Entity
Entity는 3종류로 나눔.
- 도메인 엔티티
- DB 엔티티
- JPA 엔티티
프로그래밍 언어와 데이터베이스 분야에서 표현하고 싶은 유무형의 자산 정보를 지칭하는 데 개체라는 용어를 사용.
NoSQL에서 Entity
관계형 데이터베이스에서 사용하는 엔티티라는 용어는 도큐먼트 데이터베이스에서 도큐먼트라는 용어에 대응됨.
3장 행동
데이터 위주의 사고와 행동 위주의 사고
자동차를 만들어 달라는 요청에 두 개발자가 아래와 같이 구현함.
- A 개발자
public class Car {
private Frame frame;
private Engine engine;
private Tire tire;
...
}
- B 개발자
public class Car {
public void drive() {}
public void changeDirection() {}
public void accelerate() {}
...
}
A 개발자를 데이터 위주의 사고를 가진 개발자, B 개발자를 행동 위주의 사고를 가진 개발자라고 함.
- 데이터 위주로 만들어진 클래스는 구조적인 데이터 덩어리를 만드는 데 사용하는 구조체와 다를바 없음.
- 전혀 객체지향 스럽지 않음.
- 객체를 구분 짓는 요인은 데이터가 아닌 행동임.
덕 타이핑이란
덕 테스트: 만약 어떤 새가 뒤뚱뒤뚱 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.
- 덕 타이핑은 덕 테스트에서 유래한 용어임.
- 위 말을 개발자 관점에서 보면
행동이 같다면 같은 클래스로 부르겠다.
라는 의미. - Typescript는 덕타이핑을 지원.
class Duck {
walk() {
console.log('walk');
}
swim() {
console.log('swim');
}
}
class Person {
walk() {
console.log('walk');
}
swim() {
console.log('swim');
}
}
val duck: Duck = new Person();
- 행동이 곧 역할을 정의하고 역할이 곧 객체를 정의함.
구현을 생각하면 데이터 위주의 사고가 된다
public class Car {
private int degree; // 자동차의 각도(0도 ~ 360도)
public void changeDirection(int degree) {
this.degree = degree;
}
}
- 방향을 바꾸는 행동을 구현할려고 하다보니 degree라는 필드가 생김.
- 데이터 위주의 사고로 돌아옴.
- 구현을 고민했기 때문.
- 행동을 고민하면서 구현이나 알고리즘을 고민해서는 안됨.
- 협업시 인터페이스를 정의해놓고 각자 자리에서 본인의 역할을 다하면 됨.
인터페이스와 행동을 다르다
- 인터페이스는 외부에서 어떤 객체에게 행동을 시키고자 할 때 메시지를 보낼 수 있는 창구.
- 인터페이스란 어떤 행동을 지시하는 방법의 집합.
행동 위주의 사고를 하는 방법
- 실체에 집중할 때 데이터 위주의 사고를 하고 역할에 집중할 때 행동 위주의 사고를 함.
- ex : 자동차와 탈것, 자동차 -> 실체 / 탈것 -> 역할
- 행동과 역할에 집중하라는 것이 단순히 추상화를 많이 하라는 뜻이 아님.
함수와 메서드의 차이
- 함수의 각 입력값은 정확히 하나의 출력값으로 대응됨.
- 같은 입력에 대해 항상 같은 출력을 해야함.
- 같은 입력에 대해 두 개의 출력을 갖는 것은 함수가 아님.
- 예를 들어 Vehicle 인터페이스의 구현체 Car와 Airplane이 있을 때 vehicle.move() 함수의 결과가 다를 수 있다면 그건 함수가 아님.
- 메서드란 어떤 메시지를 처리해 달라는 요청을 받았을 때 이를 어떻게 처리하는지 방법(method)를 정의한 것.