1. 자료 추상화
: 자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 것이 좋다.
public class Point {
public double x;
public double y;
}
Java
복사
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
Java
복사
: 왼쪽 코드는 Concreate 클래스로, Point 클래스를 구체적으로 정의하였다. 하지만 오른쪽 코드는 포인트를 인터페이스로 선언함으로써 추상적으로 정의하였다.
: 또한 6-2에서 값을 설정할 때는 set을 이용해 무조건 2개의 인자를 받아야 하기 때문에 set을 통한 값 설정에 실수를 줄일 수 있다.
: 물론 인터페이스를 사용해서 추상적으로 만들었다고 해서 다 되는 것은 아니다. 다음 코드를 보자.
public interface Vehicle() {
double getFuelTankCapacityInGallons();
}
Java
복사
public interface Vehicle() {
double getPercentFuelRemaining();
}
Java
복사
: 자 둘 다, 인터페이스를 사용한 추상적인 클래스다. 그럼 이 둘은 자료를 잘 추상화하고 있는 걸까?
: 왼쪽 코드는 뭔가 Vehicle 내부의 자료인 연료탱크 용량을 갤런화시켜서 반환해주는 것을 알 수 있는데, 이를 통해 우리는 Vehicle이라는 클래스에 연료탱크 용량이라는 변수가 있음을 알아낼 수 있다.
⇒ 아마 저 메소드의 모태는 getFuelTankCapacity() 였으리라
: 반면 오른쪽 코드는 단순히 연료가 어느정도 남아있는지 반환하는 함수로 내부의 동작을 정확히 알 수 없고 단순히 연료가 어느정도 남았는지 퍼센트로만 제공받는다.
: 즉 인터페이스로 정의를 하더라도 메서드를 구체적으로 정의한다면 자료 추상화에 실패한 것이나 마찬가지라는 것이다!
2. 자료/객체 비대칭
•
객체
: 추상화를 통해, 자신의 자료를 숨기고 자료를 사용한 동작을 다루는 함수만 제공한다.
⇒ 캡슐화, 상속, 추상화, 다형성
public interface Shape {
public double area();
}
public class Square implements Shape {
private Point topLeft;
private double side;
@Override
public double area() {
return side*side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
@Override
public double area() {
return height * width;
}
}
public class Circle implements Shape {
private Point center;
private double radius;
public final double PI = 3.141592653589793;
@Override
public double area() {
return PI * radius * radius;
}
}
Java
복사
•
자료구조(절차 지향)
: 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다.
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();
}
}
Java
복사
: 오른쪽의 코드를 토대로 Geometry 클래스에 둘레 길이를 구하는 함수를 추가해도 기존 Square, Rectangle 등의 클래스엔 아무 영향도 미치지 않는다.
: 왼쪽의 코드는 반면 새로운 클래스를 추가하는 경우, 단순히 Shape를 상속받은 새 객체를 만들면 된다. 기존 코드에 변경이 발생하지 않는다.
⇒ 하지만 새 함수를 추가해야 한다면 도형 클래스 전부를 수정해야 한다..!
: 요즘 오른쪽의 코드는 거의 보지 못한 것 같으니 객체지향의 단점을 안고 가더라도 왼쪽 형태로 구현하는 게, 생산성에 더 기여할 수 있는 방향인 것 같다..!
디미터 법칙
: 자료를 숨기고 함수를 공개하도록 하는 법칙
: 잘 알려진 휴리스틱으로 Demeter라는 프로젝트를 진행하던 개발자들이 어떤 객체가 다른 객체의 많은 정보를 알게 되는 경우, 결합도가 높아지고 이는 곧 생산성 저하로 이어지는 설계라는 것을 발견하였다.
: 디미터 법칙은 클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다.
1.
클래스 C나 C를 상속받은 객체들
2.
f 내부에서 생성한 객체, ex) Instance instance = new Instance();
3.
f의 매개변수로 넘어온 객체, ex) method(Instance instance);
4.
클래스 C의 인스턴스 변수에 저장된 객체 public Instance instance;
•
기차 충돌
: 다음과 같은 코드를 기차 충돌이라고 부른다. final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
: 여러 객체가 한 줄로 이어진 기차처럼 보이기 때문, 위 코드는 가독성을 위해서라도 각 단계를 분리하는 편이 좋다.
Option opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
Java
복사
: 하지만 위 함수의 경우, 디미터 법칙을 위반하게 된다. 기차 충돌을 방지하기 위해, 즉 가독성을 높이기 위해 디미터 법칙을 위반하게되는 경우이다.
: 디미터 법칙을 꼭 지킬 필요는 없는 것 같다. 내 신념에 따라 일관성있게 개발하는 것이 더 중요할 것 같다.. 이런 것도 있다만 알아가자
•
잡종 구조
: 중요한 기능을 수행하는 함수도 있고 get/set 함수도 있는 객체와 자료구조를 섞어 놓은 객체를 말한다.
: 이는 객체와 자료구조의 단점만 모아놓은 구조로 피하는 편이 좋다.
4. 자료 전달 객체
: 자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스를 의미하는데, 이런 자료 구조체를 때로는 DTO라고 한다.
: DTO는 데이터베이스와 통신하거나 소켓에서 받은 메시지의 구문을 분석할 때 유용하다.
: DTO는 데이터베이스에 저장된 정보를 애플리켕시ㅕㄴ 코드에서 사용할 객체로 변환하는 형식으로 많이 사용한다.