// 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)
//}
...
}
자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다.
이런 자료 구조체를 떄로는 DTO라 한다.
DB 혹은 소켓 통신을 통해 받은 메시지 구문을 분석할 때 유용하고,
흔히 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용되는 구조체다.
swift 에서는 보통 codable 을 채택하는 객체
새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합
새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합
우수한 소프트웨어 개발자는 편견 없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 선택한다.