본문 바로가기
Architecture & Engineering/Clean Software

[Clean Code] 3. 객체와 자료구조

by 신숭이 2023. 3. 17.

[Clean Code] 3. 객체와 자료구조

 

 

객체와 자료구조 : 자료 추상화

 

자료구조 : 구체적으로 데이터를 그대로 보여준다. 즉 데이터를 있는 그대로 표현하는 것으로, 쓸데없이 멤버변수를 private으로 하고  public 한 getter와 setter를 만드는 것은 매우 무의미한 행위 (이런 행위를 한다고 추상화가 된다고 착각해선 안된다.)

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

 

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

 

 

객체 : 추상적인 개념으로 함수를 통해 자료를 설명한다. 이 객체를 사용하는 사용자는 내부적으로 어떻게 구현이 되어있는지 모른 채 자료의 핵심을 조작할 수 있어야 진정한 추상화이다. 즉 객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.

아래의 Point는 위 Point 클래스에 비해 내부적으로 극좌표계인지 직교좌표계인지 알 수가 없다. (즉 내부 구조를 알 수 없다 => 이게 좋다)

public interface Point {
    double getX();
    double getY();
    void setCartesian(double x, double y);
    double getR();
    double getTheta();
    void setPolar(double r, double theta);
}

위 Vehicle에 비해 아래의 Vehicle은 정확한 데이터가 아닌 추상적인 개념으로 제공한다.

public interface Vehicle {
    double getPercentFuelRemaining();
}

 

 

 

객체와 자료구조 : 절차적 클래스(자료구조) vs 다형적 클래스(객체)

 

다음은 절차적 도형 클래스의 예시이다. 

public class Square {
    public Point p;
    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 s = (Rectangle) shape;
            return s.height * s.width;
        } else if (shape instanceof Circle) {
            Circle s = (Circle) shape;
            return PI * s.radius * s.radius;
        }

        throw new NoSuchShapeException();
    }
}

절차적 도형 클래스(자료 구조)의 다음의 장/단점으로 특징을 설명할 수 있다.
장점 : 함수(기능)의 추가가 쉽다. 
(위에서 Geometry에 둘레길이를 구하는 perimeter()함수를 추가한다고 했을 때, 자료 구조의 수정 없이 손쉽게 추가할 수 있다.)
단점 : 자료구조의 추가가 어렵다.
(위에서 새 도형을 추가한다고 해보자. Geometry의 모든 함수를 수정해야한다) 

 

다음은 다형적 클래스(객체)의 예시이다.

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 topLeft;
    private double radius;
    public final double PI = 3.141592653589793;

    public double area() {
        return PI * radius * radius;
    }
}

 

다형적 도형 클래스(객체)의 다음의 장/단점으로 특징을 설명할 수 있다.
장점 : 자료의 추가가 쉽다.
(새로운 자료를 추가해도 기존 함수에는 아무 영향을 끼치지 않는다. area()는 다형적 메서드이기 때문)
단점 : 함수(기능)의 추가가 어렵다.
(만약 새로운 함수를 추가하려면 모든 클래스를 수정해야한다)

책에서 말하고자 하는 것은 이 둘은 이렇듯 근본적으로 양분되고, 상호보완적이라는 것.
복잡한 시스템을 작성할 때,
새로운 자료타입이 많이 추가될 것 같은 서비스면 => 객체 지향적 코드로 작성
새로운 기능이 많이 추가될 것 같은 서비스면 => 절차적 코드로 작성

저자는 OOP에 취해서 모든 것을 객체생각하는 행위는 미신에 가까운 행동이라고 한다. 때론 절차적 코드가 적합한 상황도 있다.

 

 

 

디미터의 법칙

 

자신이 조작하는 객체의 속사정은 몰라야 한다. => 조회 함수로 내부 구조를 공개해선 안된다.
이것을 어떻게 실현할 수 있을까? 디미터의 법칙은 다음의 규칙을 만족하라고 이야기한다.

클래스 C 내에서 호출할 수 있는 메서드의 종류

  1. 클래스 C의 멤버 메서드 f
  2. f가 생성한 객체의 멤버 메서드
  3. f의 인수로 넘어온 객체의 메서드
  4. C의 인스턴스 변수로 저장된 객체의 메서드

만약 위의 메서드의 반환으로 나온 객체의 메서드를 쓰면 규칙을 위반하는 것.

만약 기능 구현을 하면서 위와 같은 법칙을 만족 못하고 더욱 깊숙이 있는 메서드나 객체를 끄집어낸다면 조회함수가 내부 구조를 충분히 숨기고 있지 못한 것이다.
(아래의 예시에선 AClass가 좋은 조회함수를 제공하지 않는 것이다. Caller로 하여금 더 깊은 곳의 메서드를 콜 하게 하였으니)

public class EClass {
    public void eFunction() {

    }
}


public class DClass {
    public void dFunction() {

    }
}

public class BClass {
    public void bFunction() {

    }
}

public class AClass {
    public DClass aFunction() {
        return new DClass();
    }
}


public class CClass {
    public BClass bInstance = BClass();
    public void cFunction1(AClass aParameter) {
        EClass eVariable = new EClass();
        eVariable.eFunction();  //가능
        cFunction2(); // 가능
        bInstance.bFunction();  // 가능
        aParameter.aFunction(); // 가능
        aParameter.aFunction().dFunction(); // 위반 !!
    }

    public void cFunction2() {

    }
}

 

다만 자료 구조를 반환받는다면 디미터의 법칙이 적용되지 않는다.

aParameter.aFunction().value1;
aParameter.aFunction().value2;
aParameter.aFunction().value1.x;
aParameter.aFunction().value1.y;

 

* 자료 전달 객체(DTO) : DB나 서버와 통신할 때, 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 첫 번째로 가용하는 구조체.

 

 

댓글