넘치게 채우기

6장. 객체와 자료 구조 본문

개발/Clean Code

6장. 객체와 자료 구조

riveroverflow 2023. 8. 4. 17:29
728x90
반응형

변수를 비공개로 정의하는 이유는 남들이 변수에 의존하지 않게 만들기 위해서입니다.

그렇다면 어째서 get 함수와 set함수는 공개해서 외부에 노출할까요?

 

객체와 자료 구조

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

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

둘은 상반된 관계입니다.

자료 추상화

추상화는 개발자가 객체의 구현 세부 사항에 신경 쓰지 않고도 객체의 기능에 집중할 수 있도록 해주는 중요한 개념입니다. 코드의 복잡성을 줄이고 가독성을 높이는 데 중요한 역할을 합니다.

 

데이터 추상화를 사용하면, 객체의 실제 구현 내용을 숨기고 사용자에게 필요한 기능만을 제공하는 인터페이스를 제공할 수 있습니다. 이를 통해, 객체의 내부 구현이 변경되더라도 사용자 코드에 영향을 주지 않는 격리를 구현할 수 있습니다.

 

따라서, 가능하면 인터페이스를 추상적으로 유지하고, 사용자가 필요로 하는 기능만 제공하는 것이 좋습니다. 이를 통해 코드의 복잡성을 줄이고, 유지 보수성과 확장성을 향상시킬 수 있습니다.

 

  • 구체적인 Vehicle 클래스 구체적인 용량과 양을 받는 모습.
public interface Vehicle {
	double getFuelTankCapacityInGallons();
	double getGallonsOfGasoline();
}
  • 추상적인 Vehicle 클래스. 필요한 수치인 퍼센티지만 받는 모습.
public interface Vehicle {
	double getPercentFuelRemaining();
}

 

자료/객체 비대칭

앞선 예제는 객체와 자료 구조 사이에 벌어진 차이를 보여줍니다.

  • 절차적인 도형 클래스
public class Square {
	public Point topLeft;
	public double side;
}

public class Rectangle {
	public Point topleft;
	public double height;
	public double weight;
}

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.size;
		}
		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();
	}
}
  • 객체 지향적인 도형 클래스
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 final double PI = 3.141592653589793;
	private Point center;
	private double radius;

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

두 종류는 상호 보완적인 특징이 있습니다.

절차적인 코드는 기존 자료 구조를 변경하지 않고 새로운 함수를 만들기 쉽습니다.

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

 

절차적인 코드는 새로운 자료 구조를 추가하기 어렵습니다. 모든 함수를 고쳐야 하기 때문입니다.

객체 지향 코드는 새로운 함수를 추가하기 어렵습니다. 모든 클래스를 고쳐야 하기 때문입니다.

 

 

디미터 법칙

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

즉, 내부 구조를 모르게 해야 한다는 것 입니다.

메서드가 반환하는 객체의 메서드를 사용하면 안 됩니다.

 

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

흔히 이런 코드를 기차 충돌(trainwreck)이라고 부릅니다. 여러 객체가 한 줄로 이어진 기차처럼 보이기 때문입니다.

이러한 방식은 피하는 것이 좋습니다.

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

이렇게 나누는 편이 낫습니다.

 

두 코드들 모두 ctxt, options, scratchDir이 객체인 경우 디미터 법칙을 위반합니다.

조회 함수를 사용하는 바람에 혼란이 생깁니다.

 

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

이런 식으로 구현했다면 자료 구조로써 사용하는 것 이기 때문에 디미터 법칙이 적용되지 않는 것이죠.

 

자료 구조는 무조건 함수 없이 공개 변수만,

객체는 비공개 변수와 공개 함수를 포함한다면 훨씬 코드가 간단해집니다.

 

잡종 구조

절반은 객체, 절반은 자료 구조인 잡종 구조가 있습니다.

이는 피해야 할 구조입니다.

객체와 자료 구조의 약점들만 모아져 있는 구조입니다.

 

