## 목차 ##



## 자료 추상화 ##

  • 자료 추상화는 구현을 감춘다.
    • 사용자가 구현을 모른채 자료의 핵심을 조작할수 있어야 진정한 의미의 클래스다.
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); 
}

Point 클래스는 구현을 노출한다. 직교좌표계를 사용한다는 것도 확실하다.

Point 인터페이스는 구현을 감춘다. 직교좌표계인지, 극좌표계인지 알 길이 없다. 그럼에도 불구하고 메소드를 통해 자료의 핵심을 조작할 수 있다.


public interface Vehicle {
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
}
public interface Vehicle {
    double getPercentFuelRemaining();
}

첫 번째 인터페이스는 자동차 연료 상태를 구체적 숫자값으로 알려준다. 두 함수가 변수값을 읽어 반환할 뿐이라는 사실이 거의 확실하다.

두 번째 인터페이스는 정보가 어디서 오는지 전혀 드러나지 않는다. 내부의 계산식이 감추어져있다.

자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다. 단지 인터페이스, getter, setter 같은것을 사용한다고해서 추상화가 이뤄지지 않는다. 개발자가 스스로 자료를 표현할 가장 좋은 방법을 고민해야 한다.



## 자료/객체 비대칭 ##

  • 자료구조를 사용한 절차지향적인 코드와 객체지향적인 코드는 상호 보완적이다.
    • 자료구조에서 유리한 측면이 객체지향에서는 불리해지고, 그 반대도 마찬가지이다.
    • 상황에 따라 맞는 방식의 코드 스타일을 따라야한다.

**절차 지향적인 도형

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(); 
  }
}

절차 지향적인 코딩 스타일로 짠 도형 클래스다. 각 도형 클래스는 아무런 메소드도 포함하지 않는 자료구조다. 동작방식은 Geometry 클래스에서 구현한다.

새로운 함수를 추가하고 싶다면 그냥 추가하면 된다. 그러나 반대로 새로운 자료구조를 추가하려면 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;
  } 
}

이번에는 객체 지향적인 코드로 짠 도형 클래스다. Geometry 클래스는 필요없다.

새 도형 자료구조를 추가한다고 해서 기존 함수에 아무런 영향이 없다. 단지 새로운 구현 클래스만 추가하면 된다. 반면 새로운 함수를 추가하려면 모든 구현 클래스에 함수를 추가해야한다.


새로운 자료구조 추가에서는 객체지향이 유리하고 새로운 함수 추가에서는 절차지향이 유리하다.



## 디미터 법칙 ##

  • 디미터의 법칙은 객체지향 디자인 원칙 중 하나이다.
    • 결합도가 낮은 설계를 위한 형식
    • 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.
    • 결합도가 높으면 모듈의 재사용성이 떨어진다.

아래 코드는 어떤가?

class Boy {
    public String chooseDateCourse(Girl girl) {
        if(girl.stomach.isEmpty()) {
            return "restaurant";
        } else {
            return "theater";
        }
    }
}

위 코드에서는 인수 girl이 stomach라는 인스턴스를 가지고 있으며 반환받은 객체가 isEmpty() 메소드를 가지고 있다는 것을 알아야한다. girl, stomach 두 객체의 내부구조를 모두 알아야한다.

결합도가 높다. 이렇게 되면 결합된 모듈이 수정될 때 마다 해당 클래스도 수정해야 한다. 결과적으로 결합도가 높은 모듈은 재사용성이 떨어진다.

아래와 같이 사용할 수 있도록 수정되어야한다. girl의 isHungry() 구현이 내부적으로는 어떻게 되어있는지 모르도록 추상화되어있다.

class Boy {
    if(girl.isHungry()) {
        return "restaurant";
    } else {
        return "theater";
    }
}

디미터의 법칙은 클래스 C의 함수 f는 아래와 같은 객체의 메서드만 호출해야 한다.

+ 클래스 C(자신)
+ f 블록 내에서 생성한 객체
+ f 인수로 넘어온 객체
+ C 인스턴스 변수의 객체
class A {

    private B b;

    public setA(B b) {
        b = b;
    }
    public myMethod(OtherObject other) {
        // ...
    }

    /* 디미터의 법칙을 잘 따른 예 */
    public okLawOfDemeter(Paramemter param) {
        myMethod();     // 자신의 메소드
        b.method();   // 멤버변수의 메소드
        Local local = new Local();
        local.method();    // 직접 생성한 객체의 메소드 
        param.method();    // 인자의 메소드
    }

    /* 디미터의 법칙을 어긴 예 */
    public violateLawOfDemeter(Paramemter param) {
        C c = param.getC();
        c.method();    // 인자의 메소드가 반환하는 객체의 메소드
        param.getC().method(); // 위와 같음.
    }
}
  • 기차 충돌
    • 여러 객차가 한 줄로 이어진 기차처럼 보인다.
    • 기차 충돌은 조잡하다 여겨지는 방식이므로 피해야한다.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

위와같은 기차충돌 코드는 명백히 디미터 법칙 위반이다. 따라서 아래와같이 나누어주는게 좋다.

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

이제는 디미터 법칙을 위반할까? 그 여부는 위 변수들이 자료구조인지 객체인지에 달렸다.

객체라면 내부구조를 숨겨야하므로 확실히 위반이다. 왜냐하면 기차충돌 형태에서 모습만 변했지 근본적으로는 반환받은 객체들의 내부구조를 알지 못하면 호출할 수 없는 메소드들이기 때문이다.

자료구조라면 애초에 내부 구조를 노출하는 형식의 코딩이므로 디미터 법칙이 적용되지 않는다.

위 코드가 디렉터리의 절대경로를 얻으려는 이유가 그곳에 어떠한 파일을 생성하기 위함이라 가정해보자. 그렇다면 함수 호출이 아래와 같은 모습이 되어야한다.

BufferedOutputStream bos = ctxt.createScratchFileStream(fileName);

위 코드에서는 객체에게 내부구조를 드러내라하지 않고 뭔가를 시킬 뿐이다. 또한 몰라야하는 여러 객체를 탐색할 필요가 없다. 이제는 확실히 디미터 법칙을 위반하지 않는다.

  • 잡종 구조
    • 객체와 자료구조를 섞어놓은 구조
    • 양쪽의 단점만 모아놓은 구조이다.



## 자료 전달 객체 ##

공개 변수만 존재하고 메소드나 함수가 없는 클래스를 자료 전달 객체(DTO - Data Transfer Object)라고 한다. 데이터베이스나 소켓과의 통신에서 가공되지 않는 정보를 넘겨받는 대표적인 형태다.

  • 자료 전달 객체에 getter와 setter 혹은 다른 비지니스 함수를 추가했다고해서 객체처럼 취급하면 안된다.
    • 기능을 담는 클래스를 따로 만들고 자료전달객체를 멤버변수로 사용해 내부구조를 숨기고 추상화된 함수를 제공해야한다.



## 결론 ##

  • 객체는 함수를 공개하고 자료를 숨긴다.

    • 기존 함수의 변경 없이 새로운 자료/클래스를 추가하기 쉽다.
    • 새로운 함수를 추가하기는 어렵다.
  • 자료구조는 별다른 함수 없이 자료를 노출한다.

    • 새로운 함수를 추가하기는 쉽다.
    • 기존 함수에 새로운 자료구조를 추가하기 어렵다.
  • 시스템을 구현할 때 새로운 자료타입을 추가하는 유연성이 필요한지 새로운 함수를 추가하는 유연성이 필요한지에 따라 적합한 방식의 스타일을 선택해야한다.

+ Recent posts