변수를 비공개(private)로 정의하는 이유가 있다.
남들이 변수에 의존하지 않게 만들고 싶어서다.
그렇다면 어째서 조회(get) 함수와 설정(set) 함수를 당연하게 공개(public)해 비공개 변수를 외부에 노출할까?
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 interface Vehicle {
double getFuelTankCapacityInGallons();
double getGallonsOfGasoline();
}
public interface Vehicle {
double getPercentFuelRemaining();
}
자료를 세세하게 공개하기 보다는 추상적인 개념으로 표현하는 편이 좋다.
인터페이스나 조회/설정 함수만으로는 추상화가 이뤄지지 않는다.
아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다.
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 클래스에 둘레 길이를 구하는
perimeter()
함수를 추가하고 싶다면?
도형 클래스는 아무 영향도 받지 않는다. (도형 클래스에 의존하는 다른 클래스도 마찬가지)
반대로 새 도형을 추가하고 싶다면?
Geometry 클래스에 속한 함수를 모두 고쳐야 한다.
area()는 다형 메서드이다.
Geometry 클래스는 필요 없다.
그러므로 새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다.
반면, 새 함수를 추가하고 싶다면 도형 클래스 전부를 고쳐야 한다.
public class Square implements Shape {
private Point topLeft;
private double side;
double area() {
return side*side;
}
}
public class Rectangle implements Shape {
private Point topLeft;
private double height;
private double width;
double area() {
return height * width;
}
}
public class Circle implements Shape {
private Point center;
private double radius;
public final double PI = 3.141592653589793;
double area() {
return PI * radius * radius;
}
}
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
기차 충돌(train wreck)
이라 부른다.Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final STring outputDir = scratchDir.getAbsolutePath();
final String outputDir = ctxt.options.scratchDir.absolutePath;
ctxt.getAbsolutePathOfScratchDirectoryOption();
ctx.getScratchDirectoryOption().getAbsolutePath();
String outFile = outputDir + "/" + className.replace('.', '/') + ".class";
FileOutputStream fout = new FileOutputStream(outFile);
BufferedOutputStream bos = new BufferedOutputStream(fout);
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
객체는 동작을 공개하고 자료를 숨긴다.
그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다.
자료 구조는 별 다른 동작 없이 자료를 노출한다.
그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다.
(어떤) 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다.
다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.