노션에서 정리한 내용을 벨로그로 옮겼기 때문에 노션으로 보면 조금 더 보기 더 편합니다🤗
이동하기 → junnkk's Notion
클래스를 정의하는 표준 자바 관례에 따른 순서
⇒ 추상화 단계가 순차적으로 내려감( like 신문 기사)
캡슐화
변수와 유틸리티 함수 → 공개하지 않는 편이 좋지만 반드시는 아님.
테스트 코드가 함수를 호출하거나 변수를 사용해야 할 때 protected로 선언하거나 패키지를 전체 공개 하기도 하지만 그 전에 비공개 유지할 방법 강구
⇒ 캡슐화를 풀어주는 결정은 최후의 수단
클래스는 작아야 한다 → 맡은 책임 개수 ↓
클래스 이름은 해당 클래스의 책임을 기술해야한다.
→ 간결하지 못한 이름 = 너무 많은 책임
→ 모호한 이름 ex) Processor, Manager, Super 등 = 여러 책임을 가짐
클래스 설명은 if, and, or, but 없이 25 단어 내외
단일 책임 원칙(SRP)
: 클래스나 모듈을 변경할 이유(책임)가 단 하나 뿐이어야 한다.
예시)
p175~176 [10-2]
→ 변경할 이유 1. 소프트웨어가 출시할 때마다 달라지는 소프트웨어 버전 정보 추적
→ 변경할 이유 2. 자바 스윙 컴포넌트 관리. 즉, 스윙 코드 변경 시 버전 정보 변화
p76 [10-3] 10-2에서 버전 정보를 다루는 메소드를 빼낸 독자적인 단일 책임 클래스
→ 재사용 용이
⇒ 예시처럼 책임(변경할 이유)을 파악하려 하면 추상화가 쉬워짐.
규모가 있는 복잡한 시스템을 다루려면 체계적인 정리 필수.
→ 위치 파악이 용이해짐
→ 변경 시 직접 영향이 미치는 컴포넌트만 이해해도 됨.
⇒ 큰 클래스보다 작은 클래스 여럿이 더 바람직 함
∵ 작은 클래스는 각자 맡은 책임이 하나이며 변경할 이유가 없으며, 다른 작은 클래스와 협력해 시스템에 필요한 동작을 수행.
응집도
응집도가 높다 = 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다
클래스는 인스턴스 변수 수가 작아야 한다.
각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다.
→ 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 ↑
→ but 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높지만 불가능하고 바람직 X
'함수를 작게, 매개변수 목록을 짧게' 하면 몇몇 메서드만이 사용하는 인스턴스 변수 개수 ↑(응집도 낮아짐을 의미)
⇒ 응집도가 높아지도록 변수와 메서드를 적절히 분리해 새로운 클래스 두세 개로 쪼갠다.
예시) p178 응집도가 높은 클래스
public class Stack {
private int topOfStack = 0; // 인스턴스 변수
List<Integer> elements = new LinkedList<Integer>(); // 인스턴스 변수
public int size() {
return topOfStack; // 클래스 인스턴스 변수 topOfStack 사용
}
public void push(int element) {
topOfStack++; // 클래스 인스턴스 변수 topOfStack 사용
elements.add(element); // 클래스 인스턴스 변수 elements 사용
}
public int pop() throws PoppedWhenEmpty {
if (topOfStack == 0) // 클래스 인스턴스 변수 topOfStack 사용
throw new PoppedWhenEmpty();
int element = elements.get(--topOfStack); // 클래스 인스턴스 변수 elements 사용
elements.remove(topOfStack);
return element;
}
}
응집도를 유지하면 작은 클래스 여럿이 나온다
큰 함수를 작은 함수로 쪼개다 클래스가 응집력을 잃는다면 클래스를 쪼개야 한다.
예시) p179~184 [10-5] → [10-6]~[10-8] 리팩터링
→ 프로그램의 길이가 길어진 이유
원래 프로그램 ⇒ 세 가지 책임(PrimePrinter, PowColumnPagePrinter, PrimeGenerator)
원래 프로그램을 수정한 과정(리팩터링)
예시)
p186 [10-9] 변경이 필요한 클래스. 주어진 메타 자료로 적절한 SQL 문자열을 만드는 Sql 클래스
→ 변경할 이유(책임)가 두 가지(SRP 위반)이므로 수정이 필요하다.
p187~188 [10-10] 10-9를 개선한 닫힌 클래스 집합
→ 공개 인터페이스를 각각 Sql 클래스에서 파생하는 클래스로 만듦.
→ 비공개 메서드는 해당하는 파생 클래스로 이동 ex) valueList
→ 모든 파생 클래스가 공통으로 사용하는 비공개 메서드는 Where과 ColumnList라는 두 유틸리티 클래스로 이동
⇒ 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직. 새 기능 추가 시 시스템 확장 but 기존 코드 변경 X
변경으로부터 격리
구체적인 클래스 Concrete Class - 상세한 구현(코드) 포함
추상 클래스 Abstract Class - 개념만 포함
→ 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험하므로 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다.
예시) p189~190 Portfolio
→ 5분 마다 값이 달라지는 API를 사용 구체적인 클래스를 사용하는 대신 테스트용 클래스는 stockExchange 인터페이스를 구현하며 고정된 주가를 반환하는 추상적인 클래스를 사용
⇒ 변경을 포함하는 구체적인 클래스 대신 추상 클래스로 테스트
⇒ 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성이 높아짐.
( 결합도가 낮다 = 각 시스템의 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다
→ 각 요소도 이해하기 쉽다. )
⇒ 결합도를 최소로 줄이면 자연스럽게 DIP(클래스가 상세한 구현이 아니라 추상화에 의존해야 한다는 클래스 설계 원칙)을 따르는 클래스가 나온다.