구조체 감추기

만약 ctxt, options, scratchDir이 정말 객체인 경우, 위처럼 조회 함수를 줄줄이 엮으면 안 됩니다.

객체라면 내부 구조를 감춰야 하기 때문입니다.

이의 해결법은

ctxt 객체에 임시 파일을 생성하라고 시키는 방법이 있습니다.

BufferedOutputStream bos = ctx.createScratchFileStream(classFileName);

이러면 내부 구조를 보이지 않고, 모듈에서 함수는 여러 객체를 탐색할 필요가 없습니다.

 

자료 전달 객체(Data Transfer Object)

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

데이터베이스와 통신하거나 소켓에서 받은 메시지의 구문 분석에 유용합니다.

ㅣ이의 일반적인 형태를 빈 구조라고 합니다.

일반적인 비공개 변수와 getter, setter함수만을 가진 클래스는 빈 구조라고 합니다.

빈(Bean)은 자바에서 사용하는 개념으로, 정보를 나타내는 데 사용되는 객체를 의미합니다. 이는 일반적으로 프로퍼티라는 사항들로 이루어져 있으며, 이 프로퍼티는 보통 get 및 set 메서드를 통해 접근됩니다. 이러한 메서드를 각각 getter와 setter라고 부릅니다.

자바 빈(Java Bean)은 다음의 규칙에 따라 작성된 클래스입니다.

  1. 모든 변수는 private으로 선언되어야 합니다. 이는 데이터 보호를 위함입니다.
  1. 각 변수에 대해 getter와 setter 메서드가 있어야 합니다. 이를 통해 private 변수에 접근할 수 있습니다.
  1. 클래스는 Serializable 인터페이스를 구현해야 합니다. 이는 객체의 상태를 저장하거나 전송할 수 있게 합니다.
  1. 클래스에는 public 형태의 인자가 없는 생성자가 있어야 합니다.

 

빈 구조 예시

public class Employee implements java.io.Serializable
{
    private int id;
    private String name;

    public Employee() {}

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

위의 예시에서 Employee 클래스는 두 개의 private 필드 idname을 가지고 있으며, 이들 필드에 대해 getter와 setter 메서드를 제공하고 있습니다. 또한 Serializable 인터페이스를 구현하고 있으며, 인자 없는 public 생성자를 제공하고 있습니다.

 

이렇게 빈의 구조를 이용하면, 데이터를 캡슐화하여 보호할 수 있고, 클래스의 상태를 쉽게 저장하거나 전송할 수 있습니다. 또한, 클래스의 상태에 대한 접근과 수정을 보다 통제할 수 있습니다. 이러한 이유로 빈 구조는 자바에서 널리 사용됩니다.

 

 

활성 레코드

dto의 특수한 형태입니다. 빈 구조에서 save나 find같은 탐색 함수도 제공하는 클래스입니다.

 

주의할 점은 이러한 dto들에 추가적인 비즈니스 규칙 메서드를 추가하면 안 된다는 것입니다.

그러면 자료 구조도 아니고 객체도 아닌 잡종 구조가 나오기 때문입니다.

 

마무리

객체는 동작을 공개하고 자료를 숨깁니다.

기존 동작을 변경하지 않으면서 새 자료 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵습니다.

자료 구조는 별다른 구조 없이 자료를 노출합니다. 그래서 기존 자료 구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵습니다.

 

우수한 프로그래머는 이들을 이해하고, 객체와 자료구조, 객체 지향 프로그래밍과 절차 지향 프로그래밍을 때에 따라 적절히 사용할 줄 압니다.

728x90
반응형

'개발 > Clean Code' 카테고리의 다른 글

8장. 경계  (0) 2023.08.07
7장. 오류 처리  (0) 2023.08.05
5장. 형식 맞추기  (0) 2023.08.03
4장. 주석  (0) 2023.08.01
3장. 함수  (0) 2023.07.30