10장 클래스
1. 📌 핵심 개념 정리
✅ 요약하기
1. 클래스 체계
-
클래스를 정의하는 표준 자바 관례에 따르면, 가장 먼저 변수 목록이 나온다.
-
정적 공개 상수가 있다면 맨 처음에 나온다.
-
다음으로 정적 비공개 변수가 나오며, 이어서 비공개 인스턴스 변수가 나온다.
-
공개 변수가 필요한 경우는 거의 없다.
-
변수 목록 다음에는 공개 함수가 나온다.
-
비공개 함수는 자신을 호출하는 공개 함수 직후에 넣는다. 즉, 추상화 단계가 순차적으로 내려간다.
-
프로그램은 신문기사처럼 읽힌다.
-
캡슐화
- 클래스 내 선언된 변수, 함수 등은 외부에서 접근 못하게 private 선언으로 숨기는 것을 의미한다.
- 변수와 유틸리티 함수는 가능한 공개하지 않는 편이 낫지만 반드시 숨겨야 한다는 법칙도 없다.
- 때로는 변수나 유틸리티 함수를 protected로 선언해 테스트 코드에 접근을 허용하기도 한다.
- 같은 패키지 안에서 테스트 코드가 함수를 호출하거나 변수를 사용해야 한다면 그 함수나 변수를 protected로 선언하거나 패키지 전체로 공개한다.
- 하지만 그 전에 비공개 상태를 유지할 온갖 방법을 강구한다. 캡슐화를 풀어주는 결정은 언제나 최후의 수단이다.
2. 클래스는 작아야 한다!
-
클래스 설계의 기본 원칙
- 클래스는 작아야 한다.
- 클래스를 설계할 때도 함수와 마찬가지로 "작게"가 기본 규칙이다.
-
클래스 크기의 척도: 책임(Responsibility)
- 클래스가 맡은 책임을 세어보라.
- 클래스 이름은 해당 클래스의 책임을 명확히 설명해야 한다.
- 간결한 이름이 떠오르지 않는다면, 클래스가 너무 크다는 신호다.
- 클래스 설명은
if
,and
,or
,but
없이 25단어 내외로 작성해야 한다. - 클래스 이름이 모호하다면, 클래스의 책임이 너무 많다는 뜻이다.
-
모호한 클래스 이름이 의미하는 것
- 클래스 이름에
Processor
,Manager
,Super
같은 애매한 단어가 포함되었다면,- 여러 책임을 떠안고 있다는 증거다.
- 적은 수의 거대한 클래스보다, 많은 수의 작은 클래스를 지향하라.
- 클래스 이름에
-
작은 클래스가 많은 시스템 vs. 큰 클래스 몇 개뿐인 시스템
- 두 시스템 모두 돌아가는 부품의 개수는 비슷하다.
- 하지만 규모가 커질수록 논리와 복잡성이 증가한다.
- 복잡성을 다루려면 체계적인 정리가 필수다.
- 그래야 개발자가 필요한 부분을 빠르게 찾을 수 있다.
- 그래야 변경 시 직접 영향이 미치는 컴포넌트만 이해하면 된다.
- 거대한 다목적 클래스 몇 개로 구성된 시스템은 변경이 어려워진다.
- 불필요한 정보까지 이해해야 하고, 코드 분석이 더욱 힘들어진다.
- 결론: 작은 클래스를 많이 만들어 체계적으로 구성하라!
3. 단일 책임 원칙 (SRP)
- 클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야 한다는 원칙이다.
- SRP는 책임이라는 개념을 정의하며 적절한 클래스 크기를 제시한다.
- 클래스는 책임, 즉 변경할 이유가 하나여야 한다는 의미다.
- 객체 지향 설계에서 가장 중요한 규칙이지만, 가장 무시되는 규칙 중 하나이다.
- 책임, 즉 변경할 이유를 파악하려 애쓰다 보면 코드를 추상화하기도 쉬워진다. 더 좋은 추상화가 더 쉽게 떠오른다.
- 작은 클래스 많이 두기
- 큰 서랍 몇 개에 모두 던져 넣기보다는 작은 서랍 많이 두고 쓸모에 맞게 정리하여 넣기.
4. 응집도 (Cohesion)
-
클래스와 응집도
- 클래스는 인스턴스 변수 수가 적어야 한다.
- 각 메서드는 클래스의 인스턴스 변수를 하나 이상 사용해야 한다.
- 일반적으로 메서드가 변수를 더 많이 사용할수록 응집도가 높아진다.
- 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 최고로 응집도가 높은 클래스다.
-
응집도가 높은 클래스의 중요성
- 응집도가 높다는 것은 클래스의 메서드와 변수가 서로 밀접하게 연관된다는 의미다.
- 응집도가 높을수록 클래스는 논리적인 단위로 깔끔하게 정리된다.
- 하지만 너무 높은 응집도는 현실적으로 구현하기 어렵고, 바람직하지 않을 수도 있다.
-
클래스를 쪼개야 하는 신호
- 작은 함수와 짧은 매개변수 목록을 유지하려다 보면
- 특정 메서드에서만 사용하는 인스턴스 변수가 많아지는 경우가 있다.
- 이는 새로운 클래스로 분리해야 한다는 신호다.
- 응집도를 유지하려면, 관련된 변수와 메서드를 적절히 분리해 새로운 클래스로 나눈다.
- 작은 함수와 짧은 매개변수 목록을 유지하려다 보면
-
큰 함수를 작은 함수로 나눌 때의 문제
- 예를 들어, 여러 변수를 사용하는 큰 함수가 있다고 가정하자.
- 일부 로직을 새로운 함수로 분리하고 싶지만, 4개의 변수를 사용하고 있다.
- 이 4개의 변수를 새로운 함수의 매개변수로 넘겨야 할까?
- 아니다! 대신 이 변수를 클래스의 인스턴스 변수로 승격하면 매개변수 없이 사용할 수 있다.
- 하지만 이렇게 하면 클래스의 응집력이 낮아질 위험이 있다.
-
응집력을 유지하는 방법
- 클래스가 응집력을 잃는다면 쪼개라!
- 큰 함수를 작은 함수 여러 개로 나누다 보면 작은 클래스로도 쪼갤 기회가 생긴다.
- 그 결과, 프로그램 구조가 더 체계적이고 가독성이 높아진다.
5. 변경하기 쉬운 클래스
-
대다수 시스템은 지속적인 변경이 가해진다. 그리고 뭔가 변경할 때마다 시스템이 의도대로 동작하지 않을 위험이 따른다. 깨끗한 시스템은 클래스를 체계적으로 정리해 변경에 수반하는 위험을 낮춘다.
-
코드 수정 시 건드릴 코드가 최소인 시스템 구조가 바람직하다.
-
변경으로부터 격리
- 요구사항은 변하기 마련이고, 이에 따라 코드도 변한다.
- 상세한 구현에 의존하는 클라이언트 클래스는 구현이 바뀌면 위험에 빠진다.
- 그래서 우리는 인터페이스와 추상 클래스를 사용해 구현이 미치는 영향을 격리한다.
- 상세한 구현에 의존하는 코드는 테스트가 어렵다.
- 테스트가 가능할 정도로 시스템의 결합도를 낮추면 유연성과 재사용성도 더욱 높아진다.
- 결합도가 낮다는 소리는 각 시스템 요소가 다른 요소로부터 그리고 변경으로부터 잘 격리되어 있다는 의미다. 시스템 요소가 서로 잘 격리되어 있으면 각 요소를 이해하기도 더 쉬워진다.
- 결합도를 최소로 줄이면 자연스럽게 DIP(의존성 역전 원칙)를 따르는 클래스가 나온다. 본질적으로 DIP는 클래스가 상세 구현이 아닌 추상화에 의존해야 한다는 원칙이다.
2. 🤔 이해가 어려운 부분
🔍 질문하기
1. 결합도
- 어려웠던 부분: 결합도란 무엇을 의미하고 결합도를 낮추는 예시 살펴보기
- 이해한 점
- 결합도(Coupling)는 한 모듈(클래스, 함수)이 다른 모듈과 얼마나 강하게 연결되어 있는지를 나타내는 개념입니다.
- 높은 결합도 (High Coupling): 한 모듈이 다른 모듈의 내부 구현에 직접 의존하는 경우, 변경이 어렵고 유지보수 비용이 증가합니다.
class PayPalPaymentProcessor { void processPayment(double amount) { System.out.println("Processing payment of $" + amount + " via PayPal"); } } class OrderService { private PayPalPaymentProcessor paymentProcessor; public OrderService() { this.paymentProcessor = new PayPalPaymentProcessor(); // 직접 의존 } public void checkout(double amount) { paymentProcessor.processPayment(amount); } }
- 낮은 결합도 (Low Coupling): 한 모듈이 다른 모듈과 독립적으로 동작할 수 있도록 인터페이스 또는 추상화 계층을 두는 방식으로 결합을 줄입니다.
// 추상화 계층 추가 interface PaymentProcessor { void processPayment(double amount); } // PayPal 구현 class PayPalPaymentProcessor implements PaymentProcessor { @Override public void processPayment(double amount) { System.out.println("Processing payment of $" + amount + " via PayPal"); } } // Stripe 구현 class StripePaymentProcessor implements PaymentProcessor { @Override public void processPayment(double amount) { System.out.println("Processing payment of $" + amount + " via Stripe"); } } // OrderService는 구체적 클래스가 아니라 인터페이스에 의존 class OrderService { private final PaymentProcessor paymentProcessor; public OrderService(PaymentProcessor paymentProcessor) { this.paymentProcessor = paymentProcessor; // 인터페이스에 의존 } public void checkout(double amount) { paymentProcessor.processPayment(amount); } }
- 높은 결합도 (High Coupling): 한 모듈이 다른 모듈의 내부 구현에 직접 의존하는 경우, 변경이 어렵고 유지보수 비용이 증가합니다.
- 결합도(Coupling)는 한 모듈(클래스, 함수)이 다른 모듈과 얼마나 강하게 연결되어 있는지를 나타내는 개념입니다.
2. 변경으로부터 격리
- 어려웠던 부분: 책에서 말하는 구체적인 클래스에서 상세한 구현에 의존하는 클래스가 어떤 클래스를 말하는 건지 이해하기 어려웠다.
- 이해한 점: 인터페이스나 추상 클래스없이 작성한 클래스를 구체적인(Concrete) 클래스라고 말한다.
- 예시
이 경우public class Driver { private Car car = new Car(); }
Car
의 동작이 바뀌면Driver
에도 영향을 미친다. - 따라서 다음과 같이 코드를 개선한다:
public interface Vehicle { void drive(); } public class Car implements Vehicle { @Override public void drive() { System.out.println("자동차를 운전한다."); } } public class Driver { private Vehicle vehicle; public Driver(Vehicle vehicle) { this.vehicle = vehicle; } public void drive() { vehicle.drive(); } }
- 예시
3. 응집도를 판단하는 기준
- 응집도(Cohesion)를 판단 기준
- 모듈 내부의 기능들이 얼마나 밀접하게 관련되어 있는가?
- 변경의 이유 서로 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐 있는가?
- 모듈이 명확하고 일관된 책임을 가지고 있는가?
- 메서드 수준, 함수 수준, 모듈 수준.
- 응집도와 대비되는 개념으로
결합도(Coupling)
가 있다. - 결합도는 소프트웨어 시스템에서 모듈 간의 상호 의존도를 측정한 것으로, 모듈이 밀접하게 연결되어 있는 정도를 나타낸다.
- 결합도가 높으면 변경하고 검토해야 하는 모듈 수가 많아지는 단점이 있다.
3. 📚 참고 사항
📢 논의하기
1. 관련 자료 공유
- 추가 자료
No Comments