본문 바로가기
독후감

클린코드 6장 - 객체와 자료구조

by 고고 2021. 12. 20.

자료 추상화

// 구체적인 Point 클래스
// 구현을 노출한다.
public class Point {
	public var x: Double
	public var y: Double
}

// 추상적인 Point 클래스
// 클래스 메서드가 접근 정책을 강제한다.
public protocol Point {
	func getX() -> Double
	func getY() -> Double // 조회는 각각 가능하지만
	func setCartesian(Double x, Double y) // 설정을 2개의 값을 동시에 넣어주어야 한다.
	func getR() -> Double
	func getTheta() -> Double
	func setPolar(Double r, Double theta)
}

 

변수를 private로 선언하더라도 각 값마다 get, set함수를 제공한다면 구현을 외부로 노출하는 셈이다.

 

변수 사이에 함수라는 계층을 넣는다고 구현이 저절도 감춰지지는 않는다. 구현을 감추려면 추상화가 필요하다! 추상 인터페이스(프로토콜)을 제공해 사용자가 구현을 모른 채 자료의 핵심을 조작할 수 있어야 진정한 의미의 클래스다.

 

 

 

// 구체적인 Vehicle 클래스
// 변수를 그대로 리턴하는 함수일 것이 틀림없다.
public interface Vehicle {
	public getFuelThankCapacityInGallons();
	public getGallonsOfGasoline();
}

// 추상적인 Vehicle 클래스
// 백분율이라는 추상적인 개념으로 반환하기에 어디서 오는지 사용자에게 드러나지 않는다.
public interface Vehicle {
	double getPercentFuelRemaining();
}

 

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

 

객체 vs 자료구조

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

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

 

 

절차적 vs 객체적

// 절차적인 도형
// 각 도형 클래스는 간단한 자료구조
// 도형이 동작하는 방식은 Geometry 클래스에서 구현
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(Object 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 Circle implements Shape { 
	private Point center;
	private double radius;
	public final double PI = 3.141592653589793;

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

새 도형을 추가해도 기존 함수에 아무런 영향을 미치지 않는다.

반면 새 함수를 추가하고 싶다면 도형 클래스 전부를 고쳐야 한다.

 

 

 

(자료구조를 사용하는) 절차적인 코드 - 기존 자료 구조를 변경하지 않으면서 새 함수를 추가하기 쉽다.

객체 지향 코드 - 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.

 

==

 

절차적인 코드 - 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다.

객체 지향 코드 - 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.

 

 

 

디미터 법칙

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

 

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

  • 클래스 C
  • f가 생성한 객체
  • f 인수로 넘어온 객체
  • C 인스턴스 변수에 저장된 객체

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

 

- 기차 충돌

여러 객차가 한 줄로 이어진 기차처럼 보이는 코드.

// Before
let outputDir: String = ctxt.getOptions().getScratchDir().getAbsolutePath()

 

// After
let opts: Options = ctxt.getOptions()
let scratchDir: File = opts.getScratchDir()
let outputDir: String = scratchDir.getAbsolutePath()

 

ctxt, Options, ScratchDir이

개체라면 내부 구조를 숨겨야 하므로 디미터 법칙을 위반한다.

자료구조라면 당연히 내부 구조를 노출하므로 디미터 법칙이 적용되지 않는다.

 

만약 자료구조였다면 이렇게 구현했어야 한다.

let outputDir: String = ctxt.options.scratchDir.absolutePath

 

만약 객체라면 임시 디렉터리의 절대 경로는 어떻게 얻어야 좋을까?

// 1
// ctxt 객체에 공개해야 하는 메서드가 너무 많음
ctxt.getAbsolutePathOfScratchDirectoryOption();
// 2
// ctxt가 객체라면 뭔가를 하라고 해야 하는데
// getScratchDirectoryOption()에서 속을 드러내라고 말하는 느낌이다.
ctxt.getScratchDirectoryOption().getAbsolutePath()

 

임시 디렉터리의 절대 경로를 얻으려는 이유가 임시 파일을 생성하기 위함이기에 ctxt 객체에 임시 파일을 생성하라고 시키면 객체에게 맡기기에 적당한 임무로 보인다!

// ctxt는 내부 구조를 드러내지 않으며, 모듈에서 해당 함수는 여러 객체를 탐색할 필요가 없다.
// 디미터 법칙 충족
let bos: BufferedOutputStream = ctxt.createScratchFileStream(classFileName);

 

 

 

결론

어떤 시스템을 구현할 때, 새로운 자료 타입을 추가하는 유연성이 필요하면 객체가 더 적합하다.

다른 경우로 새로운 동작을 추가하는 유연성이 필요하면 자료 구조와 절차적인 코드가 더 적합하다.

 

 

편견 없이 직면한 문제에 최적인 해결책을 선택해야 한다.

댓글