CS/클린코드
[클린코드] chapter-6 객체와 자료구조
yhsim98
2022. 8. 28. 03:24
자료 추상화
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);
}
- 밑의 클래스는 구현을 완전히 숨기지만 접근 정책을 강제한다
- 좌표를 읽을 때는 각 값을 개별적으로 읽어야 하고
- 좌표를 설정할 때는 두 값을 한꺼번에 설정해야 한다
- 위의 클래스는 변수를 private 으로 설정하더라도 각 값마다 get이나 set을 제공한다면 구현을 외부로 노출한다
- 함수만 넣는 것이 아닌 추상화가 필요하다
- 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스
자료/객체 비대칭
- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다
- 자료구조는 자료를 그래로 공개하며 별다른 함수는 제공하지 않는다
// 절차적인 도형
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.1415;
public double area(Object shape) throws NoSuchShapeException {
if (shape instanceOf square) {
Square s = (Sqaure)shape;
return s.side * s.side;
}
else if (shape instanceOf Rectangle) {
Rectangle r = (Ractangle) shape;
return r.height * r.width;
}
else if (shape instanceOf Circle) {
Circle c = (Circle)shape;
return PI * c.radius * c.radious;
}
throw new NoSuchShapeException();
}
}
- 위 클래스들은 절차지향적인 클래스이다
- 둘레의 길이를 구하거나 하는 등 행위를 추가시켜도 도형 클래스에는 아무런 영향을 받지 않는다
- 하지만 새로운 도형이 추가되면 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 Circle implements Shape {
private Point center;
private double radius;
public final double PI = 3.141592653589793;
public double area() {
return PI * radius * radius;
}
}
- 위 클래스는 객체지향적인 클래스이다
- 도형을 추가해도 아무런 영향이 없지만
- 행위를 추가하면 모든 도형에 대해 해당 행위를 구현해야 한다
- 두 방식은 상호 보완적인 특질이 있다
- 그래서 객체와 자료구조는 근본적으로 양분된다
- (자료구조를 사용하는) 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다
- 반대쪽도 참이다
- 절차적인 코드는 새로운 자료구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다. 객체 지향 코드는 새로운 함수를 추가히기 어렵다. 그러려면 모든 클래스를 고쳐야 한다
- 때로는 객체지향이 아닌 단순한 자료구조와 절차적인 코드가 가장 적합한 상황도 있다
디미터 법칙
- 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙
- 객체는 조회 함수로 내부 구조를 공개하면 안 된다는 의미
기차 충돌
// 기차충돌 코드
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
// 분리 코드
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
- 위와 같은 코드를 기차 충돌이라 부르는데 일반적으로 조잡하다 여겨지는 방식이므로 아래와 같이 나누는 편이 좋다
- 위의 코드의 Options, ScratchDir 등이 객체라면 디미터 법칙을 위반한 것이다
- 만약 자료구조라면 당연히 내부 구조를 노출하므로 디미터 법칙이 적용되지 않는다
잡종 구조
- 때때로 절반은 객체, 절반은 자료 구조인 잡종 구조가 나온다
- 잡종 구조는 중요한 기능을 수행하는 함수도 있고, 공개 변수나 공개 조회/설정 함수도 있다
- 공개 조회/설정 함수는 비공개 변수를 그대로 노출하기 때문에
- 다른 함수가 절차적인 프로그래밍의 자료 구조 접근 방식처럼 비공개 변수를 사용하고픈 유혹에 빠지기 십상이다
- 이런 구조는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어렵다. 양쪽 세상에서 단점만 모아놓은 구조다.
- 그러므로 되도록 이런 구조는 피하도록 하자. 프로그래머가 함수나 타입을 보호할지 공개할지 확신하지 못해 어중간하게 내놓은 설계에 불과하다.
구조체 감추기
- 위의 options 등이 정말 객체라면 내부 구조를 감춰야 한다
String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);
- 위 코들르 보면 outputDir 경로를 얻으려는 이유가 임시파일을 생성하기 위한 목적임을 알 수 있다
- 그렇다면 ctxt 객체에 임시 파일을 생성하라고 시키면 어떨까?
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
- 객체에게 맡기기에 적당한 임무로 보인다!
- ctxt는 내부 구조를 드러내지 않으며, 모듈은 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없다. 따라서 디미터 법칙을 위반하지 않는다
자료 전달 객체
- 공개 변수만 있고 함수가 없는 클래스
- DTO라고 한다
public class Address {
public String street;
public String streetExtra;
public String city;
public String state;
public String zip;
public Address(args){
this.attribute = args;
....
}
public String getter(){
return this.attribute;
}
}
활성 레코드
- DTO의 특수한 형태
- getter, setter 뿐 아니라 다른 find이나 save 함수도 제공한다
- 불행히도 활성 레코드에 비즈니스 규칙 메서드를 추가해 이런 자료 구조를 객체로 취급하는 개발자가 흔하다
- 하지만 이렇게 하게 되면 잡종 구조가 나오게 된다
- 활성 레코드는 자료구조로 취급하고, 비즈니스 규칙을 담으며 내부 자료를 숨기는 객체는 따로 생성해라
결론
결론은 결국 상황에 따라 객체와 자료구조를 잘 선택할 것!!