새로운 자료 타입을 추가하는 유연성
이 필요하면 객체가 더 적합새로운 동작을 추가하는 유연성
이 필요하면 자료 구조와 절차적인 코드가 더 적합의무적으로 getter, setter를 생성하던 습관이 있었기 때문에 java class, interface 등으로 객체를 구현할 때 추상적이고 유연하게 생성할 수 있는 방법에 대해 더 고민이 필요하다는 생각이 들었습니다.
// 추상적인 Point
public interface Point {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double r, double theta);
}
Point interface는 자료 구조 이상을 표현한다.
변수를 private으로 선언하더라도 각 값마다 getter, setter를 제공한다면 구현을 외부로 노출하는 셈.
구현을 감추려면 추상화가 필요! (getter, setter로 변수를 다룬다고 클래스가 되지 않는다.)
자료를 세세하게 공개하기 보단 추상적인 개념으로 표현하는 편이 좋다.
// 자동차 연료 상태를 백분율이라는 추상적인 개념으로 알려줌
// 정보가 어디서 오는지 전혀 드러나지 않음
public interface Vehicle {
double getPercentFuelRemaining();
}
private로 선언한 field는 선언한 class에서만 접근할 수 있고
private + final 로 선언한 field는 한 번 생성된 뒤에는 변경되지 않음이 보장됩니다. (final 선언된 field는 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();
}
}
다형적인 도형
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 radius * radius * PI;
}
}
(자료 구조를 사용하는) 절차적인 코드는 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.
반면, 객체 지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다
- 절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. (모든 함수를 고쳐야 하기 때문에)
- 자료 구조에 대한 처리를 parameter로 받아올 수 있다면?
- 객체 지향 코드는 새로운 함수를 추가하기 어렵다 (모든 클래스를 고쳐야 하기 때문)
- interface의 default를 활용한다면?
- 반드시 객체로 구현할 수 있는 것은 아니기에 정답은 없는 것 같지만 개인적으로는 좀 더 고치기 편한 형태로 구현하는 것이 낫다고 생각합니다.
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
이어진 기차처럼 보이는 조잡한 방식으로 피하는 편이 좋다.
다음과 같이 나누는 편이 좋다.
Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();
위의 예제가 디미터 법칙을 위반하는지 여부는 ctxt, Options, ScratchDir이 객체인지 아니면 자료 구조인지에 달렸다.
자료 구조는 무조건 함수 없이 공개 변수만 포함하고 객체는 비공개 변수와 공개 함수를 포함한다는 이분법적 접근은 불가능
// ctxt 객체에 공개해야 하는 메서드가 너무 많아진다.
ctxt.getAbsolutePathOfScratchDirectoryOption();
// getScratchDirectoryOption()이 객체가 아닌 자료 구조를 반환한다고 가정한다.
ctx.getScratchDirectoryOption().getAbsolutePath();
field 값을 직접 조회하거나 조작하도로 하면 안된다는 것으로 이해했습니다.
임시 디렉터리의 절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위한 목적이라면
ctxt 객체에 임시 파일을 생성하라 시키면 되지 않을까?
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
ctxt는 내부 구조를 드러내지 않고, 모듈에서 해당 함수는 자신이 몰라야 하는 여러 객체를 탐색할 필요가 없다.
따라서 디미터 법칙을 위반하지 않는다.
// bean 구조의 예 - address.java
public class Address {
private String street;
private String streetExtra;
private String city;
private String state;
private String zip;
public Address(String street, String streetExtra,
String city, String state, String zip) {
this.street = street;
this.streetExtra = streetExtra;
this.city = city;
this.state = state;
this.zip = zip;
}
public String getStreet() {
return street;
}
public String getStreetExtra() {
return streetExtra;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZip() {
return zip;
}
}
- 특히 setter는 field의 변경이 언제든 가능하게 만들기 때문에 로직 구현에 혼란이 된다고 생각합니다.
- 다른 개발자분이 작성했던 방식을 차용하여 일단
private final
로 field를 구성하고 변경이나 설정이 필요한 경우에만 해당 field의 final를 제거하고 값 세팅 setter를 설정하는 방식을 익히고 있습니다.- field의 값 조회가 필요하지 않다면 getter도 제외해도 괜찮을 것입니다. (필요 값만 조회하는 getter만 생성)
- class에 대한 확인이 용이하도록 hashCode, equals, toString은 기본적으로 구현되어 있으면 유용하다고 생각합니다.
활성 레코드에 대하여 정확히 이해하지는 못했지만 필요한 데이터 형태를 제공하는 method 외에 값을 변경하거나, 계산하거나, 부차적인 처리를 제공하는 함수는 해당 자료구조를 param으로 받아와 책임이 부여된 객체에서 수행하도록 구분을 명확히 해봐야겠습니다.