Published on

6장. 객체와 자료 구조

6장. 객체와 자료 구조

변수를 private으로 선언하는 잉는 남들이 변수에 의존하지 않게 만들고 싶어서입니다.

그런데 아이러니 하게 getter, setter를 당연하게 제공하며 비공개 변수를 외부에 노출합니다.

왜 그럴까요?

자료 추상화

Getter, Setter로 무분별하게 구현을 노출시키는 것이 가장 나쁜 방식입니다.

그러기보다는 구현은 감추고 추상적인 개념으로 표현하는 것이 좋습니다.

필드를 감추고 Getter, Setter를 만든다고 캡슐화가 되는것이 아닙니다.

Getter, Setter를 일단 모두 넣어 놓은 그 모든 곳이 잘못되었다는 것입니다.

자료/객체 비대칭

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

두 정의는 본질적으로 정반대입니다.

절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽지만 새로운 자료 구조를 추가하기 어렵습니다.

객체 지향 코드는 기존 함수를 변경하지 않으면 새 자료구조를 추가하기 쉽지만 새로운 함수를 추가하기 어렵습니다.

이렇기에 모든 것이 객체라는 생각은 미신입니다.

떄로는 단순한 자료 구조와 절차적인 코드가 가장 적합한 상황도 있습니다.

디미터 법칙

디미터 법칙은 잘 알려진 휴리스틱으로, 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙입니디ㅏ.

디미터 법칙은 클래스 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();

위 코드처럼 작성하는 것이 좋습니다.

방식의 차이는 제쳐두고 두 방식 모두 디미터 법칙을 위반하는 것처럼 보입니다.

위반 여부는 ctxt, Options, ScratchhDir이 객체인지 자료 구조인지에 따라 달라집니다.

자료 구조라면 디미터 법칙이 적용되지 않기 때문입니다.

하지만 Getter함수를 통해 호출함으로써 객체인지 자료 구조인지 헷갈립니다.

final String outputDir = ctx.options.scratchDir.absolutePath;

위 처럼 작성하면 문제는 간단하지만, 단순한 자료 구조에도 Getter, Setter를 모두 적용하라 요구하는 프레임워크와 표준(예, Bean)이 존재합니다.

잡종 구조

이런 혼란으로 반은 객체, 반은 자료 구조잡종 구조가 나옵니다.

잡종 구조는 객체와 자료 구조의 단점만 모아 놓은 구조입니다.

프로그래머가 함수나 타입을 보호할지 공개할지 확신하지 못해 어중간하게 내놓은 설계에 불과하므로 피하는것이 좋습니다.

구조체 감추기

ctxt, options, scratchDir이 정말 객체라면 임시 디렉터리의 절대 경로는 어떻게 얻어야 할까요?

우선 디렉터리의 절대 경로를 얻는 이유부터 알아야 합니다.

String ouputFile = outputDir + "/" classNae.replace(',', '/') + ".class";
FileOutputStream fout = new FileOutputStream(ouputFile);
BufferedOutputStream fout = new BufferedOutputStream(fout);

위 로직을 보면 임시 파일을 생성하기 위함이라는 것을 알 수 있습니다.

그럼 ctxt객체에게 생성하라고 시키는 것이 더 좋습니다.

BufferedOuputStream bos = ctxt.createScratchFileStream(classFileName);

이처럼 작성하면 모듈은 자신이 모르는 여러 객체를 탐색할 필요가 없으므로 디미터 법칙을 위반하지 않습니다.

자료 전달 객체

자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스 입니다.

흔히 알고 있는 DTO가 이것입니다.

좀 더 일반적인 형태는 Bean 구조입니다.

Bean은 변수를 Getter, Setter를 사용하여 조작합니다.

일종의 사이비 캡슐화로 별다른 이익을 제공하지는 않습니다.

활성 레코드

활성 레코드는 DTO의 특수한 형태입니다.

DB 테이블이나 다른 소스에서 자료를 직접 변환한 결과입니다. (Entity)

여기에 비즈니스 로직을 추가하면 자료 구조도 아니고 객체도 아닌 잡종 구조가 나오게 되므로 옳지 않습니다.

활성 레코드자료 구조로 취급하고 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성하는 것이 옳습니다.