책 리뷰/클린코드

6장 객체와 자료 구조

Lahezy 2023. 7. 25.
728x90

6장 객체와 자료 구조

변수를 private으로 선언하는데 왜 많은 개발자들은 getter와 setter 함수를 public으로 하는 걸까

만약 get, set함수를 제공한다면 구현을 외부로 노출하는 것과 같다.

자료 추상화

변수 사이에 함수라는 계층을 넣는다고 구현이 저절로 감춰지지는 않는다 구현을 감추려면 추상화가 필요하다.

개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 신중하게 고민해야 한다. 아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다

자료/객체 비대칭

객체는 추상화 뒤로 자료를 숨김 채 자료를 다루는 함수만 공개한다.

자료구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.

두 개념은 사실상 정반대다. 사소한 차이로 보일지 모르지만 그 차이가 미치는 영향은 굉장하다.

절차적인 코드는 기존 자료구조를 변경하지 않으면서 새 함수를 추가하기 쉽다. 반면 객체 지향 코드는 기존 함수를 변경하지 않으면 서 새 클래스를 추가하기 쉽다

절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다. 객체 지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야한다.

 

복잡한 시스템을 자다 보면 새로운 함수가 아니라 새로운 자료 타입이 필요한 경우가 생긴다. 이때는 클랙스와 객체지향 기법이 가장 적합하다. 반면 새로운 자료 타입이 아니라 새로운 함수가 필요한 경우 절차적인 코드와 자료구조가 더 적합하다.

 

책에서의 예시 코드는 다음과 같다. shape을 상속받는 도형이 있을 때 만약 새 함수를 추가하고 싶다면 shape을 모두 고쳐야 한다. 하지만 만약 도형들을 각각의 자료구조로 선언해 두었다면 함수의 추가는 쉽지만 새 도형을 추가하는 경우 함수를 모두 고쳐야 한다는 것이다.

 

객체 지향적 코드

// Shape 클래스 (부모 클래스)
abstract class Shape {
    abstract double area();
}

// Rectangle 클래스 (Shape 클래스를 상속)
class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    double area() {
        return width * height;
    }
}

// Triangle 클래스 (Shape 클래스를 상속)
class Triangle extends Shape {
    private double base;
    private double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    double area() {
        return 0.5 * base * height;
    }
}

// Circle 클래스 (Shape 클래스를 상속)
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle(5, 4);
        System.out.println("Rectangle Area: " + rectangle.area());

        Triangle triangle = new Triangle(6, 3);
        System.out.println("Triangle Area: " + triangle.area());

        Circle circle = new Circle(2);
        System.out.println("Circle Area: " + circle.area());
    }
}

자료구조적 코드

// Rectangle 클래스
class Rectangle {
    public double width;
    public double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
}

// Triangle 클래스
class Triangle {
    public double base;
    public double height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
}

// Circle 클래스
class Circle {
    public double radius;

    public Circle(double radius) {
        this.radius = radius;
    }
}

// Geometry 클래스
class Geometry {
    public static String getShapeType(Object shape) {
        if (shape instanceof Rectangle) {
            return "Rectangle";
        } else if (shape instanceof Triangle) {
            return "Triangle";
        } else if (shape instanceof Circle) {
            return "Circle";
        } else {
            return "Unknown Shape";
        }
    }

    public static double getArea(Object shape) {
        if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.width * rectangle.height;
        } else if (shape instanceof Triangle) {
            Triangle triangle = (Triangle) shape;
            return 0.5 * triangle.base * triangle.height;
        } else if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.radius * circle.radius;
        } else {
            return 0.0;
        }
    }
}

// 사용 예시
public class Main {
    public static void main(String[] args) 
        Rectangle rectangle = new Rectangle(5, 4);        
        Triangle triangle = new Triangle(6, 3);
        Circle circle = new Circle(2);

        System.out.println(Geometry.getShapeType(rectangle) + " Area: " + Geometry.getArea(rectangle));
        System.out.println(Geometry.getShapeType(triangle) + " Area: " + Geometry.getArea(triangle));
        System.out.println(Geometry.getShapeType(circle) + " Area: " + Geometry.getArea(circle));
    }
}

즉 객체 지향적 방법은 코드의 재사용성과 가독성이 높으며 도형을 추가하기가 쉽지만 메서드를 추가하기는 어렵다. 절차 지향적 방법은 메서드의 추가가 쉽지만 도형을 추가하기가 어렵다는 특징이 있다.

 

디미터 법칙

휴리스틱으로 모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙이다. 객체는 자료를 숨기고 함수를 공개한다. 즉 객체는 조회 함수로 내부 공개를 하면 안 된다는 의미다.

final String oupturDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

는 디미터 법칙을 위배한 코드이다. 이런 코드를 기차 충돌이라고 부른다. 여러 객차가 한 줄로 이어진 기차처럼 보여서 일반적으로 조잡하다고 여겨진다. 따라서 이런 코드는 피하는 것이 좋다.

위 코드가 디미터 법칙을 위반하는지의 여부는 호출하는 메서드의 값이 객체인지, 자료구조인지에 달렸다.

만약 객체라면 내부를 숨겨야 해서 디미터 법칙을 위배 하지만 자료구조라면 내부 구조 노출을 하므로 디미터 법칙이 적용되지 않는다.

자료 구조는 무조건 함수 없이 공개변수만 포함하고 객체는 비공개 변수와 공개 함수를 표현한다면 간단할 것이다.

하지만 단순한 자료구조에도 조회함수와 설정함수를 정의하라 요구하는 프레임워크와 표준이 있다.

 

잡종 구조

잡종구조는 중요한 기능을 수행하는 함수도 있고 공개변수나 공개 조회/설정 함수도 있다. → 잡종구조는 피하는 것이 좋다.

구조체 감추기

만약 우의 ctx, oprions 가 객체라면 위의 코드처럼 메서드를 연결해서는 안 된다. 내부 구조를 감춰야 하기 때문이다.

 

자료 전달 객체

자료 구조체의 전형적인 형태는 공개 변수만 있고 함수가 없는 클래스다.

이런 자료 구조체를 때로는 자료 전달 객체 (DTO)라 한다.

데이터 베이스의 통신, 소켓 메시지 구문 분석등에 유용하다.

 

활성 레코드

DTO의 특수한 형태이다.

활성 객체는 자료구조로 취급한다. 비즈니스 규칙을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다.

 

결론

객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 자료구조는 별다른 동작 없이 자료를 노출한다. 그래서 기존 자료구조에 새 동작을 추가하기는 쉬우나 기존함수에 새 자료구조를 추가하기는 어렵다.

728x90

'책 리뷰 > 클린코드' 카테고리의 다른 글

7장 오류처리  (0) 2023.10.14
5장 : 형식 맞추기  (0) 2023.06.28
클린코드 4장 : 주석  (0) 2023.06.21
3장 함수  (0) 2023.06.05
Clean Code(1,2 장)  (0) 2023.05.04

댓글