엘레강트 오브젝트 - Birth

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

객체는 살아있는 유기체이다.

1. 출생

🛬 가시성 범위

if (price < 100) {
	Cash extra = new Cash(5);
    price.add(extra);
}
  • 객체는 자신의 가시성 범위 안에서 살아갑니다.
  • if 블록 안에서만 extra라는 객체를 볼 수 있기 떄문에, if 블록 내부가 extra 객체의 가시성 범위가 됩니다.

🎈 유지 보수성

  • 책의 목표는 코드의 유지보수성을 향상시키는 것입니다.
  • 모든 소프트웨어의 중요한 가치이며, 코드를 이해하는데 걸리는 시간으로 측정할 수 있습니다.
  • 코드를 이해하는데 시간이 오래 걸릴수록 유지보수성을 나빠지고 코드 품질이 저하됩니다.
    가독성이 떠오르네요. 가독성 좋은 코드 = 유지보수하기 좋은 코드 ?
  • 객체지향 프로그래밍에서 객체와 객체의 역할을 이해함으로써 코드의 유지보수성을 향상시킬 수 있으며, 코드의 길이는 더 짧아지고, 소화하기 쉬워지며, 모듈성이 향상되고, 응집도가 높아집니다. 프로젝트에서 비용 절감을 의미합니다.

1.1 -er로 끝나는 이름을 사용하지 마세요

🐏 객체와 클래스의 차이점?

객체가 필요하면 꺼내쓰고, 더 이상 필요하지 않은 객체를 반환할 수 있는 객체의 웨어하우스로 클래스를 바라봐라, 클래스는 능동적인 관리자

  • 클래스는 객체의 팩토리입니다.

  • 클래스는 객체를 생성합니다. 일반적으로는 클래스가 객체를 인스턴스화한다라고 표현합니다.

  • new는 객체의 팩토리를 제어할 수 있는 원시적인 수단입니다.

  • 클래스는 객체의 어머니라고 할 수 있다.

🚛 클래스 이름 짓기

클래스 이름 짓기의 잘못된 방법은 클래스의 객체들이 무엇을 하고 있는지를 살펴본 후 기능에 기반해서 이름을 짓는 방법

🐰 잘못된 이름

class CashFormatter {
	private int dollars;
    
    CashFormatter(int dlr) {
    	this.dollars = dlr;
    }
    
    public String format() {
    	return String.format("$ %d", this.dollars);
    }
}
  • CashFormmater 클래스의 객체는 dollar에 저장된 금액을 문자열로 포맷팅하는 일을 수행합니다.

  • 클래스의 이름은 객체가 노출하고 있는 기능에 기반해서는 안 됩니다. 클래스의 이름은 무엇을 하는지가 아니라 무엇인지에 기반해야합니다.

🐌 잘 지은 이름

class Cash {
	private int dollars;
    
    Cash(int dlr) {
    	this.dollars = dlr;
    }
    
    public String usd() {
    	return String.format("$ %d", this.dollars);
    }
}
  • 객체는 속성이 아닌 역량으로 특징지어져야합니다. What I can do

  • CashFormatter 가 아니라 -> Cash, USDCash, CashInUSD

  • format() 가 아니라 -> usd()

🦔 -er 숨겨진 악마

  • -er로 지어진 이름은 잘못 지어진 이름입니다.
  • 예외 : 오랜시간이 흐르면서 의미가 정착된 경우 ex) computer, user 등
  • 객체는 객체의 외부 세계와 내부 세계를 이어주는 연결장치가 아닙니다.
  • 객체는 내부에 캡슐화된 데이터를 다루기 위해 요청할 수 있는 절차의 집합이 아닌 캡슐화된 데이터의 대표자 입니다.

대표자?

  • 스스로 결정을 내리고 행동할 수 있는 자립적인 엔티티입니다.

💫 어떻게 지어야 하는가?

  • 클래스의 객체들이 무엇을 캡슐화할 것인지를 관찰하고 이 요소들에 붙일 적합한 이름을 찾아야 합니다.
  • 오직 소수만으로 구성된 리스트를 얻는 것이 목적이라면 ?
    Primer PrimeFinder PrimeChooser PrimeHelper 로 지으면 안 됩니다.
  • PrimeNumbers 라고 지어야 합니다.
  • 지금은 내가 바로 그 목록이야 ! 라고 이야기 해야합니다.

좋지 않은 클래스 이름은 Util이나 Utils로 끝나는 경우입니다. ?

util이나 utils를 사용해왔는데 이게 잘못된 이름이라는게 좀 놀랐어요. 이외에도 -er 이 들어간 우리가 당연하다는 듯이 사용했던 class 이름들도요. 해당 상황에서는 또 어떤 이름을 사용해할까 고민이 되었습니다. 보통 util로 사용하는 메서드들을 모아두는 클래스고 객체를 따로 만들지 않는게 보통인데 이 친구를 굳이 네이밍에 맞춰줘야할까 라는생각도 드네요

