변수는 보통 private 형으로 정의한다.
캡슐화를 통해 외부에서 접근하지 못하게 한다.
의존성을 없애서 구현을 맘대로 변경하기 위해서다.
1. 자료 추상화
포인트 라는 자료구조를 나타내는 클래스가 있다.
public class Point {
public double x;
public double y;
}
이 클래스의 인스턴스 변수는 public 으로 선언되어 값에 직접 접근이 가능하다.
이는 클래스간의 의존성을 높이고 결합도를 높인다.
그래서 인스턴스 변수를 priavte으로 바꾸고 추상 인터페이스를 만들었다.
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
private 형 변수를 위해 우리는 흔히 getter, setter을 사용한다.
이 getter, setter의 사용은 그냥 구현을 외부로 노출하는 것과 크게 다르지 않다.
추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작 할 수 있어야 진정한 의미의 클래스다.
아래를 보자.
탈것의 주유 용량과 현재 주유량을 getter로 가져온다.
private으로 선언했기에 getter을 썼나보다.
public interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
우리는 현재 주유된 양의 퍼센티지를 알기 위해 이 두 값을 꺼내오는 getter 메서드를 선언했다.
하지만 아래와 같이 구현한다면?
Public interface Vehicle {
double getPercentFeulRemaining();
}
메소드의 네이밍은 get~ 이지만 이 메서드는 클래스의 인스턴스 변수 값을 그대로 가져오는 Getter로 설계되지 않았다.
클래스에 메세지를 보내서, 클래스 내에서 주유 용량과 현재 주유량을 이용하여 백분율을 구한 후 백분율 값을 반환하게 설계하였다.
getPercentFeulRemaining() 을 호출하는 입장에서는 호출받는 내부에서 어떤 것들로 어떻게 구현하는지 전혀 알 방법이 없다.
이것이 추상화이며 완전한 캡슐화이다.
2. 자료/객체 비대칭
자료구조를 사용하는 절차적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
하지만 새로운 자료구조를 추가하려면 자료구조를 사용하는 메소드가 정의된 클래스 전체를 바꿔야 하기에 어렵다.
객체를 사용하는 객체지향 코드는 기존 함수를 변경하지 않으면서 새로운 클래스를 추가하기가 쉽다.
하지만 새로운 함수를 추가하기가 어렵다. 그러려면 모든 클래스를 고쳐야 한다.
자료와 객체는 상호 보완적이라 할 수 있다.
3. 디미터 법칙
디미터의 법칙 : 모듈은 자신이 조작하는 객체의 속을 몰라야 한다.
객체의 자료를 숨기고(private) 함수를 공개(public)해야 한다.
getter, setter를 사용하면 안된다.
디미터 법칙 (객체 O의 메소드 m)
다음과 같은 메소드들의 호출만 허용하라.
1. O 객체 자신의 메소드들.
2. m의 파라미터로 넘어온 객체들의 메소드들.
3. m 안에서 생성 되거나 초기화된 객체의 메소드들.
4. O객체의 직접 소유하는 객체의 메소드들.
5. O객체의 m에서 접근이 가능한 전역변수의 메소드들.
예를 들면 다음과 같은 코드는 디미터의 법칙을 어긴다.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
객체의 메소드의 반환객체의 메소드의 반환객체의 메소드를 사용하는 상황이다.
3-1. 기차 충돌
위 코드와 같은 상황을 기차 충돌이라 한다.
객체가 점으로 이어져 있기 때문이다.
이 방식은 피하는 편이 좋다.
대신 아래와 같이 작성한다.
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
이 코드는 디미터 법칙을 만족할까?
ctxt, Options, ScratchDir이 객체라면 getter로 내부를 노출하고 있기 때문에 디미터의 법칙을 위배한다.
하지만 자료구조라면, 물론 ctxt.options 처럼 바로 호출하지 않고 getter를 사용하여 혼란을 일으키긴 하지만,
당연히 내부 구조를 공개하므로 문제가 되지 않는다.
자료구조라면, 아래와 같이 작성하면 디미터의 법칙을 고민할 필요 없이 깔끔하게 작성할 수 있다.
final String outputDir = ctxt.options.scatchDir.absolutePath;
3-2. 잡종 구조
공개 변수와 로직을 처리하는 함수를 같이 쓰거나 하는 잡종구조는
프로그래머가 변수의 접근범위를 판단하지 못해 발생하는 단점만 모아놓은 안좋은 구조다.
3-3. 구조체 숨기기
만약 3-1의 ctxt, Options, ScratchDir이 객체라면? 어떻게 해야 할까.
객체는 내부 구조를 숨겨야 한다.
ctxt.getAbsolutePathOfScratchDirectoryOption();
ctxt.getSrcatchDirectoryOption().getAbsolutePath();
첫번째 방법은 ctxt 객체 내부에 공개해야 하는 정보가 너무 많아진다.
두번째 방법은 기차 충돌이 일어나고 있다.
ctxt가 객체라면, 내부의 정보를 꺼내오는 것이 아니라 메세지를 보내 일을 해달라고 해야한다.
이 코드는 절대 경로를 받아오는 코드다.
이 프로그램에서 절대 경로는 왜 필요할까? 프로그램의 아래에는 이 절대경로값을 이용하여 임시 파일을 생성한다.
그렇다면 ctxt에서 값을 가져와서 임시 파일을 생성하는 것이 아니라, 임시 파일을 생성하기 위해 필요한 정보를 가지고 있는 ctxt에게 임시 파일을 생성하라고 메세지를 보낸다.
ctxt.createScratchFileStream(classFileName);
4. 자료 전달 객체(DTO. Data Transfer Object)
자료 구조체의 전형적인 형태는 공개 변수만 있고, 함수가 없는 클래스이다.
또는 비공개 변수에 변수를 조작할 수 있는 getter, setter가 있다.
자료 구조체는 때로 자료 전달 객체라고 부른다.
보통 DTO는 데이터베이스에서 꺼내온 가공되지 않은 값들을 에플리케이션에서 사용하기 위해 객체로 변환하는 과정에서 가장 처음 사용하는 구조체이다.
Spring의 Bean이 일반적인 형태이다.
DTO는 자료구조이므로 내부에 비지니스 규칙 메소드를 정의하지 않는다.
데이터베이스에서 DTO를 통해 값을 꺼내온 후, 비즈니스 로직을 처리하는 클래스를 따로 만들어 그 객체에 값을 옮겨 처리하도록 한다.
자료구조는 절차지향적인 코드에서 쓰이는 구조이며 공개 변수만 있다.
객체는 객체지향 코드에서 쓰이며 내부 구조를 숨긴다.
계속 새로운 자료 타입이 추가되는 프로그램에는 객체가 적합하다.
계속 새로운 동작이 추가되는 프로그램에는 자료구조가 적합하다.
프로그래머는 이를 인지하고 유연성 있게 선택을 해야한다.