자료구조와 객체
자료구조(Data Structure)
- 데이터 그 자체를 말함
- 자료를 공개
- 변수 사이에 Getter, Setter로 변수를 다룬다고 자료구조가 객체가 되는게 아니다.
객체(Object)
- 비즈니스 로직과 관련이 있다.
- 자료를 숨기고, 추상화한다. 자료를 다룰 수 있는 함수만 제공한다.
- 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있다.
구체적인 것과 추상적인 것?
추상화라는것은 최소한의 정보를 제공하는 것이다.
우리는 100 x (가솔린 양) / (연료탱크 용량)은 몰라도 된다. 몰라야만 할 수도 있다.
우리가 가솔린이 몇 퍼센트 차있는지, 그 퍼센트가 필요하다.
따라서 두번째, 추상적인 클래스가 더 낫다.
구체적인 Vehicle 클래스
public interface Vehicle {
double getFuelTankCapacityInGallons(); //연료탱크 용량
double getGallonsOfGasoline(); //가솔린 양
}
추상적인 Vehicle 클래스
public interface Vehicle {
double getPercentFuelRemaining(); //백분율로 표현한 남은 가솔린 양
}
자료구조 vs 객체
(1) 절자적인 도형 , (2) 객체지향적인 도형
두 코드를 한번 읽어보자.
(1) 절차적인 도형 : 자료구조
class Square{
public Point topLeft;
public double side;
}
class Circle{
public Point center;
public double radius;
}
public class Geometry {
public final double PI = 3.141592653589793;
public double area(Object shape)throws NoSuchElementException{
if(shape instanceof Square){
Square s = (Square) shape;
return s.side * s.side;
}
else if(shape instanceof Circle){
Circle c = (Circle) shape;
return PI * c.radius * c.radius;
}
else{
throw new NoSuchElementException();
}
}
}
(2) 다형적인 도형 : 객체
public interface Shape {
double area();
}
class Circle implements Shape{
private Point center;
private double radius;
public final double PI = 3.141592;
@Override
public double area() {
return PI * radius;
}
}
class Square implements Shape{
private Point topLeft;
private double side;
@Override
public double area() {
return side * side;
}
}
(1), (2)와 같이 도형 클래스를 절차적으로, 객체지향적으로 만들 수 있다.
(1)과 (2)중에 상황에 맞는 선택을 하면 된다.
(1)과 (2)는 상호 보완적인 특질이 있다.
사실상 정 반대이다.
자료구조를 사용하는 절차적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
절차적인 코드는 새로운 자료구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다.
반면, 객체지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.
디미터 법칙
클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다.
- 클래스 C
- 자신이 생성한 객체
- 자신의 인수로 넘어온 객체
- C 인스턴스 변수에 저장된 객체
기차충돌 - 디미터 법칙 위배
객체의 기차충돌 예제이다. 이는 디미터의 법칙을 위배했다.
아래와 같은 코드를 "기차충돌"이라 부른다.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
자료구조는 아래와 같이 쓸수 있다. 이는 자료구조라서 디미터 법칙을 거론할 필요가 없다.
final String outputDir = ctxt.options.scratchDir.absolutePath;
기차충돌 - 더 나은 방식
(디미터 법칙이 해결된건 아니다.)
위의 기차충돌 방식은 일반적으로 조잡하다고 여겨지는 방식이다.
아래와 같이 나누어 쓰는게 낫다.
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
디미터 법칙 해결 ? -> X
만약 위의 outputDir을 가져오는 코드를 이렇게 바꿔도 이는 디미터 법칙을 해결한것이 아니다.
첫번째 코드는 세가지 함수 호출을 하나의 함수 안에서 호출하는 것이고,
두번째 코드는 세가지 함수 중 두개의 함수를 함수하나에서 호출하는 것일 뿐,
결국 세가지 함수를 다 호출하게 된다.
ctxt.getAbsolutePathOfScratchDirectoryOption();
ctxt.getScratchDirectoryOption().getAbsolutePath();
디미터 법칙 문제 해결
해당 코드는 사실 임시 파일을 생성하기 위해 임시 디렉터리의 절대경로를 얻으려는 코드였다.
ctxt 객체에 임시 파일을 생성하라고 시키자!
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName)
이렇게 하면 ctxt는 내부 구조를 드러내지 않으며 모듈에서 해당 함수가 자신이 몰라야하는 여러 객체를 탐색할 필요가 없다. 따라서 디미터법칙을 위반 하지 않는다.
자료 전달 객체 DTO(Data Transfer Object)
다른 계층 간에 데이터를 교환할 때 사용한다.
로직 없이 필드만 갖고, getter/setter 정도만 갖기도 한다.
일반적으로 클래스 명이 DTO 로 끝나게 만든다.
데이터베이스와 통신하거나 소켓에서 받은 메시지의 구문을 분석할 때 유용하다.
흔히 DTO는 데이터베이스에 저장된 가공되지않은 정보를
애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체이다.
결론
객체는 동작을 공개하고 자료를 숨긴다.
그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면,
기존 객체에 새 동작을 추가하기는 어렵다.
자료구조는 별다른 동작 없이 자료를 노출한다.
그래서 기존 자료구조에 새 동작을 추가하기는 쉬우나,
기존 함수에 새 자료구조를 추가하기는 어렵다.
어떤 시스템을 구현할 때,
- 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하고
- 새로운 동작을 추가하는 유연성이 필요하면 자료구조와 절차적인 코드가 더 적합하다.
좋은 개발자는 편견없이 이 사실을 이해해서 직면한 문제에 최적인 해결책을 선택한다.
'기타 > Book Review' 카테고리의 다른 글
[Effective Java] Item1 : 생성자 vs 정적 팩터리 메서드 (0) | 2024.03.14 |
---|---|
[CleanCode] 클린코드 리뷰_7장 : 오류처리 (0) | 2023.06.24 |
[Clean Code] 클린코드 리뷰_ 5장 : 형식 맞추기 (1) | 2023.06.17 |
[CleanCode] 클린코드 리뷰_ 4장 : 주석 (0) | 2023.06.16 |
[Clean Code] 클린코드 리뷰_ 3장 : 함수 (0) | 2023.06.15 |