클린코드 핵심 정리 (6장 객체와 자료구조)

자료구조 vs 객체


예시 Vehicle


자료구조

// 자료 구조
public interface Vehicle {
	double getFuelTankCapacityInGallons();  // 연료탱크 용량(갤런 단위)
	double getGallonsOfGasoline();  // 가솔린 (갤런 단위)
}

public class Car implements Vehicle {
	double fuelTankCapacitInGallons;
	double gallonsOfGasoline;
	public double getFuelTankCapacityInGallons() {
		return this.fuelTankCapacitInGallons;
	}
	public double getFuelTankCapacityInGallons() {
		return this.gallonsOfGasoline;
	}
}

비지니스 로직 없이 순수 데이터를 저장하는 자료구조의 역할이다.


객체

// 객체
public interface Vehicle {
	double getPercentFuelRemain();
}

public class Car implements Vehicle {
	private double fuelTankCapacitInGallons;
	private double gallonsOfGasoline;

	public Car(double fuelTankCapacitInGallons, double gallonsOfGasoline) {
		if (fuelTankCapcitInGallons <= 0) {
			throw new IllegalArgumentException("fuelTankCapacitInGallons must be greater than zero");
		}
		this.fuelTankCapacitInGallons = fuelTankCapacitInGallons;
		this.gallonsOfGasoline = gallonsOfGasoline;
	}

	public double getPercentFuelRemain() {
		return this.gallonsOfGasoline / this.fuelTankCapacitInGallons * 100;
	}
}

위의 코드는 생성자에 비즈니스 로직과 관련된 코드가 들어가 있고, 필드를 private 으로 하여 자료를 숨겼다.


예시 Shape


자료구조

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) {
			Shape 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();
	}
}

만약 Triangle 이라는 새로운 클래스(자료구조가) 추가된다면? area() 함수 안에 if-else 문을 수정해야 한다.

  • 절차적인 코드는 새로운 자료구조를 추가하기 어렵다.
  • 함수를 고쳐야 한다.

객체

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;
	}
}

만약 Triangle 이라는 클래스를 추가한다면?
Triangle 클래스를 생성 후 Shape 인터페이스를 상속 받은 후area() 함수를 오버라이딩 하면된다.

즉 객체지향 코드는 새로운 클래스를 추가하기가 쉽다. 하지만 함수를 추가해야 한다.


상황에 맞는 선택을 하면 된다.


자료구조

  • 자료구조를 사용하는 절차적 코드는 기본 자료구조를 변경하지 않으면서 새 함수를 추가하기가 쉽다.
  • 절차적인 코드는 새로운 자료 구조를 추가하기 어렵다. 그러려면 모든 함수를 고쳐야 한다.

객체

  • 객체지향 코드는 기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉽다.
  • 객체 지향 코드는 새로운 함수를 추가하기 어렵다. 그러려면 모든 클래스를 고쳐야 한다.


객체 - 디미터 법칙


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

  • 클래스 C
  • 자신이 생성한 객체
  • 자신의 인수로 넘어온 객체
  • C 인스턴스 변수에 저장된 객체

A a = new A();
B b = a.getB()            (O)
C c = a.getB().getC();    (X)

디미터의 법칙에 어긋나는 상황

// 객체 - 기차 충돌. 디미터의 법칙 위배
final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

// 자료구조 - OK
final String outputDir = ctxt.options.scratchDir.absolutePath;

// 객체에 대한 해결책이 아니다. getter를 통했을 뿐, 값을 가져오는 것은 자료구조이다.
ctxt.getAbsolutePathOfScratchDirectoryOption();
ctxt.getScratchDirectoryOption().getAbsolutePath();

// 왜 절대 경로를 가져올까.. 근본 원인을 생각해보자!
// 객체는 자료를 숨기고 자료를 다루는 함수만 공개한다.
BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);


DTO


다른 계층 간 데이터를 교환할 때 사용

  • 로직 없이 필드만 갖는다.

  • 일반적으로 클래스명이 Dto(or DTO)로 끝난다.

  • getter/setter 를 갖기도 한다.

    - Beans

  • Java Beans: 데이터 표현이 목적인 자바 객체

  • 멤버 변수는 private 속성이다.

  • getter와 setter를 가진다.

public class AddressDto {
	private String street;
	private String zip;
}

public AddressDto(String street, String zip) {
	this.street = street;
	this.zip = zip;
}

public String getStreet() {
	return street;
}

public String setStreet(String street) {
	this.street = street;
}

public String getZip() {
	return zip;
}

public String setZip(String zip) {
	this.zip = zip;
}


Active Record


Database row를 객체에 맵핑하는 패턴

  • 비즈니스 로직 메서드를 추가해 객체로 취급하는 건 바람직하지 않다.
  • 비즈니스 로직을 담으면서 내부 자료를 숨기는 객체는 따로 생성한다.
  • 하지만.. 객체가 많아지면 복잡하고, 가까운 곳에 관련 로직이 있는 것이 좋으므로 현업에서는 Entity에 간단한 메서드를 추가해 사용한다.
public class Employee enxtends ActiveRecord {
	private String name;
	private String address;
	...
}

// ----

Employee bob = Employee.findByName("Bob Martin");

bob.setName("Robert C. Martin");
bob.save();


참고

해당 포스팅은 제로 베이스 클린코드 한달한권을 수강 후 정리한 내용입니다.

좋은 웹페이지 즐겨찾기