TIL (2022.02.01)
DAY 11
🔖 오늘 읽은 범위 : 6장, 객체와 자료 구조 (~p.128 결론)
😃 책에서 기억하고 싶은 내용을 써보세요.
- 변수를 비공개(private)로 정의하는 이유가 있다. 남들이 변수에 의존하지 않게 만들고 싶어서다.
- 그렇다면 어째서 수많은 프로그래머가 조회(get) 함수와 설정(set) 함수를 당연하게 공개(public)해 비공개 변수를 외부에 노출할까?
- 자료 추상화
- 구체적인 Point 클래스
public class Point {
public double x;
public double y;
}
- 목록 6-1 은 구현을 노출한다. 변수를 private으로 선언하더라도 각 값마다 조회(get) 함수와 설정(set) 함수를 제공한다면 구현을 외부로 노출하는 셈이다.
- 추상적인 Point 클래스
public interface Point {
double getX() ;
double getY( ) ;
void setCartesian(double x, double y) ;
double getR( ) ;
double getTheta();
void setPolar(double r, double theta) ;
}
- 사실 목록 6-2는 자료구조 이상을 표현한다. 클래스 메서드가 접근 정책을 강제한다.
- 그보다는 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
- 구체적인 Vehicle 클래스
public interface Vehicle {
double getFuelTankCapacityinGallons();
double getGallonsOfGasoline( ) ;
}
- 목록 6-3은 자동차 연료 상태를 구체적인 숫자 값으로 알려준다.
- 추상적인 Vehicle 클래스
public interface Vehicle {
double getPercentfuelRemaining( ) ;
}
- 목록 6-4는 자동차 연료 상태를 백분율이라는 추상적인 개념으로 알려준다. (정보가 어디서 오는지 전혀 들어나지 않는다.)
- 자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다. 인터페이스나 조회 설정 함수만으로는 추상화가 이뤄지지 않는다.
- 자료/객체 비대칭
- 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.
- 절차적인 도형
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클래스에 속한 함수를 모두 고쳐야 한다.
- 다형적인 도형
publie 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 ;
publie 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;
}
}
- 객체 지향적인 도형 클래스다. 여기서 area()는 다형(polymorphic) 메서드다. Geometry 클래스는 필요 없다. 그러므로 새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다.
- 반면 새 함수를 추가하고 싶다면 도형 클래스 전부를 고쳐야 한다.
- 노련한 객체 지향 설계자는 VISITOR 혹은 Dual-Patch 등과 같이 잘 알려진 기법을 사용해 이 문제를 해결한다. 하지만 이들 기법 역시 대가가 따르며, 일반적으로 절차적인 프로그램에서 볼 수 있는 구조를 반환한다.
- 그래서 객체와 자료 구조는 근본적으로 양분된다.
(자료 구조를 사용하는) 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다. 객체 지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.
- 분별 있는 프로그래머는 모든 것이 객체라는 생각이 미신임을 잘 안다. 때로는 단순한 자료 구조와 절차적인 코드가 가장 적합한 상황도 있다.
- 디미터 법칙
- 디미터 법칙은 잘 알려진 휴리스틱(heuristic)으로, 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다.
- 좀 더 정확히 표현하자면, 디미터 법칙은 “클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다”고 주장한다.
- 클래스 C
- f가 생성한 객체
- f 인수로 넘어온 객체
- C 인스턴스 변수에 저장된 객체
- 하지만 위 객체에서 허용된 메서드가 반환하는 객체의 메서드는 호출하면 안 된다. 다시 말해, 낯선 사람은 경계하고 친구랑만 놀라는 의미다.
- 기차 충돌
- 디미티 법칙을 어기는 예시
final String outputDir = ctxt.getOptions( ).getScratchDir( ) .getAbsolutePath( ) ;
- 흔히 위와 같은 코드를 기차 충돌(train wreck)이라 부른다. 여러 객차가 한 줄로 이어진 기차처럼 보이기 때문이다.
- 위 코드는 다음과 같이 나누는 편이 좋다.
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir( ) ;
final String outputDir = scratchDir.getAbsolutePath();
- 잡종 구조
- 이런 혼란으로 말미암아 때때로 절반은 객체, 절반은 자료 구조인 잡종구조가 나온다.
- 덕택에 다른 함수가 절차적인 프로그래밍의 자료 구조 접근 방식처럼 비공개 변수를 사용하고픈 에 빠지기 십상이다. (때로는 기능 욕심(Feature Envy)이라고도 부른다.)
- 이런 잡종 구조는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어렵다. 양쪽 세상에서 단점만 모아놓은 구조다.
- 구조체 감추기
- 자료 전달 객체
- 자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다. 이런 자료 구조체를 때로는 자료 전달 객체(DataTransferObject, DTO)라 한다.
- 흔히 DTO는 데이터베이스에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체다.
- 좀 더 일반적인 형태는 ‘빈(bean)' 구조다. 빈은 비공개(private) 변수를 조회 설정 함수로 조작한다. 일종의 사이비 캡슐화로, 일부 00 순수주의자나 만족시킬 뿐 별다른 이익을 제공하지 않는다.
- 활성 레코드
- 활성 레코드는 DTO의 특수한 형태다. 공개 변수가 있거나 비공개 변수에 조회/설정 함수가 있는 자료 구조지만, 대개 save나 find와 같은 탐색 함수도 제공한다.
- 불행히도 활성 레코드에 비즈니스 규칙 메서드를 추가해 이런 자료구조를 객체로 취급하는 개발자가 흔하다. 하지만 이는 바람직하지 않다. 그러면 자료 구조도 아니고 객체도 아닌 잡종 구조가 나오기 때문이다.
- 해결책은 당연하다. 활성 레코드는 자료 구조로 취급한다. 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다.
- 결론
- 객체는 동작을 공개하고 자료를 숨긴다.
- 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다.
- 자료 구조는 별다른 동작 없이 자료를 노출한다.
- 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다.
- (어떤) 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다. 다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.
🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요
- 이번 챕터는 특히나 어렵게 다가왔다. 그동안 객체와 자료 구조는 연관성 없는 별개의 개념으로 생각해왔기 때문이다. 누구도 나에게 이 둘을 다르게 봐야하고, 구분해서 써야한다는 말을 해준 적이 없다. 그저 어려운 용어들로 세분화해서 설명해줬을 뿐이다. 그렇기에 한글자 한글자가 읽기 어려웠지만, 그만큼 뇌리에 박히는 말들이기도 했다. (앞으로는 절대 잊지 않을 것 같다.) 객체 지향적인 프로그래밍을 작성하기 위해 어렵지만 한번쯤은 꼭 짚고 넘어갈 내용이었던 것 같다.
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- VISITOR 패턴
- VISITOR 패턴은 주로 상속 없이 클래스에 메서드를 효과적으로 추가하기 위해 사용한다.
- 하지만 합성 객체의 내부 구조가 VISITOR에 의해 열리게 되므로 캡슐화를 위반한다는 문제점이 생긴다.
- Dual-Patch 기법
- 디미터 법칙
소감 3줄 요약
- 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.
- 객체와 자료 구조는 근본적으로 양분된다.
- 디미터 법칙 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다. (자료 구조에는 해당되지 않는다.)