[CleanCode] -6. 객체와 자료 구조

Young Min Sim ·2021년 4월 14일
0

CleanCode

목록 보기
6/16

자료 추상화

// class
public class Point {
  public double x;
  public double y;
}

vs

// interface
public interface Point {
  double getX();
  double getY();
  void setCartesian(double x, double y);
  double getR();
  ...
}
  • 두 클래스 모두 2차원 점을 표현한다.
    하지만 한 클래스는 구현을 외부로 노출하고 한 인터페이스는 구현을 완전히 숨긴다.

  • 자료를 세세하게 공개하기보다는 인터페이스를 이용하여 추상적인 개념으로 표현하는 편이 좋다.

    캡슐화의 장점

    • 변경에 영향을 덜 받음(결합도가 낮아짐)
    • 불필요한 부분을 외부에 노출하지 않아 오용을 방지할 수 있다

자료/객체 비대칭

  • 객체: 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개
  • 자료구조: 자료를 그대로 공개하며 별다른 함수는 제공하지 않음

두 정의는 본질적으로 상반된다.

  • 절차적인 도형
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;
  }
}

객체와 자료구조는 상호보완적인 특징이 있다. 사실상 반대다.
(자료구조를 사용하는) 절차적인 코드: 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
객체지향 코드: 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.

새로운 함수가 필요한 경우: 절차 지향 방법이 적절
새로운 자료 타입이 필요한 경우: 객체 지향 기법이 적절

새로운 자료 타입이 더 많이 필요할지, 새로운 함수가 더 많이 필요 할지 예측하여 구조를 선택하는게 가능한가? 라는 생각이..
개인적으로는 저런 switch 문은 가독성이 떨어져서 객체지향 쪽 코드를 선호


디미터 법칙

모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다

  • 기차 충돌 코드
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
  • 개선한 코드
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

만약 위 코드에서 ctxt, Options, scratchDir 이 자료구조인 경우 디미터 법칙을 위반하지 않았다고 볼 수 있다.

final String outputDir = ctxt.options.scratchDir.absolutePath;

하지만 자료구조가 아닌 객체라면,
각 객체 의 내부 구조를 안다는 점에서 여전히 디미터 법칙을 위반한다고 볼 수 있다.

여기서 문제는 객체의 내부 구조를 감춰야 하는데,
get 함수를 통해 내부 구조의 정보를 계속해서 얻으려 한다는 점이다.
객체에게는 뭔가를 하라고 말해야지 속을 드러내라고 말하면 안 된다. (<- 가장 중요한 포인트)
(단순히 딱딱하게 외우자면.. 현재 가지고 있는 객체로부터 다른 객체를 받아 사용하면 안된다 정도로 이해해도 될 것 같다)

위 예제는 결국 '임시 파일을 생성하기 위해 절대 경로(getAbsolutePath)'를 얻고자 하는 코드이다.
따라서 ctxt 객체에게 임시 파일을 생성하도록 수정하는 것이 적절하다.

BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);

다른 예제

public class Post {
    private final Comments comments;

    public Post(Comments comments) {
        this.comments = comments;
    }

    public Comments getComments() {
        return comments;
    }
    
    //public void addComment(String content) {
    // 	comments.add(new Comment(content);
    //}
}

public class Board {
    private final List<Post> posts;

    public Board(List<Post> posts) {
        this.posts = posts;
    }

    public void addComment(int postId, String content) {
        posts.get(postId).getComments().add(new Comment(content));
    }
    
    //public void addComment(int postId, String content) {
    //    let post = posts.get(postId) // 자료구조
    //    post.addComment(content)
    //}
    ...
}

주의해야 할 점

  • (외부로부터 내부 구조를 숨겨야 하는 객체와 달리) 자료구조인 경우 . 으로 내부 조회해도 됨
  • 객체의 경우에는 '내부 구조를 외부에 노출하지 않도록 하여 결합도를 낮춘다' 는 것에 포인트가 있음.
    무조건 . 하나만 써라 라는 규칙은 아님
    참고 링크
  • 예를 들어 (Swift 뿐만은 아니지만) Swift 의 메서드 체이닝처럼 자기 자신을 return 하는 경우는 .이 아무리 많아도
    디미터 법칙을 위반한 것은 아니다.

자료 전달 객체

자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다.
이런 자료 구조체를 떄로는 DTO라 한다.

DB 혹은 소켓 통신을 통해 받은 메시지 구문을 분석할 때 유용하고,
흔히 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용되는 구조체다.

swift 에서는 보통 codable 을 채택하는 객체

결론

새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합
새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합

우수한 소프트웨어 개발자는 편견 없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 선택한다.

추가로 참고할만한 링크

0개의 댓글