- 새로운 자료 타입 추가에 대한 유연성이 필요할 때는 객체, 새로운 동작에 대한 유연성이 필요하면 자료 구조와 절차적인 코드를 사용하는것이 좋다
- 상황에 맞는 방법을 선택하라
1. 자료 추상화
- 구현을 감추려면 추상화가 필요하다
- 추상 인터페이스를 제공해 사용자가 구현을 몰느채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다
- 자료는 추상적인 개념으로 표현하는 것이 좋다
public class Point {
public double x;
public double y;
}
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
자료와 객체의 차이
- 객체 : 추상화 뒤에 자료를 숨긴 채 자료를 다루는 함수만 공개한다
- 자료 구조 : 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다
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(Object 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();
}
}
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 Circle implements Shape {
private Point center;
private double radius;
public final double PI = 3.141592653589793;
public double area() {
return PI * radius * radius;
}
}
- 절차지향적인 코드에서는 if문을 통해 절차적으로 처리한다
- 새로운 참수를 추가하고 싶다면 geometry 내부에 하나의 함수를 새로 만들기만 하면 된다
- 기존의 자료 구조에 영향을 끼치지 않는다
- 새로운 도형인 오각형을 넣는다고 하면 geometry에 속한 모든 함수를 변경해줘야 한다
- 객체를 사용하여 만들었을 경우 변수를 감추고 함수로만 외부에서 사용하도록 열어준다
- 새로운 함수를 추가하고 싶다면 모든 클래스에 해당 함수를 구현해줘야 한다
- 새로운 도형인 오각형을 추가하고 싶다면 새로운 클래스를 만들어 그에 맞는 메서드만 구현해주면 된다
3. 디미터 법칙
- 객체는 조회 함수로 내부 구조를 공개하면 안된다
- 클래스 C의 메서드 f는 이하의 메서드만 호출해야 한다
- Class C
- f가 생성한 객체
- f 인수로 넘어온 객체
- C instance 변수에 저장된 객체
기차 충돌
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
- 여러 객체가 한 줄로 이어진 기차처럼 보인다고 해서 이런 코드를 기차 충돌이라고 부른다
- 위 코드는 피하고 아래와 같이 나누는 것이 좋다
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
- 이렇게 작성하는 경우 ctxt, optx, scratchDir이 객체인지 자료구조인지를 먼저 명확하게 해야한다
- 객체인 경우 내부 구조가 노출되었기 때문에 디미터 법칙을 위반하게 된다
- 자료 구조인 경우는 내부 구조를 노출하기 때문에 디미터 법칙이 적용되지 않는다
final String outputDir = ctxt.options.scratchDir.absolutePath;
- 자료 구조인 경우에는 의미없는 getter, setter를 사용하는 것보다 .을 이용해 잇는것이 좋다
잡종 구조
- 객체와 자료구조가 섞인 잡종 구조는 피하는 것이 좋다
4. 해결책
구조체 감추기
- ctxt, options, scratchDir이 객체일 때
String outFile = outputDir + "/" + className. replace( '.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
- 절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위함임을 알았다
- 임시 파일을 생성하는 것을 해당 객체가 아닌 ctxt 객체에 위임을 하는 경우 ctxt는 불필요하게 내부 구조를 노출할 필요가 없어진다
자료 전달 객체
- 공개 변수만 있고 함수가 없는 클래스(DTO : Data Transfer object)
- 데이터를 전달만하며 가공되지 않은 원천정보를 애플리케이션 코드에서 사용할 객체로 변환하는 과정 중 가장 처음 사용하는 구조체
활성 레코드
- DTO의 특수한 형태
- DTO에서 save나 find같은 탐색 함수도 제공한다
- 활성 레코드에 비즈니스 규칙 메소드를 추가하면 잡종 구조가 된다
- 이를 해결하기 위해 활성 레코드는 자료 구조로 취급한다
개인적인 감상
- DTO, DAO, VO의 구분이 어려웠기에 이번 기회를 통해 정리를 해보았다
- 객체인지 자료 구조인지 구별하지 않고 사용해왔고 개념에 대한 정의 또한 없었기 때문에 객체와 자료구조에 대해 생각해 볼 수 있었다
추가적인 정리
- private를 사용해서 외부에 노출시키고 싶지 않은 데이터를 보호하는데 private로 접근자를 설정한 변수가 외부로 노출되지 않는가?
- 프로그래밍적으로는 노출되지 않지만 getter와 setter를 통해서 조작이 가능하다
- 그렇기에 추상화와 메서드명을 통해 대략의 목적은 유추할 수 있으나 세부적인 내용은 유추하지 못하도록 작성한다
- 자료구조는 객체가 데이터와 메서드를 분리시켜 객체의 역할을 축소시킨 모습니다
- 자료구조를 사용하면 모듈에 동작을 정의하게 되면 이런 형태의 코딩 방식을 절차지향적 프로그래밍이라고 한다
- 자료구조는 객체이지만 동작을 가지지 않기 때문에 외부의 동작에 사용되는 절차지향적 프로그래밍을 따르게 된다
- 모든 것이 객체라는 생각은 미신이다
DAO, DTO, VO
- DAO
- Data Access Object
- DB의 데이터에 접근하기 위한 객체
- 직접 DB에 접근하여 data를 삽입, 삭제 , 조회 등 조작할 수 있는 기능을 수행한다
- DTO
- Data Transfer Object
- 계층 간 데이터 교환을 위한 Java Bean을 의미
- 로직을 가지지 않는 데이터 객체
- getter, setter 메소드만 가진 클래스를 의미한다
- VO
- Value Object
- Read-only 속성을 가진 값 오브젝트
- 값 타입을 표현하기 위해 불변 클래스를 만들어 사용한다
- 따라서 getter 기능만 존재한다