개인적 감상평
결론부터 얘기하자면, 새로운 자료 타입 추가에 대한 유연성이 필요할때는 객체, 새로운 동작에 대한 유연성이 필요하면 자료 구조와 절차적인 코드를 사용하자.
굳이 OOP에 집착할 필요 없이 그때 그때 상황에 맞는 방법을 선택하면 된다.
내가 평소에 생각했던 부분을 얘기해 준 파트였다. 기껏 객체에서는 각종 필드들을 private로 선언해놓고 접근을 막아놓고서는 setter, getter 를 만들어서 접근/제어가 가능하게 만들면 의존성이 생기고 의존성이 생기면 결합도가 높아지면서 변경이 어려워진다.
그렇다면 어떻게 해야할까? 에 대한 내용으로 객체간에는 메세지만 주고받는다는 말을 설명하는 파트다.
1. 자료 추상화
단순히 변수(field)사이에 함수(method)라는 계층을 넣는다고 구현이 감춰지지는 않는다.
구현을 감추려면 추상화가 필요하다.
추상 인터페이스를 제공해 사용자가 구현을 모른채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다.
//구체적인 Vehicle 클래스
public interface Vehicle{
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
//추상적인 Vehicle 클래스
public interface Vehicle{
double getPercentFuelRemaining();
}
Java
복사
단순히 클래스를 인터페이스화하여 공통 메소드를 뽑아내어 추상화를 할 게 아니라 인터페이스 내에서도 너무 많은 자료를 노출하는 메소드보다는 추상적인 메소드를 만들 필요가 있다.
자료/객체 차이
객체와 자료 구조 사이에는 서로의 장단점이 명확합니다.
•
객체: 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
•
자료 구조: 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다.
객체와 자료구조가 서로 제공하는 것이 상반되는데 이게 어떤 트레이드 오프를 가질지 생각해보자.
우선 자료구조 도형 클래스를 제공하며 여기서 Geometry 클래스에서 도형을 구현하면
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 class Geometry {
public final double PI = 3.141592653589793;
public double area(Objects shape) throws NoSuchShapeException {
if (shape instanceof Square) {
Square s = (Square) 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;
}
throw new NoSuchShapeException();
}
}
Java
복사
•
절차지향 코드의 이점
◦
Geometry클래스에 새로운 함수를 추가할 때 도형 클래스는 수정하지 않아도 된다.
•
절차지향 코드의 단점
◦
새로운 도형을 추가한다면 Geometry 클래스의 속한 함수를 모두 고쳐야 한다.
반면 객체지향적인 코드로 각 도형들을 다형성을 이용해 구현한다면
public class Square implements Shape {
private Point topLeft;
private double side;
public double area() {
return side * side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
public double area() {
return height * width;
}
}
public class Rectangle implements Shape {
private Point center;
private double radius;
public final double PI = 3.1415926533589793;
public double area() {
return PI * radius * radius;
}
}
Java
복사
•
객체지향적인 코드의 이점
◦
기존 코드나 함수를 수정하지 않고 새로운 클래스를 추가하기 쉽다.
•
객체지향적인 코드의 단점
◦
새로운 함수를 추가하기 위해서는 모든 클래스를 고쳐야 한다.
3. 디미터 법칙
모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.
- 실용주의 프로그래머 (227페이지)
•
객체는 조회 함수로 내부 구조를 공개하면 안 된다.
•
클래스 C의 메서드 f는 다음과 같은 메소드만 객체의 메소드만 호출해야 한다.
◦
클래스 C
◦
f 가 생성한 객체
◦
f 인수로 넘어온 객체
◦
C 인스턴스 변수에 저장된 객체
기차 충돌
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Java
복사
흔히 위와 같이 메소드체이닝한 코드를 기차 충돌(train wreck)이라 부른다.
일반적으로 조잡하기 때문에 피하는 편이 좋고 나누는게 좋다.
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
Java
복사
하지만, 위와 같이 코드를 작성하면 디미터 법칙을 위반하지 않는걸까? 위 코드가 객체라면 내부 구조를 숨겨야 하기에 디미터 법칙을 위반한다.
자료구조라면 아래와 같이 이미 노출된 내부 구조를 사용하기에 디미터 법칙을 위반하지 않는다.
final String outputDir = ctxt.options.scratchDir.absolutePath;
Java
복사
잡종 구조
단순한 자료구조에도 조회 함수와 설정 함수를 정의하라 요구하는 프레임워크와 표준(ex: bean)이 존재하기에 객체와 자료구조가 섞인 잡종 구조가 나오는데, 이는 새로운 함수, 새로운 자료 구조 둘 다 추가하기 어려운 단점만 모아놓은 구조로 피하는 편이 좋다.
4. 해결책
구조체 감추기
ctxt, options, scratchDir이 객체라면 위와 같이 체이닝을 해서는 안된다. 객체는 내부 구조를 감춰야 하기에 내부를 노출하는 get 함수는 썩 내키지 않는다.
그럴 때는 이 로직을 살펴보는게 좋은데, 해당 로직은
String outFile = outputDir + "/" + className. replace( '.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);
Java
복사
이런 식으로 절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위함이라는 것을 알았다.
그렇다면, 임시 파일을 생성하는 것을 해당 객체가 아닌 ctxt 객체에게 위임하면 어떨까?
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
Java
복사
이제 임시파일 생성에 대한 책임이 ctxt에게 가며 ctxt는 불필요하게 내부 구조를 노출할 필요가 없어진다. 그렇기에 디미터 법칙도 위반하지 않는다.
자료 전달 객체
공개 변수만 있고 함수가 없는 클래스 (DTO: Data Transfer Object)
데이터를 전달만하며 가공되지 않은 원천정보를 애플리케이션 코드에서 사용할 객체로 변환하는 과정중 가장 처음 사용하는 구조체.
활성 레코드
DTO에서 save나 find와 같은 탐색 함수도 제공한다.
여기서 주의해야하는 점은 활성 레코드에 비즈니스 규칙 메소드를 추가하면 잡종 구조가 되버린다.
5. 결론
•
객체는 동작을 공개하고 자료를 숨긴다.
•
자료 구조는 별다른 동작 없이 자료를 노출한다.