https://link.coupang.com/a/37DdD
↑ 정말 재밌게 봤고 좋은 책
이 책에서는 클래스 기반 객체 지향 설계의 전반을 다룬다.
클래스 기반이란? '데이터'와 '그 데이터를 조작하는 논리'를 클래스 단위로 묶어서 정의해 가며, 프로그램을 작성하는 방법
3.1 클래스 단위로 잘 동작하도록 설계하기
가장 중요한 것은 클래스 단위로도 잘 동작하게 설계해야 한다.
전자제품 예를 들면 헤어드라이기는 그 자체로 사용할 수 있다 .(다른 제품과 결합해야만 사용할 수 있지않는다)
그리고 드라이어를 조작하는 기능들을 제공한다. 전원 on/off, 바람 세기 등
클래스도 마찬가지로 복잡한 초기 설정 없어도 사용할 수 있게 만들어야 하며, 외부에서 클래스를 마음대로 조작해서 클래스가 고장 나는 일 없게 최소한의 조작 방법만 외부에 제공해야 한다.
3.1.1 클래스의 구성 요소
잘 만들어진 클래스는 아래 두 가지로 구성되어야 한다.
- 인스턴스 변수
- 인스턴수 변수에 잘못된 값이 할당되지 않게 막고, 정상적으로 조작하는 메서드
데이터 클래스에서 일어나는 폐해 -> 데이터 클래스는 해당 데이터의 인스턴스 변수를 조작하는 로직이 다른 클래스에 구현되어 있고, 그로 인해 데이터와 로직의 연관성을 다른 사람이 알아채기 어려워서 코드가 중복되어 구현될 수 있고, 수정 발생 시 중복된 부분의 수정을 놓칠 수 있다.
3.1.2 모든 클래스가 갖추어야 하는 자기 방어 의무
다른 클래스를 사용해서 초기화와 유효성 검사를 해야 하는 클래스는 그 자체로는 안전하게 사용할 수 없는 미성숙한 클래스이다.
클래스는 스스로 자기 방어 임무(혼자서 초기화와 유효성 검사 가능해야 함)를 수행할 수 있어야 소프트웨어의 품질을 높이는 데 도움이 된다.
3.2 성숙한 클래스로 성장시키는 설계 기법
class Money{
int amount;
Currency currency;
}
3.2.1 생성자로 확실하게 정상적인 값 설정하기
로우 데이터 객체(raw data object)를 유발하는 매개 변수 없는 생성자 사용되면 안됨.
클래스 인스턴스를 생성하는 시점에 확실하게 인스턴스 변수가 정상적인 값을 갖게 만들면 된다.
즉, 적절한 초기화 로직을 생성자에 구현하면 된다.
class Money{
int amount;
Currency currency;
Money(int amount, Currency currency){
this.amount = amount;
this.currency = currency;
}
}
위 처럼 생성자를 만들면 인스턴스 생성 시점에 변수가 무조건 초기화는 된다.
하지만 현재 상태에서는 new Money(-1, null) 처럼 잘못된 매개변수가 전달될 수도 있다.
잘못된 값이 들어간 인스턴스가 생성되지 못하게 생성자에서 유효성 검사를 정의한다.
class Money{
int amount;
Currency currency;
Money(int amount, Currency currency){
if (amount < 0 ) throw new ...Exception();
if (currency == null ) throw new ...Exception();
this.amount = amount;
this.currency = currency;
}
}
3.2.2 계산 로직도 데이터를 가진 쪽에 구현한다.
데이터와 데이터를 조작하는 로직이 함께 있는 구조가 응집도가 높은 구조이다.
따라서 인스턴스 변수를 조작하는 로직은 같은 클래스에 넣는다.
class Money{
// ...
void add(int other){
amount += other;
}
}
3.2.3 불변 변수로 만들어서 예상하지 못한 동작 막기
생성된 하나의 인스턴스의 인스턴스 변수를 여러번 할당하면서 사용하면 지금 할당된 값이 무엇인지 계속 신경 써야 하고, 예상하지 못한 부수 효과가 쉽게 발생할 수 있다고 한다. (아래 예시 코드만으로는 인스턴스 변수를 바꾸는거나, 인스턴스 변수를 불변으로 만들어서 매번 새로운 Money() 클래스를 만드는거나 어떤 차이(장점)가 있는지는 정확하게는 모르겠긴 함)
money.amount = originalPrice;
// 생략
if (specialServiceAdded){
money.add(additionalServiceFee);
////
if (seasonOffApplied){
money.amount = seasonPrice();
}
}
class Money{
final int amount;
final Currency currency;
Money(int amount, Currency currency){
this.amount = amount;
this.currency = currency;
}
}
3.2.4 변경하고 싶다면 새로운 인스턴스 만들기
인스턴스 변수의 내용을 변경하는 것이 아니라, 변경된 값을 가진 인스턴스를 만들어서 사용하면 된다.
class Money{
// ...
void add(int other){
int added = amount + other;
return new Money(added, currency);
}
}
3.2.5 메서드 매개변수와 지역 변수도 불변으로 만들기
메서드와 생성자의 매개변수, 지역변수도 중간에 값을 변경하면 값의 의미가 바뀔 수 있기 때문에 final 붙여서 불변으로 만든다.
class Money{
// ...
void add(final int other){
final int added = amount + other;
return new Money(added, currency);
}
}
3.2.6 엉뚱한 값을 전달하지 않도록 하기
-> 이 예시에서 말하고자 하는 것은 기본 자료형을 사용하면 사람이 실수로 다른 메서드 호출하거나 할 때 잘못된 변수를 넘길 수 있다는 것이다. (같은 int형 이기에 가능하고, 컴파일 에러도 발생하지 않음)
이런 경우는 기본 자료형을 사용하는 것이 아니라 사용자가 만든 값 객체 자료형을 사용하면, 누군가가 실수로 의미가 다른 값을 전달할 때 컴파일 오류가 발생하여 실수를 사전에 막을 수 있다.
class Money{
// ...
void add(final Money other){
if (!currency.equals(other.currency)) throw new ...Exception("통화 단위가 다릅니다.");
final int added = amount + other;
return new Money(added, currency);
}
}
3.3 악마 퇴치 효과 검토하기
퇴치된 악마
- 중복 코드
- 수정 누락
- 가독성 저하
- 쓰레기 객체
- 잘못된 값
- 생각하지 못한 부수 효과
- 값 전달 실수
인스턴스 변수를 중심으로, 인스턴스 변수가 잘못된 상태에 빠지지 않게 설계하면 악마를 퇴치할 수 있다.
클래스 설계란 인스턴스 변수가 잘못된 상태에 빠지지 않게 하기 위한 구조를 만드는 것이라고 해도 과언이 아니다.
3.4
3.4.1 완전 생성자
인스턴스 변수가 생성될 때 인스턴스 변수 전부 정상적인 값으로 초기화되도록 하는 패턴
3.4.2 값 객체
값을 클래스(자료형)로 나타내는 디자인 패턴.
애플리케이션에서 사용되는 금액, 날짜, 주문 수, 전화번호 등 다양한 값을 값 객체로 만들 수 있다.
값 객체로 만들어서 사용하면 각각의 값과 로직을 응집도가 높은 구조로 만들 수 있다.
예를 들어 금액을 단순한 int 자료형의 지역 변수 또는 매개변수로 사용하면, 금액 계산 로직이 이곳저곳 분산될 것이다.
즉, 응집도가 낮은 구조가 되는 것이다.
'책 내용 정리 > [책] 내 코드가 그렇게 이상한가요?' 카테고리의 다른 글
12장 메서드: 좋은 클래스에는 좋은 메서드가 있다. (0) | 2023.07.28 |
---|---|
7장 컬렉션: 중첩을 제거하는 구조화 테크닉 (0) | 2023.07.27 |
6장 조건 분기: 미궁처럼 복잡한 분기 처리를 무너뜨리는 방법 (0) | 2023.07.23 |
5장 응집도: 흩어져 있는 것들 (0) | 2023.07.18 |
4장 불변 활용하기: 안정적으로 만들기 (0) | 2023.07.17 |
댓글