책에서 표시한 3.2.3 을 먼저 읽어봤습니다. 잘 알려진 유틸리티 클래스의 예제로 java.lang.Math 가 있습니다. 유틸리티 클래스는 어떤 것의 팩토리가 아니기 때문에 진짜 클래스라고 부를 수 없다고 하네요. 네이밍을 떠나서 정적 메서드의 단점을 몇배로 증폭시키고 안티 패턴이라고 가까이 하지마라고 하네요

1.2 생성자 하나를 주 생성자로 만드세요

  • 생성자는 새로운 객체에 대한 진입점으로 몇개의 인자들을 전달받아, 어떤 일을 수행한 후 , 임무를 수행할 수 있도록 객체를 준비시킵니다.
class Cash {
	private int dollars;
    
    Cash(int dlr) {
    	this.dollars = dlr;
    }
}
  • 위 코드는 하나의 생성자가 존재하고 이 생성자가 수행하는 하나의 작업은 인자로 전달된 달러를 dollars라는 이름의 private 정수 프로퍼티에 캡슐화하는 일입니다.
  • 클래스에는 2~3개의 메서드와 5~10개의 생성자를 포함하는 것이 적당합니다.
    => 과학적인 근거는 없으며 임의로 정했을 뿐입니다.
  • 핵심은 응집도가 높고 견고한 클래스에는 적은 수의 메서드와 상대적으로 더 많은 수의 생성자가 존재한다는 점입니다.

🧸 생성자가 여러 개 일 경우?

  • 유연성이 향상됩니다.
  • 중복 코드가 줄어듭니다.

🐢 주생성자, 부생성자

  • 초기화 하는 로직을 단 하나의 생성자에만 위치 시키자 -> 주생성자
  • 다른 생성자들이 주생성자를 호출하도록 만들어라 -> 부생성자
class Cash {
	private int dollars;
    
    //부생성자
    Cash(float dlr) {
    	this((int) dlr);
    }
    
    //부생성자
    Cash(String dlr) {
		this(Cash.parse(dlr));
	}
    
    //주생성자
    Cash(int dlr) {
		this.dollars = dlr;
    }
}
  • 주생성자를 부생성자 뒤에 위치시키는 이유는 유지 보수성 때문입니다.
    보통 주 생성자를 제일 앞에 뒀는데 제일 뒤에 두는게 편하다고 생각할 수도 있다는게 의외였습니다. 저는 그래도 제일 앞에 둘거에용

  • 주생성자는 인자로 전달된 정수를 이용해서 this.dollars를 초기화합니다. 부생성자는 정수형이 아닌 인자를 전달받아 파싱하거나 변환한 후 주 생성자로 전달할 정수형 인자를 준비합니다.

  • dollars의 값이 항상 양수여야한다고 가정을 하면 위의 코드는 한 곳에만 유효성 검사 로직을 추가하면 됩니다.

    주 생성자는 초기화만 해야합니다 ! 부생성자에서 인자를 준비하고, 포맷팅하고, 파싱하고 변환만 해야합니다.이를 통해(생성자 오버로딩) 코드의 복잡성을 줄이고 중복을 제거할 수 있습니다.

1.3 생성자에 코드를 넣지 마세요

<첫 번째 예제>

class Cash {
	private int dollars;
    
    Cash(String dlr) {
		this.dollars = Integer.parseInt(dlr):
    }
}
  • 위의 코드는 객체를 초기화하는 동안 생성자에 전달된 인자에 접근하고 있습니다.
  • 객체 초기화에는 코드가 없어야 하고 인자를 건드려서는 안 됩니다.
  • 대신 필요하다면 인자들을 다른 타입의 객체로 감싸거나 가공하지 않은 형식으로 캡슐화 해야합니다.

<두 번째 예제>

class Cash{
	private Number dollars;
    
    Cash(String dlr) {
		this.dollars = new StringAsInteger(dlr):
    }
}

class StringAsInteger implements Number {
	private String source;
    
    StringAsInteger(String src) {
    	this.source = src;
    }
    
    int intValue() {
    	return Integer.parseInt(this.source);
    }
}
  • 첫 번째 예제에서는 객체를 초기화하는 시점에 텍스트를 숫자로 변환하지만, 두 번째 예제에서는 실제로 사용하는 시점까지 객체의 변환 작업을 연기합니다.
  • 진정한 객체지향에서 인스턴스화란 더 작은 객체들을 조합해서 더 큰 객체를 만드는 것을 의미합니다.
    => 조합해야 하는 이유는 새로운 계약을 준수하는 새로운 엔티티가 필요하기 때문입니다.
  • 인자를 전달된 상태 그대로 캡슐화하고 나중에 요청이 있을 때 파싱하도록 하면, 클래스의 사용자들이 파싱 시점을 자유롭게 결정할 수 있게 됩니다.
  • 파싱이 여러 번 수행되지 않도록 하고 싶다면 데코레이터를 추가해서 최초의 파싱 결과를 캐싱할 수도 있습니다.

0개의 댓글