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 함수도 제공한다
  • 불행히도 활성 레코드에 비즈니스 규칙 메서드를 추가해 이런 자료 구조를 객체로 취급하는 개발자가 흔하다
  • 하지만 이렇게 하게 되면 잡종 구조가 나오게 된다
  • 활성 레코드는 자료구조로 취급하고, 비즈니스 규칙을 담으며 내부 자료를 숨기는 객체는 따로 생성해라

결론

결론은 결국 상황에 따라 객체와 자료구조를 잘 선택할 것!!