본문 바로가기
Clean Code

[클린 코드] 6장 - 객체와 자료 구조 (Objects and Data Structures)

by 노력남자 2022. 3. 6.
반응형

이번 포스팅에선 클린 코드 6장 - 객체와 자료 구조에 대해서 알아보겠습니다.

 

자료 구조 vs 객체

 

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

 

public class Point {
    public double x;
    public double y;
}

 

딱봐도 직교좌표계를 이용하는 걸 알 수 있다. 변수 x, y의 구현이 노출되어 있다.

 

public interface Vehicle {
    double getFuelTankCapacityInGallons();
    double getGallonsOfGasoline();
}

 

위 두 함수는 그냥 변수 값을 리턴하는 게 확실해보인다. 자료를 세세하게 노출하고 있다.

 

 

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

 

public interface Point {
    double getX();
    double getY();
    void setCartesian(double x, double y); // 직교좌표계
    double getR();
    double getTheta();
    void setPolar(double r, double theta); // 극좌표계
}

 

Point를 봐서는 어떤 좌표계를 사용하는지 어떤 걸 사용하는지 정확히 알 수 없다. 추상화가 잘 되어있다.

 

public interface Vehicel {
    double getPercentFuelRemaining();
}

 

getPercentFuelRemaining()만 봐서는 어떻게 남은 연료의 퍼센트지를 계산하는지 알 수가 없다. 어디서 정보가 오는지 전혀 드러나지 않는다.

 

자료 구조 + 절차적인 코드 vs 객체 + 객체 지향 기법

 

자료 구조 + 절차적인 코드

 

언제 자료 구조를 사용하고 언제 객체를 사용할까?

 

아래 코드를 보고 느껴보자!

 

Square, Rectangle, Circle의 넓이를 Geometry.area에서 구해준다.

 

음? 왜 저렇게 사용하지?라는 생각이 들어야 정상이다. 그 이유는 아래에서...

 

장점

 

- 함수 추가가 쉽다. ex) 둘레 길이를 구하는 perimeter() 함수를 추가하고 싶다면? 그냥 추가해주면 된다. 기존 클래스에 영향이 가지 않는다! 단순히 Geometry에 메소드만 추가해주면 된다.

 

단점

 

- 새로운 자료 구조를 추가하기가 어렵다. ex) 새도형을 추가하고 싶다면? Geometry의 함수 모두에 새로운 도형을 처리하는 로직이 추가되야 한다.

 

public class Square {
    private Point topLeft;
    private double side;
}

public class Rectangle {
    private Point topLeft;
    private double height;
    private double width;
}

public class Circle {
    private Point center;
    private double radius;
}

public class Geometry {
    public final double PI = 3.141592653589793;
    
    public double area(Object shape) throw NoSuchShapeException {
        if (shape instanceof Square) {
            Square s = (Square)shape;
            return s.side * s.side;
        } else if (shape instanceof Rectangle) {
            Rectangle r = (Rectangel)shape;
            return r.height * r.width;
        } else if (shape instanceof Circle) {
            Circle c = (Circle)shape;
            return PI * c.radius * c.radius;
        }
    }
}

 

객체 + 객체 지향 기법

 

아마 나도 아래처럼 개발했을 거 같다.

 

Shape Interface를 두고 구현체들을 만들어서 Geometry 클래스가 필요 없다.

 

장점

 

- 새로운 클래스를 추가하기가 쉽다. ex) 새도형을 추가하고 싶다면? Shape를 구현하는 구현체 하나 추가해주면 끝!

 

단점

 

- 새로운 함수를 추가하기 어렵다. ex ) 새로운 함수를 추가하면 각 구현체에 구현을 다 추가해줘야 한다.

 

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 PI * radius * radius;
    }
}

 

* 하고자 하는 말 : 둘의 장단점을 생각하여 꼭 객체지향적으로 짠다는 생각만 하지 말고 상황에 맞게 잘 사용하자!

 

딱 예제를 들기가 어렵네요. 추후에 이제 개발하면서 저런 경우가 있는지 봐야겠습니다.

 

디미터 법칙

 

자신이 조작하는 객체의 속사정을 몰라야 한다는 법칙

 

낯선 사람은 경계하고 친구랑만 놀아라 법칙 

 

직접적인 관계가 있는 객체의 정보만 사용해라.

 

ex) 클래스 C의 메서드 f는 다음과 같은 객체의 메서드만 호출해야 한다.

 

- 클래스 C

- f가 생성한 객체

- f 인수로 넘어온 객체

- C 인스턴스 변수에 저장된 객체

 

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

 

getOptions().getScratchDir() 까지는 괜찮은데 getOptions()가 가지고 온 객체의 메소드를 이용하므로 위 코드는 디미터 법칙을 위반한다.

 

만약? options, scratchDir, absolutePath가 객체가 아니라 자료 구조인 경우

 

ctxt.options.scratchDir.absolutePath로 호출해도 상관없다. 이유는? 객체가 아니기 때문이다.

 

 

잡종 구조

 

절반은 객체, 절반은 자료 구조인 구조

 

중요한 기능을 수행하는 함수 + 공개 변수 or 공개 조회/설정 함수

 

잡종 구조는 새로운 함수는 물론, 새로운 자료 구조도 추가하기 어렵다.

 

되도록이면 사용하지 말자!

 

구조체 감추기

 

아까 위에 나온 options, scratchDir, absolutePath가 객체라면? 아래 소스를 어떻게 디미터 법칙을 어기지 않고 사용할 수 있을까?

 

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

 

호출하는 객체들의 로직을 잘 파악해서 하나의 메서드로 만들자. 음? 뭔가 당연한 거 같기도 하고...?

 

ctxt.createScratchFileStream(classFileName);

 

자료 전달 객체 (DTO)

 

위에선 자료 구조는 공개 변수만 가지고 있고 변수를 호출하기 위한 함수는 따로 없다고 했다.

 

근데 자료 구조에도 조회, 설정 함수를 요구하는 프레임 워크들이 있는데 거기서 사용하는 게 바로 빈(Bean) 구조다.

 

우리가 알고있는 DTO, Entity 같은 모습을 말한다.

 

자료 구조다. 자료 구조! 당연히 객체인줄 알았는데...

 

public class SearchStduentRequest {
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

 

활성 레코드 (Active Record)

 

아마 자바 개발자들은 이런 코드가 뭔가 싶을 건데

 

객체 안에서 save, find 함수를 제공하는 걸 말합니다.

 

예전에 Django에서 본 적이 있는데 자바에선 이렇게 쓰는 걸 아직 한 번도 못 봤네요.

 

public class Student {
    private Long id;
    private String name;
    private Course course;

    ...
    
    public Student findById(Long id) { << 이겁니다!!
    	studentRepository.findById(id);
    }
}

 

 

결론 : 객체와 자료 구조의 장단점을 잘 파악해서 적절한 위치에 잘 적용하자!

 

뭔가 저는 전부 객체라고 생각하고 개발했는데 이번 장을 읽으면서 생각이 많아졌습니다.

 

짧은 장이였는데 뭔가 이해가 정확하게 가지는 않네요.

 

추후 개발하면서 많이 느껴보고 느껴진다면 다시 수정하겠습니다.

반응형

댓글