[엘레강트 오브젝트] 2장 - Education (1)

HyeBin, Park·2022년 6월 9일
0
post-thumbnail

엘레강트 오브젝트 2장 - Education (1)

이 책에 쓰여진 모든 조언들은 직접적으로 코드의 복잡성에 영향을 미치고 복잡성은 직접적으로 유지보수성에 영향을 미칩니다. 복잡성이 높을수록 유지보수성이 저하되고, 시간과 돈이 낭비되며, 고객 만족도가 떨어집니다.

2.1 가능하면 적게 캡슐화하세요

4개 또는 그 이하의 객체를 캡슐화할 것을 권장합니다. 더 많은 객체를 캡슐화해야야 한다면, 클래스에 문제가 있는 것이기 때문에 리팩토링이 필요합니다.

class Cash {
	private Integer digits;
    private Integer cents;
    private String currency;
}
  • 내부에 캡슐화된 객체 전체를 가리켜 객체의 상태 또는 식별자 라고 부릅니다.
    상태 == 식별자 라고 여기는 부분도 공감이 안 간다. 상태는 충분히 달라질 수 있는 건데 이를 식별자라고 여기면 어떻게 매번 같은 객체라는걸 식별할 수 있는지? 상태가 달라지면 완전히 다른 것 이라고 여기는 부분이 객체지향의 사실과 오해와는 전혀 다른 관점으로 상태와 식별자를 바라보고 있는 것 같다.
  • 위의 Cash 클래스는 3개의 객체를 캡슐화 하고 있고, 3개의 객체들이 Cash 클래스의 객체를 식별한다.
    => 동일한 값의 달러, 센트, 통화를 캡슐화하는 Cash 클래스는 서로 동일하다.
  • 캡슐화 가능한 객체의 수는 4개이며 더 많은 객체가 필요하다면, 클래스를 더 작은 클래스들로 분해해라.

🎈 개인적(저자)으로 객체 패러다임은 다음과 같은 방식으로..

Cash x = new Cash(29, 95, "USD");
Cash y = new Cash(29, 95, "USD");
assert x.equals(y); // true
assert x == y; // false 
  • Java, C++에서 객체의 식별자와 상태는 서로 분리 되어있습니다.
  • 위의 코드와 같은 결과는 C++로부터 물려받은 Java 언어의 설계적 결함
    자바는 OOP 적인 언어가 아니다. OOP 관점에서는 설계적 결함이다.. 너무나도 당연하다고 생각했던걸 결함이라고 하니 공감이 안 간다.
  • 위의 설계적 결함을 해결하기 위해 == 연산자를 사용하지 말고 항상 equalse() 메서드를 오버라이드 하길

2.2 최소한 뭔가는 캡슐화하세요

class Year {
	int read() {
    	return System.currentTimeMillis()
        	/ (1000 * 60 * 60 * 24 * 30 * 12) - 1970;
    }
}
  • Year 클래스의 인스턴스는 어떤 것도 캡슐화하지 않기 때문에 이 클래스의 모든 객체들은 동일하다 => 아무것도 캡슐화하지 않는 방식, 설계가 잘못됐다.

💫 프로퍼티가 없는 클래스는 정적 메서드와 유사하다

  • 정적메서드가 존재하지 않고 인스턴스 생성과 실행을 엄격하게 분리하는 순수한 OOP에서는 기술적으로 프로퍼티가 없는 클래스를 만들 수 없다.
    순수한 OOP란 뭔지.. 이상과 꿈?
  • 실행으로부터 인스턴스 생성을 고립시켜야합니다.
    => 생성자에서만 new 연산자를 허용해야한다.

🐾 객체가 무와 비슷한 무언가가 아니라면 무언가를 캡슐화해라

  • 캡슐화된 상태는 세계안에서 객체의 위치를 지정하는 고유한 식별자
  • 어떤 것도 캡슐화하지 않는다면 객체의 좌표는 바로 객체 자신, 객체 자신이 세계 전체가 되어야합니다.
  • 오직 하나의 세계만 존재할 수 있기 때문에 이 클래스는 오직 하나만 존재해야합니다.

🤖 무(nothing)

  • '무' 란 세계 안에서 좌표가 없는 존재
  • 생존이나 자신의 좌표를 표현하기 위해 다른 엔티티를 필요로 하지 않는다.

2.3 항상 인터페이스를 사용하세요

객체들이 서로를 필요로 하기 때문에 결합된다. 인터페이스는 우리가 전체적인 환경을 구조화된 상태로 유지할 수 있도록 해줍니다.

💦 결합도

  • 설계를 시작하는 단계에서 객체가 어떤 일 을 수행하고 서비스를 제공하는지 알고 있는 편이 낫기 때문에 결합이 유용하다.
  • 애플리케이션이 성장하기 시작하고 객체의 수가 수십 개를 넘어가면서부터 강한 결합도가 심각한 문제가 된다.
  • 결합도 문제는 유지보수성에 영향을 미친다.
    => 최선을 다해서 객체를 분리해야한다.
    => why? 상호작용하는 객체를 수정하지 않고도 해당 객체를 수정할 수 있도록 만든다는 것을 의미하기 때문이다.

🐏 훌륭한 도구 인터페이스

interface Cash {
	Cash multiply(float factor);
}

