정보처리기사를 공부할 때, 객체지향의 3요소 객체, 클래스, 메시지를 외웠다.
캡슐화, 추상화, 상속, 다형성같은 객체 지향의 특징도 달달달 외웠다.
Java 강의를 통해 객체지향적으로 코드를 짜려면 인터페이스를 사용해야한다는 것도 배웠다.
인풋이 있으면 그만큼 아웃풋도 있어야 하는데, 나는 과연 배운 만큼 제대로 아웃풋을 내왔을까?🤔
이 책이 이론을 실무에 녹여낼 수 있는 징검다리가 되주길 바라며 차근히 완독해봐야겠다.
이 책의 목차는 이렇게 이뤄져있다.
01 .협력하는 객체들의 공동체
02 .이상한 나라의 객체
03 .타입과 추상화
04 .역할, 책임, 협력
05 .책임과 메시지
06 .객체 지도
07 .함께 모으기
이 목차만 봤을 때는 아직 감이 잡히지 않는다.
책 앞단에 이 책은 1장부터 7장까지 유기적인 흐름으로 연결되어있으니 순차적으로 읽는 것을 권장하고 있다.
저자의 추천대로 앞장부터 차례대로 흐름을 따라가보려한다.
2024년은 '객체 지향의 사실과 오해'와 함께 마무리 지어야겠다.
1. 객체지향의 본질
손님이 커피를 주문하고 커피를 받기까지 과정에서 객체지향의 본질을 파악할 수 있다.
키워드는 역할, 책임, 협력이다.
여기서 손님, 캐셔, 바리스타는 각자의 역할을 한다.
손님은 커피를 주문하고 캐셔는 주문을 바리스타에게 전달하고 바리스타는 커피를 만든다.
바리스타가 커피를 만들어서 캐셔에게 전달하면 캐셔는 손님에게 커피 완성을 알리고 손님은 커피를 받아간다.
손님은 커피를 주문할 책임, 캐셔는 주문을 받고 전달할 책임, 바리스타는 커피를 제조할 책임이 있다.
각 역할마다 책임이 있다. 역할은 책임이라는 개념을 가지고 있다.
손님이 커피를 주문하고 커피를 받는 이 단순해보이는 과정도 역할을 맡은 사람들이 책임을 다하며 협력했기 때문에 가능한 일이다.
아침에 출근하고 나면 카페인풀이라는 카페에 가서 따뜻한 카페라떼 한잔을 사는 것이 내 아침 루틴이다.
카페인풀은 주문받는 사람이 커피도 제조한다.
이처럼 한 사람이 두 가지의 역할도 할 수 있다.
객체지향 세계에서 사람은 '객체'다.
커피를 주문하는 것(요청)은 '메시지'다.
바리스타가 아메리카노를 고소하게 만들지, 산미있게 만들지 자신의 방식대로 만들 수 있다. 이건 '메서드'다.
동일한 주문이지만 다르게 주문을 처리할 수 있다. 이것이 '다형성'이다.
손님은 캐셔가 누구인지 바리스타가 누군지 상관없이 주문을 하고 원하는 커피를 받아가면 된다.
캐셔도 마찬가지로 손님이 누구인지 상관없다.
바리스타도 물론 주문을 캐셔에게 전달받든 키오스크를 통해 전달받든 커피만 잘 만들면 된다.
역할은 대체 가능하다는 것이다.
손님은 키오스크로 주문할 수도 있고 캐셔에게 직접 주문할 수 있다.
캐셔는 바리스타에게 주문 내역을 말로 전달할 수도 메모지에 적어서 전달할 수도 있다.
역할을 수행하는 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있다.
객체는 스스로 판단하고 결정한다.
손님은 커피를 주문할 뿐 바리스타가 커피를 '어떻게' 만들 지에 대해서는 간섭할 수 없다.
바리스타가 커피를 어떻게 만들지는 바리스타의 사적인 부분이다.
커피 머신은 바리스타만 사용할 수 있다.
객체는 접근이 허락된 수단을 통해서만 의사소통 할 수 있다.
이 개념이 바로 '캡슐화'다.
위 개념들을 이해하기 쉽게 코드를 봐보자.
✅인터페이스 정의
역할에 따라 객체의 행동을 추상화한다.
'어떤' 책임을 수행할 지를 정의하며 '어떻게' 수행할지는 정의하지 않는다.
// 주문을 처리하는 인터페이스
public interface OrderHandler {
String handleOrder(String order); // 주문 처리 메서드
}
// 커피를 만드는 인터페이스
public interface CoffeeMaker {
String makeCoffee(String order); // 커피 제조 메서드
}
✅바리스타
바리스타는 커피를 만드는 인터페이스의 커피 제조 메서드를 구체적으로 구현한다.
여기서 '어떻게' 만들지를 정한다.
커피머신은 private 접근제어자로 바리스타만 사용할 수 있다.
public class Barista implements CoffeeMaker {
private String coffeeMachine = "신상 커피머신"; // 캡슐화된 상태
// 커피 제조: 바리스타는 자신의 방식으로 커피를 만듦
@Override
public String makeCoffee(String order) {
System.out.println("바리스타: " + order + " 주문 확인. " + coffeeMachine + "으로 제조.");
// 바리스타의 방식에 따라 다르게 제조
if (order.equalsIgnoreCase("아메리카노")) {
return "고소한 " + order;
} else if (order.equalsIgnoreCase("라떼")) {
return "부드러운 " + order;
} else {
return "기본 " + order;
}
}
}
✅캐셔
캐셔는 바리스타 객체를 직접 생성하지 않고 생성자를 통해 객체를 주입받는다. (의존성 주입)
바리스타가 커피를 만들든 어느날 로봇으로 대체되든 캐셔는 자신의 역할을 할 수 있다. (유연성)
public class Cashier implements OrderHandler {
private CoffeeMaker barista; // 캐셔는 바리스타와 소통한다.
public Cashier(CoffeeMaker barista) { //CoffeeMaker가 커피를 어떻게 만들든 소통할 수 있다.
this.barista = barista;
}
@Override
public String handleOrder(String order) {
System.out.println("캐셔 -> 바리스타 :" + order + " 주문이요.");
String coffee = barista.makeCoffee(order); // 바리스타에게 커피 제조 요청
System.out.println("캐셔 -> 손님 : 주문하신" + coffee + "나왔습니다.");
return coffee;
}
}
✅키오스크
키오스크도 캐셔와 동일한 역할을 수행할 수 있다. (역할 대체 가능성)
public class Kiosk implements OrderHandler {
private CoffeeMaker barista; // 키오스크도 바리스타와 소통
public Kiosk(CoffeeMaker barista) {
this.barista = barista;
}
@Override
public String handleOrder(String order) {
System.out.println("키오스크 -> 바리스타 :" + order);
return barista.makeCoffee(order);
}
}
✅손님
손님은 캐셔 또는 키오스크를 통해 주문할 수 있다.
public class Customer {
private String nickName;
public Customer(String nickName) {
this.nickName = nickName;
}
// 행동: 캐셔를 통해 주문
public void orderCoffee(String coffee, OrderHandler orderHandler) {
System.out.println(nickName + " 손님: " + coffee + "주세요.");
String result = orderHandler.handleOrder(coffee); // 캐셔 또는 키오스크에게 주문
System.out.println(nickName + " 손님: " + result + "를 받고 기분이 좋아졌다!");
}
}
✅카페(주문 처리)
public class cafe {
public static void main(String[] args) {
// 바리스타 생성
CoffeeMaker barista = new Barista();
// 캐셔 생성
OrderHandler cashier = new Cashier(barista);
// 키오스크 생성
OrderHandler kiosk = new Kiosk(barista);
// 손님 생성
Customer customer1 = new Customer("인똥");
Customer customer2 = new Customer("미키");
// 손님이 캐셔에게 주문
customer1.orderCoffee("아메리카노", cashier);
// 손님이 키오스크를 통해 주문
customer2.orderCoffee("라떼", kiosk);
}
}
✅실행 결과
인똥 손님: 아메리카노 주세요.
캐셔 -> 바리스타 : 아메리카노 주문이요.
바리스타: 아메리카노 주문 확인. 신상 커피머신으로 제조.
캐셔 -> 손님 : 주문하신 고소한 아메리카노 나왔습니다.
인똥 손님: 고소한 아메리카노를 받고 기분이 좋아졌다!
미키 손님: 라떼 주세요.
키오스크 -> 바리스타 : 라떼
바리스타: 라떼 주문 확인. 신상 커피머신으로 제조.
미키 손님: 부드러운 라떼를 받고 기분이 좋아졌다!
객체지향의 본질은 역할, 책임 그리고 협력에 있다.
이렇게 손님, 캐셔, 바리스타는 각자의 역할을 맡아 책임을 수행한다.
서로 협력하여 손님이 커피를 받는 목표를 달성한다.
협력 과정에서 객체는 요청(메시지)을 주고 받으며 소통한다.
손님은 바리스타가 커피를 어떻게 제조하는지 간섭하지 않고 바리스타는 커피머신을 자신만 사용한다.
이것으로 캡슐화 원칙을 이해할 수 있다.
또한, 캐셔와 키오스크는 같은 역할을 수행할 수 있다. 이는 다형성 개념을 나타낸다.
각 객체는 내부 구현 방식(커피제조법 등)을 숨기고, 명확히 정의된 인터페이스를 통해 의사소통한다.
카페에서 손님이 커피를 주문하고 받을 때까지의 과정을 통해 객체지향의 본질을 자연스럽게 이해할 수 있다.
객체지향은 역할, 책임, 협력을 통해 시스템의 유연성과 확장성을 높이는 설계 방식이다.
2. 마무리
이전 구조 : Controller -> Service -> DAO -> DaoImpl -> xml
이후 구조 : Controller -> Service -> xml
이전에 세분화 되어있던 구조를 단순한 구조로 변경한 경험이 있다.
단순한 로직도 세분화된 로직을 타다 보니 코드 간의 의존성이 증가 했고 유지보수가 어려웠었다.
간단하게 수정이 생겨도 모든 계층을 수정해야하니 비효율적으로 느껴졌다.
하지만, 구조를 축소시키는 것이 단기간엔 유지보수성을 높일 수 있지만 완전한 해답은 아니었다.
왜냐하면 Service는 비즈니스 로직만 담당해야하는 데, XML 매퍼를 Service에서 직접 매핑하게 되면서 데이터 접근(DAO)의 역할까지 하게됐기 때문이다.
처음엔 단순화된 구조가 편하게 느껴질수 있어도 시간이 지나면서 코드가 점점 많아지고 복잡도가 증가하면 이 구조가 오히려 유지보수성을 떨어트릴 수 있다.
- 중복 코드 증가 : XML 매퍼를 여러 Service에서 직접 호출하는 경우.
- 테스트 환경 구축 어려움 : Service를 테스트하려면 DB와 연결해야함.
- 변경에 취약성 : XML 쿼리를 수정하거나 새로운 필드를 추가할 때, 관련된 모든 Service를 확인하고 수정해야함.
- 복잡도 증가 : 예상치 못한 요구사항이 추가됐을 때 새로운 로직과 데이터 처리 로직이 얽힐 수 있음.
물론, 프로젝트의 상황과 맥락에 따라 설계가 달라질 수 있기때문에 설계에 정답은 없다고 생각한다.
그렇지만 객체지향에 대한 이해가 더 있다면 상황에 따라 책임 분리와 복잡도의 균형을 맞출 수 있다.
🚩구호를 외치자.
역할! 책임 ! 협력!
'프로그래밍 언어 > Java' 카테고리의 다른 글
[IntelliJ] Live Templates 사용법 | 반복되는 코드 편하게 작성하기 (0) | 2024.08.08 |
---|---|
[JSP] 게시판 만들기 | 5편.게시판 기능 구현하기 (1) | 2024.02.07 |
[JSP] 게시판 만들기 | 4편.회원가입 기능 구현하기 / 로그아웃 (1) | 2024.02.07 |
[JSP] 게시판 만들기 | 3편.로그인 기능 구현하기 (0) | 2024.02.07 |
[JSP] 게시판 만들기 | 2편.데이터베이스 설치 및 연동 (1) | 2024.02.07 |