ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [클린코드] 객체와 자료구조
    📚 개발 도서/클린코드 2023. 7. 16. 17:35

    변수를 비공개로 정의하는 이유가 있다. 남들이 변수에 의존하지 않게 만들고 싶어서다.

    그렇다면 왜 수많은 프로그래머가 조회함수와 설정함수를 당연하게 공개하여 비공개 변수를 외부에 노출할 것인가?라는 질문으로 시작한다.

    자료 추상화

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

    추상 인터페이스를 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.

    자료를 세세하게 공개하기보다는 추상적인 개념으로 표현하는 편이 좋다.

    아무 생각 없이 조회/설정 함수를 추가하는 방법이 가장 나쁘다.

    개발자는 객체가 포함하는 자료를 표현할 가장 좋은 방법을 심각하게 고민해야 한다.

    자료/객체 비대칭

    객체 vs 자료구조?

    • 객체: 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다.
    • 자료 구조: 자료를 그대로 공개하며 별다른 함수를 제공하지 않는다.

     

    객체는 추상화 뒤로 자료를 숨긴 채 자료를 다루는 함수만 공개한다. 자료 구조는 자료를 그대로 공개하며 별다른 함수는 제공하지 않는다.

     

     

    절차지향 코드

    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(Objects 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();
        }
    }
    
    • 이점: Geometry클래스에 새로운 함수를 추가할 때 도형 클래스는 수정하지 않아도 된다.
    • 단점: 새로운 도형을 추가한다면 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 Rectangle implements Shape {
        private Point center;
        private double radius;
        public final double PI = 3.1415926533589793;
    
        public double area() {
            return PI * radius * radius;
        }
    }
    

    객체지향적인 코드

    • 이점: 기존 코드나 함수를 수정하지 않고 새로운 클래스를 추가하기 쉽다.
    • 단점: 새로운 함수를 추가하기 위해서는 모든 클래스를 고쳐야 한다.

     

    모든 문제를 객체로 해결하려는 생각은 좋지 않으니 적합한 상황에서 잘 쓰도록

     

    디미터 법칙

    모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다.

    • 객체는 조회 함수로 내부 구조를 공개하면 안 된다.

     

    다음과 같은 메서드만 객체의 메서드만 호출해야 한다. 참고

     

    1. 객체 자신의 메서드들

    2. 메서드의 파라미터로 넘어온 객체들의 메서드들

    3. 메서드 내부에서 생성, 초기화된 객체의 메서드들

    4. 인스턴스 변수로 가지고 있는 객체가 소유한 메서드들

    class Demeter {
        private Member member;
    
        public myMethod(OtherObject other) {
            // ...
        }
    
        public okLawOfDemeter(Paramemter param) {
            myMethod();             // 1. 객체 자신의 메서드 
            param.paramMethod();    // 2. 메서드의 파라미터로 넘어온 객체들의 메서드 
            Local local = new Local();
            local.localMethod();    // 3. 메서드 내부에서 생성, 초기화된 객체의 메서드
            member.memberMethod();  // 4. 인스턴스 변수로 가지고 있는 객체가 소유한 메서드
        }
    }
    

    위 객체에서 허용된 메서드가 반환하는 객체의 메서드는 호출하면 안된다.

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

    기차 충돌

    바로 위의 코드는 메서드가 반환하는 객체의 메서드를 사용하기 때문에 디미터 법칙을 위반한다.

    다음과 같이 변환하는 것이 바람직하다.

    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    String outputDir = scratchDir.getAbsolutePath();
    

    → ctxt, opts, scratchDir이 객체라면 디미터 법칙 위반

    → ctxt, opts, scratchDir이 자료 구조라면 이미 노출된 내부 구조를 사용하므로 디미터 법칙이 적용되지 않음.

    final String outputDir = ctxt.options.scratchDir.absolutePath;
    

    잡종 구조

    객체와 자료구조가 섞인 잡종 구조가 나오는데, 이는 새로운 함수, 새로운 자료 구조 둘 다 추가하기 어려운 단점만 모아놓은 구조로 피하는 편이 좋다.

    프로그래머가 함수나 타입을 보호할지 공개할지 확신하지 못해 어중간하게 내놓은 설계에 불과하다.

     

    구조체 감추기

    ctxt, options, scratchDir이 객체라면 위와 같이 체이닝을 해서는 안된다.

    객체는 내부 구조를 감춰야 하기에 내부를 노출하는 get 함수는 썩 내키지 않는다.

    그럴 때는 이 로직을 살펴보는게 좋다.

    String outFile = outputDir + "/" + className. replace( '.', '/') + ".class";
    FileOutputStream fout = new FileOutputStream(outFile);
    BufferedOutputStream bos = new BufferedOutputStream(fout);
    

    이런 식으로 절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위함이다.

    그렇다면, 임시 파일을 생성하는 것을 해당 객체가 아닌 ctxt 객체에게 위임하면 어떨까?

    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
    

    이제 임시파일 생성에 대한 책임이 ctxt에게 가며 ctxt는 불필요하게 내부 구조를 노출할 필요가 없어진다. 그렇기에 디미터 법칙도 위반하지 않는다.

    자료 전달 객체

    공개 변수만 있고 함수가 없는 클래스 (DTO: Data Transfer Object)

    데이터를 전달만하며 가공되지 않은 원천정보를 애플리케이션 코드에서 사용할 객체로 변환하는 과정중 가장 처음 사용하는 구조체.

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

    DTO

    공개 변수만 있고 함수가 없는 클래스

     

    Bean

    비공개 변수와 getter, setter가 있는 클래스

     

    활성 레코드 (DTO의 특수한 형태)

    공개, 비공개 변수와 getter, setter, 그리고 탐색 함수가 있는 클래스

    활성 레코드에 비즈니스 규칙 메서드를 추가해 객체로 취급하는 경우가 있는데 바람직하지 않다.

    → 잡종 구조가 나오기 때문

     

    비즈니스 규칙을 담는 객체는 따로 생성하는 것이 옳다. 내부 자료는 활성 레코드의 인스턴스일 가능성이 높다.

    class EmailSender {
    	private Person re ceiver;
    	...
    	public void sendEmail() {
    		...
    	}
    	...
    }
    
    728x90
    반응형

    '📚 개발 도서 > 클린코드' 카테고리의 다른 글

    [클린코드] 오류 처리  (0) 2023.07.18
    [클린코드] 시스템  (0) 2023.07.01
    [클린코드] 클래스  (0) 2023.06.15
    [클린코드] 함수  (0) 2023.05.07
    [클린코드] 의미 있는 이름  (0) 2023.04.29

    댓글

Designed by Tistory.