본문 바로가기
Programming/Clean Code

6장. 객체와 자료 구조

by JKROH 2023. 11. 30.
반응형

자료 추상화

  • 아래 두 클래스를 살펴보자.
public class Rectangle {
    public double width;
    public double height;
}

/////////////////////////

public interface Rectangle {
    double getWidth();
    double getHeight();
    void setRectangle(double width, double height);
    double getLength();
    void setSquare(double length);
}
  • 위 클래스는 가로, 세로 값을 별개로 설정해야하고 별개로 읽어야한다.
  • 아래 클래스는 가로, 세로 값을 동시에 설정해야하고, 별개로 읽어야한다.
  • 구체 클래스인 Rectangle은 내부를 완전히 노출하고, 추상 인터페이스인 Rectangle은 내부를 완전히 숨긴다.
    • 구체 클래스를 사용하면, 해당 클래스 인스턴스는 반드시 직사각형이다.
    • 그러나 추상 클래스를 사용하면, 해당 인터페이스를 구현한 클래스가 직사각형인지, 정사각형인지는 모른다.
  • 구체 클래스의 변수를 private으로 설정하더라도, 접근자와 수정자를 제공하면 구현을 외부로 노출하는 셈이다.
  • 즉, 접근자와 수정자를 사용하고 변수를 private으로 설정한다고 구현을 감춘 클래스가 되는 것은 아니다.
  • 대신 추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스가 된다.
public interface Student {
    int getMathScore();
    int getEnglishScore();
    int getKoreanScore();
}

/////////////////////////

public interface Student {
    double getScoreMean();
}
  • 위 인터페이스는 모든 과목의 성적을 전부 읽어올 수 있고, 아래 인터페이스는 성적 평균을 추상적인 개념으로 알려준다.
  • 위의 인터페이스처럼 모든 자료를 단순히 반환하는 형태보다, 추상적인 개념으로 표현하는 것이 더 낫다.
  • 프로그래머는 객체가 포함하는 자료를 어떻게 표현할 것인가에 대한 고민을 통해 좋은 자료 표현법을 고안해야한다.

자료 / 객체 비대칭

  • 위에서 보여준 차이는 자료 구조와 객체의 차이를 보여준다.
  • 자료 구조는 자료를 그대로 공개하며, 별다른 함수를 제공하지 않는다.
  • 객체는 자료를 추상화 뒤에 숨기고, 자료를 다루는 함수만 공개한다.
class Triangle {
    public double width;
    public double height;
}

class Rectangle {
    public double width;
    public double height;
}
class Circle {
    public double radius;
}

public class Geometry {
    
    private final double PI = 3.141592;
    
    public double area(Object shape) throws IllegalAccessException{
        if(shape instanceof Triangle){
            Triangle triangle = (Triangle) shape;
            return triangle.width * triangle.height / 2;
        }
        if(shape instanceof Rectangle){
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.width * rectangle.height;
        }
        if(shape instanceof Circle){
            Circle circle = (Circle) shape;
            return circle.radius * circle.radius * PI;
        }
        throw new IllegalAccessException("없는 도형이에요.");
    }
}
  • 위에서 각 도형 클래스들은 절차적인 클래스다. 이들은 간단한 자료 구조로 어떤 메서드도 제공하지 않는다.
  • 만일 해당 도형들을 사용하는 Geometry클래스에 둘레를 구하는 함수를 추가한다한들, 도형 클래스에는 어떤 변화도 가해지지 않는다.
  • 그러나 도형을 새롭게 추가한다면 어떨까, Geometry 클래스에 있는 모든 함수에 if(shape instanceOf newShape) 의 조건절과 그에 따른 결과 값이 추가적으로 작성되어야 한다.
 class Triangle {
    private double width;
    private double height;

    public double area() {
        return width * height / 2;
    }
}

class Rectangle {
    private double width;
    private double height;

    public double area() {
        return width * height;
    }
}

class Circle {
    private final double PI = 3.141592;

    private double radius;

    public double area() {
        return radius * radius * PI;
    }
}
  • 위에서는 각 도형 클래스들이 객체지향적인 클래스다. 모든 도형은 객체로, 자신의 데이터는 숨기고 대신 추상화된 개념인 넓이를 통해 데이터를 표현한다.
  • 각 도형들이 자신의 넓이를 알아서 계산한다. 즉, Geometry클래스 자체가 필요없다. 이는 다시 말해, 도형을 아무리 추가해도 기존 함수에는 영향을 미치지 않는다는 뜻이다.
  • 그러나 새 함수를 추가하고 싶다면 모든 도형 클래스에 해당 함수를 추가해주어야 할 것이다.
  • 이를 통해 우리는 객체와 자료 구조가 서로 완전 반대의 개념이며, 상호 보완적임을 알 수 있다.
    • 자료 구조를 사용하는 절차적인 코드기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면, 객체 지향 코드기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
    • 절자적인 코드는 새로운 자료 구조를 추가하기 어렵다. 반면, 객체 지향 코드는 새로운 함수를 추가하기 어렵다.
  • 이렇듯 객체의 장점은 자료 구조의 단점이며, 반면에 자료 구조의 장점은 객체의 단점이다. 우리는 이 둘 간의 트레이드 오프를 적절히 고려하여 설계를 진행해야한다.