class Employee {
	private Cash salary;
}
  • 인터페이스는 의사소통하기 위해 따라야하는 계약이다.
  • Employee 클래스는 Cash 인터페이스의 구현 방법에는 관심이 없다.
  • Cash 인터페이스를 이용하면 Employee 클래스와 Cash 구현 클래스를 느슨하게 분리할 수 있다.
  • 올바르게 설계된 클래스라면 최소한 하나의 인터페이스라도 구현하지 않는 public 메서드를 포함해서는 안 된다.

🧸 느슨한 결합도

  • 클래스가 존재하는 이유는 다른 누군가가 클래스의 서비스를 필요로 하기 때문이다.
  • 서비스는 계약이자 인터페이스이기 때문에 클래스가 제공하는 서비스는 문서화되어야한다.
  • 동일한 인터페이스를 구현하는 여러 클래스들이 존재하고 서로 다른 클래스를 쉽게 대체할 수 있어야한다. => 느슨한 결합도

2.4 메서드 이름을 신중하게 선택하세요

올바르게 지은 메서드 이름은 사용자들이 객체를 설계한 목적, 객체가 수행해야하는 임무, 객체의 존재 목적과 살아가는 의미를 더 잘 이해할 수 있도록 해줍니다.

🗻 빌더 : 어떤 것을 만든다.

  • 뭔가를 만들고 새로운 객체를 반환하는 메서드
  • 빌더의 반환타입은 절대 void 가 될 수 없습니다.
  • 이름은 항상 명사여야합니다.
  • 형용사를 덧붙여 메서드의 의미를 좀 더 풍부하게 설명할 수 있다.

👾 조정자 : 뭔가를 조작한다.

  • 객체로 추상화한 실세계 엔티티를 수정하는 메서드
  • 반환 타입은 항상 void 입니다.
  • 이름은 항상 동사입니다.
  • 부사를 덧붙여 메서드의 의미를 좀 더 풍부하게 설명할 수 있다.

💊 뭔가를 만드는 동시에 조작하는 메서드가 있어서는 안된다.

// 저장된 전체 바이트를 반환
int save(String content);
// map이 변경된 경우 true 반환
boolean put(String key, Float value);
// speed를 저장한 후 이전 값을 반환
float speed(float val);
  • save() 메서드 = 조정자
    => 반환 값을 void로 바꾸거나 bytesSaved()와 같은 네이밍으로 변경해야한다.
  • put() = 조정자
    => putOperation의 인스턴스를 반환하도록 클래스의 전반적인 설계를 수정해야한다.
    => putOperation 클래스는 조정자인 save() 메서드와 성공/실패 여부를 반환하는 success() 메서드를 포함할 것이다.

🤖 빌더는 명사다.

  • 어떤 것을 반환하는 메서드의 이름을 동사로 짓는 것은 잘못이다.
  • 부적절하게 지은 메서드 이름은 객체의 저체적인 개념을 망가트리고 사용자들이 객체를 데이터 집합이나 프로시저들의 모음으로 다루도록 종용합니다.

🎡 빌더 네이밍

  • 객체는 지시에 따르는 것이 아닌 계약에 기반해 일하고 싶어합니다.
  • 메서드의 이름을 동사로 지을 때에는 객체에게 무엇을 할지를 알려줘야한다.
  • 만들라고 요청하는 것은 협력자에 대한 존중이 결여되어 있는 것이다.
  • 무엇을 만들어야 하는 지만 요청하고 만드는 방법은 객체 스스로 결정하도록 해야한다.

🐢 조정자는 동사다.

  • 조정자는 세계에 변화를 주고 싶을 뿐이고, 대상 객체는 변화될 세상의 대표자입니다.
  • 요청이 무시될 지도 모르지만 처음부터 뭔가를 돌려받을 것이라고 기대하지 않았기 때문에, 절대 모욕적이거나 무례한 일이 아닙니다.

🥠 Boolean 값을 결과로 반환하는 경우

  • Boolean 값을 반환하는 메서드는 예외적인 경우로 가독성 측면에서 형용사로 지어야한다.
  • 메서드를 읽을때는 접두사 is를 추가하고 실제 코드에서는 빼야한다.
    굳이 빼야 하는가? 코드를 읽을 때는 is를 붙여서 말하고, 코드를 작성할 때는 빼서 작성하라 라는 부분이 이해가 잘 안 간다. is 중복도 가독성을 많이 해치는 건가?
  • isEquals (x) isExists (x) => isEqualTo isPresent

적용해보기

1. 빌더의 이름은 명사로

  • discount는 현재 값을 받아 value% 만큼 할인된 가격으로 반환해주는 함수입니다.

  • 명사로 만들었습니다. 확실히 voucher.discount() 보다는 voucher.discountedValue가 반환값을 정확히 알 수 있었습니다.

2. 불변 객체로 만드세요

  • 변경 전

  • 변경 후

  • 현재 프로젝트에서는 바우처의 value 값에 따라 네이밍을 하지 않아, final 적용 후 엄청난 효과가 나지는 않았습니다.
  • 하지만 halfDiscountVoucher 객체를 가변 객체 value로 만들었다면, 책의 five&fifty 예제처럼 좋지 못한 결과를 볼 수 있을 것 같습니다.

0개의 댓글