이번 챕터는 객체지향 개념의 챕터인거 같다.
사실 react의 함수형 컴포넌트를 사용하다보면 객체지향과 멀어지는 경향이 있는것 같다.
그래도 사고를 말랑말랑하게 하기위해 읽어보면 좋을듯 싶다.
외부 사용자가 클래스 내부 변수에 의존하지 않게 하고,
외부에서의 조작으로 인해 의도치 않은 변경이나 버그를 막기 위해
흔히 private를 사용한다. (캡슐화, 정보 은닉)
이때 의무적으로? get, set 함수를 public 하게 외부에 노출시키는데 기계적으로 노출시키기보다는
이러한 방식을 좀 더 깊게 고찰해보자는 챕터인것 같다.
객체/자료구조
- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개(내부 구조 은닉)
- 자료 구조는 자료를 그대로 공개 (내부 구조 노출)
-
객체의 자료 추상화
구현을 감추기 위해선 추상화가 필요하다.
단순히 함수로 변수를 감춘다고 구현이 감춰지는것은 아니다. => 좀 더 고차원적인 사고가 필요
사용자가 어떻게 구현되었는지 몰라도(메소드 내부를 몰라도) 자료를 사용할수 있어야 좋은 코드 작성 방식이다.
자바 코드로 생각해보자. (js는 interface 개념이 없어서 java로 보는게 편하..다?)
//구체적인 Point class이고 명시적 직교 좌표계라는것을 알 수 있다.
public class Point {
public double x;
public double y;
}
// 이게 직교 좌표계인지 무엇인지 알수는 없다(추상화 되어있다) 다만 사용하고 싶은
// 자료를 명백히 제공한다.
// 추상적인 Point interface이고 클래스 메서드로만 접근 가능하게 강제한다.
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
//X,Y는 get할때 각각 구하는데 set은 한꺼번에 해야한다. => 개선 필요
위 코드의 public class는 구현을 외부로 노출하고
아래는 구현을 숨긴다.
일단 위의 point class는 private로 변수를 선언해도 getter, setter를 외부에 노출한다면 구현을 외부로 노출한다.
또 다음 예시를 보자.
//구체적 숫자 값으로 제공
public interface Vehicle {
public getFuelThankCapacityInGallons();
public getGallonsOfGasoline();
}
//연료 상태를 백분율이라는 추상적 값으로 제공
public interface Vehicle {
double getPercentFuelRemaining();
}
위 구체적 인터페이스보다는 자료를 사용할때 추상적으로 제공하는 아래의 인터페이스가 더 좋다.
결론적으로,
구현자는 사용자에게 '내부'를 알지 않아도 사용 가능하도록 추상화하는것이 좋다.
절차지향적 vs 객체지향적
본질적으로는 자료(구조)를 어떻게 사용할까에 대한 고민이다.
절차지향
절차지향적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
다만 새로운 자료구조를 추가하기 어렵다. (기존 함수를 전부 바꿔야함)
* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드
객체지향
객체지향적인 코드는 기존 함수는 그대로 놔두고 새로운 클래스를 추가하기 쉽다. (상속을 통해?)
다만 새로운 함수를 추가하기 어렵다. (그러기 위해서 모든 클래스를 고쳐야 한다.)
* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향
서로 상호보완적인 관계이니 무조건 한쪽만 사용하는것은 지양하자.
(객체지향적 사고가 100% 정답은 아님)
그런데 둘을 혼합하면 잡종 구조가 되니 이런 구조는 서로의 단점만 보이게된다. 그래서 비추천
디미터 법칙
잘 알려진 휴리스틱(경험에 기반하여 문제를 해결하거나 학습하거나 발견해 내는 방법, 최고가 아닌 최적의 방식)
모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙
정확히는 클래스 C의 메서드 f는 아래 객체의 메서드만 호출해야 한다.
- 클래스 C
- f가 생성한 객체
- f 인수로 넘어온 객체
- C 인스턴스 변수에 저장된 객체
이때 객체가 반환하는 객체의 메소드를 바로 호출하면 안된다. (잘 모르는것은 경계)
기차 충돌
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
객체가 한 줄로 이어져서 사용하는 방식으로 디미터 법칙을 무시한다.
일반적으로 조잡하다고 느껴질수 있기 때문에 가능하면 피하는것이 좋다.
해당 방식은 다음처럼 고치면 어떻게 될까?
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
이렇게 나눠도 ctext 객체가 opt를 가지고, scratchDir를 가지고 있다는 '내부 구조'를 알 수 있기 때문에
디미터 법칙을 위반할 수 있는 가능성이 존재한다.
그리고 이 위반 가능성은 ctxt가 '자료구조'인지 '객체' 인지에 따라 달라진다.
자료구조의 경우
final String outputDir = ctxt.options.scratchDir.absolutePath;
처럼 사용해도 디미터 법칙의 대상이 되지 않는다.
(자료구조는 내부 구조를 노출해도 되므로 상관X)
그럼 자료구조가 아닌, 객체라면 코드를 어떻게 개선해야할까?
'내부 방식'을 몰라도 작동할 수 있도록 개선하면 된다.
final String outputDir = ctxt.getScratchAbsolutePath();
자료 전달 객체(DTO)
전형적인 자료구조
공개 변수만 있고 함수가 없는 클래스(get, set까진 있다)
- 데이터 저장용(데이터베이스와 통신, 소켓에서 받은 메세지 구문 분석시 유용)
- 가공되지 않은 정보를 받아와 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 사용
(raw data)
class Address {
private final String street;
private final String streetExtra;
private final String city;
...
}
활성 레코드
활성 레코드는 DTO의 특수한 형태
공개 변수, 비공계 변수에 조회/설정 함수 제공
save, find 같은 탐색 함수 제공
데이터베이스, 다른 소스에서 자료를 직접 변환한 '결과'
비즈니스 로직 메서드를 활성 레코드에 넣는것은 옳지 않다. 자료구조로 취급하자.
결론
나는 잡종 구조?를 좀 많이 쓴편이긴 한데 객체와 자료구조를 분리해서 생각해야겠다
또한, 훌륭한 개발자는 편견에 빠지지 않는다. 알맞은 상황에 알맞게 쓰자
* 새로운 동작을 추가하는 유연성 -> 자료구조, 절차적인 코드
* 새로운 자료 타입을 추가하는 유연성 -> 객체 지향