디미터 법칙

  • 디미터 법칙모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다.
  • 다시 말해, 객체는 조회 함수로 내부 구조를 공개하면 안된다.
  • 이를 좀 더 정확하게 표현하면 디미터 법칙은 "클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다"고 주장한다.
    • 클래스 C
    • f가 생성한 객체
    • f의 인수로 넘어온 객체
    • C 인스턴스 변수에 저장된 객체
  • 여기에 위 객체에서 허용한 메서드가 반환하는 객체의 메서드는 포함되어있지 않다. 다시 말해, 메서드 체인의 형태로 메서드를 구축하면 안된다는 말이다.
    • String streetNumber = person.getAdderess().getStreetNumber() -> Address 객체의 메서드를 다시 호출한다!

- 기차 충돌

  • 흔히 위와 같은 코드를 기차 충돌이라 부른다. 일반적으로 좋게 여겨지지 않는다.
    • 가급적 코드를 분리해보자. Address address = person.getAddress(); String streetNumber = ... 과 같이 말이다.
  • 그래서 위 코드는 디미터 법칙을 어겼는가? 사실 위 코드는 디미터 법칙을 어기지 않았을 수도 있다.
  • 디미터 법칙은 '객체'에만 해당하는 이야기이다. '자료 구조'에는 해당하지 않는다. 만일 person, address가 모두 객체라면 확실히 디미터 법칙을 어긴 것이다. 그러나 person, address가 자료 구조라면 디미터 법칙을 어기지 않는다.
  • 이렇게 혼동이 발생하는 이유는 접근자를 사용했기 때문이다. 만일 다음과 같이 구현했으면 디미터 법칙을 거론할 필요가 없다.
    • String streetNumber = person.address.streetNumber;

- 잡종 구조

  • 때로는 절반은 자료 구조고 절반은 객체인 잡종 구조가 나온다.
  • 잡종 구조에는 중요한 기능을 수행하는 함수도 존재하고, 공개 변수나 공개 접근자 / 수정자도 존재한다.
  • 잡종 구조는 새로운 함수는 물론이고 새로운 자료 구조도 추가하기 어렵다.
  • 따라서 가급적 잡종 구조는 피하자.

- 구조체 감추기

  • 만약 person, address가 모두 객체라면 어떻게 해야할까? 일단 위 구조처럼은 사용하지 않도록 해야겠다.
  • 객체는 캡슐화를 유지해야 한다. 캡슐화를 유지하기 위해서는 객체에게 달라고 하는 것이 아니라, 해달라고 해야한다.
  • 만일 person이 객체라면, streetNumber를 달라고 하면 안된다. person이 가진 streetNumber를 가지고 무언가를 하라고 명령해야 한다.
  • 예를 들어, 해당 코드의 마지막에 streetNumber를 사용해 주문을 하는 코드가 있다고 생각해보자.
    • Order order = makeOrder(streetNumber, product);
  • 위 코드를 다음과 같이 변경할 수 있다.
    • Order order = person.order(product);
  • Person객체가 뭔가를 주문할 수 있는 애플리케이션 구조라면 저런 형태로 명령을 내릴 수 있을 것이다.

자료 전달 객체

  • 자료 구조체의 전형적인 형태는 공개 변수만 존재하고 함수가 없는 클래스다. 이런 자료 구조체를 때로는 DTO라고 부른다.

- 활성 레코드

  • 활성 레코드는 DTO의 특수한 형태다. 공개 변수가 있거나 비공개 변수에 접근자 / 수정자가 있는 자료 구조지만, 대개 save() 나 find()같은 탐색 함수도 제공한다.
  • 활성 레코드는 데이터베이스 테이블이나 다른 소스에서 자료를 직접 반환한다.
  • 활성 레코드는 자료 구조다. 이것 저것 기능을 추가하지 말자.
  • 사실 활성 레코드 자체를 쓰지 않는 것이 더 낫다.

결론

  • 객체와 자료 구조는 각각의 장단을 서로 상호 보완하는 존재다.
  • 따라서 무조건적인 객체 사용, 무조건적인 자료 구조 사용이 아닌 서로 간의 장단을 잘 고려해 사용하도록 하자.
반응형

'Programming > Clean Code' 카테고리의 다른 글

5장. 형식 맞추기  (0) 2023.11.20
4장. 주석  (0) 2023.11.10
3장. 함수  (0) 2023.10.17
2장. 의미 있는 이름  (1) 2023.10.07
1장. 깨끗한 코드  (0) 2023.09.21

댓글