객체지향 설계와 도메인-주도 설계에 관심이 많으며 행복한 팀과 깔끔한 코드, 존중과 협력이 훌륭한 소프트웨어를 낳는다는 믿음을 가지고 있는 평범한 개발자입니다. 개발자, 교육자, 관리자를 오가며 익힌 다양한 경험을 바탕으로 좋은 코드와 함께 좋은 프로덕트를 만들기 위해 노력하고 있습니다.
저서로는 『객체지향의 사실과 오해』와 『오브젝트』가 있고 번역서로는 『엘레강트 오브젝트』가 있으며 『만들면서 배우는 클린 아키텍처』에 감수자로 참여했습니다.
💡개인블로그 : https://eternity-object.tistory.com/
講義
受講レビュー
- オブジェクト - 基礎
- オブジェクト - 設計原則編
投稿
Q&A
강의 자료 관련 질문입니다! (2-4. 절차에서 객체로)
테디베어님 안녕하세요.강의 자료를 보니 말씀하신 것처럼 findDiscountCondition 메서드의 가시성은 public으로 수정하는게 맞습니다.장표를 만들면서 실수로 그 부분까지는 확인을 못하고 넘어간 것 같네요.혹시 코드가 이상하다면 github에 올라가 있는 코드와 비교해 보시면 좋아요. 🙂https://github.com/eternity-oop/object-basic-02-04/blob/main/src/main/java/org/eternity/reservation/domain/DiscountPolicy.java동영상 부분이 수정하기 어려워서 고민인데 이렇게 질문 남겨 주시면 나중에라도 한번 모아서 오류 있는 부분을 수정하도록 하겠습니다. 꼼꼼하게 봐주셔서 감사합니다!
- 1
- 2
- 22
Q&A
도메인 관련 질문이 있습니다!
테디베어님 안녕하세요.죄송하지만 질문 내용을 제가 정확하게 이해할 수 없어서 정확한 답변을 드리기 위해 몇 가지 질문을 드려야 할 것 같습니다. Q1. "영화 관람료"라는 객체를 추가하고 상영 객체가 의존한다고 하셨는데 구체적으로 영화 관람료가 맡은 책임은 무엇이고, 상영이 영화 관람료에 어떤 방식으로 의존(또는 협력)하는 것일까요? 이 부분을 이해해야만 답변을 드릴 수 있는데 의사 코드 수준으로라도 적어주시면 이해하는데 도움이 될 것 같아요. Q2. "영화 관람료"를 추가해서 상영이 할인 정책과 협력하면 지금 방식보다 어떤 점이 개선된다고 생각하시나요? 이 방식이 현실과 더 밀접하다고 하셨는데 어떤 점에서 그런지도 적어 주시면 좋겠습니다. 위 내용을 적어주시면 적어주신 내용 기반으로 정확한 답변을 드릴 수 있을것 같아요! 🙂
- 1
- 2
- 34
Q&A
6-2 보호 로직 중복 이슈
mint.inhrdev님 안녕하세요. 다음 질문과 중복되어 답변 대신 링크로 대신합니다.https://inf.run/41yfx 답변 내용을 보시면 궁금하신 부분에 대해 명확하게 이해하실 수 있으실거에요. 🙂 질문 남겨 주셔서 감사하고 해당 답변을 읽으신 후에도 궁금한 부분이 있으면 추가로 질문 남겨주세요. 감사합니다!
- 1
- 2
- 29
Q&A
3-2 메서드를 얼마나 작게 나누는게 적절한가요?
mint.inhrdev님 안녕하세요.좋은 질문 남겨주셔서 감사합니다. 먼저 SRP라는 용어는 메서드가 아니라 클래스 또는 모듈 단위에서 사용하는 용어입니다.메서드 수준에서는 단순히 응집도라는 개념을 사용하고 SRP라는 용어를 사용하지 않습니다.기본적으로 SRP에서 말하는 개념과도 상관이 없고 강의에서도 조합 메서드 패턴이라고 표현하고 SRP라는 용어는 사용하지 않고 있습니다. 조합 메서드로 리팩터링할 때 변경을 고민하기도 하지만 변경만이 메서드를 나누는 절대적인 기준은 아닙니다.강의에서도 설명드렸던 것처럼 조합 메서드의 목적은 추상화를 동일한 수준으로 맞춰서 읽고 이해하기 쉽도록 만드는 것이기 때문에 주된 목적은 가독성을 향상시키는 것입나다.따라서 질문을 SRP vs 가독성이라는 표현 대신 가독성을 향상시키기 위해 메서드를 얼마나 작게 나누는게 적절한가로 바꿔서 표현하는게 좋겠습니다. 먼저 질문에서 말씀하신 아래 코드는 “입력 파싱”이 아니라 공백을 기준으로 “토큰을 분리”하는 작업이라는 점을 짚고 넘어가야 할 것 같아요.input().toLowerCase().trim().split("\\\\s+"); 실제로 입력을 파싱하는 로직은 parseCommand() 메서드에서 처리하고 있습니다.private void parseCommand(String input) { String[] commands = input.toLowerCase().trim().split("\\\\s+"); switch (commands[0]) { case "go" -> { switch (commands[1]) { case "north" -> moveNorth(); case "south" -> moveSouth(); case "east" -> moveEast(); case "west" -> moveWest(); default -> showUnknownCommand(); } } case "look" -> showRoom(); case "help" -> showHelp(); case "quit" -> stop(); default -> showUnknownCommand(); } } 이제 질문은 위 코드를 아래처럼 변경하는게 parseCommand() 메서드 내부의 추상화 수준을 일관성 있게 만드는데 도움이 되느냐가 될겁니다.private void parseCommand(String input) { String[] commands = tokenize(input); switch (commands[0]) { case "go" -> { switch (commands[1]) { case "north" -> moveNorth(); case "south" -> moveSouth(); case "east" -> moveEast(); case "west" -> moveWest(); default -> showUnknownCommand(); } } case "look" -> showRoom(); case "help" -> showHelp(); case "quit" -> stop(); default -> showUnknownCommand(); } } private String[] tokenize(String input) { return input.toLowerCase().trim().split("\\\\s+"); } 아래 메서드가 전체적인 추상화 수준에서 일관성이 있어서 코드를 이해하기 쉽게 만든다고 판단된다면 두번째 코드처럼 분리하고, 해당 로직이 분리할 정도로 복잡하지 않거나 분리할 정도로 중요한 부분이 아니라면 그대로 두면 됩니다.제 개인적으로는 배열을 공백으로 분리하는 작업이 별도의 메서드로 분리할 정도로 중요하지 않고 parseCommand 자체가 switch 문을 이용해서 배열을 파싱하는 전체적으로 추상화 수준이 낮은 코드이기 때문에 메서드 전체의 추상화 수준에서는 이 상태로 둬도 괜찮다고 판단했습니다.만약 이 메서드를 동일한 추상화 수준으로 맞춘다면 아래 코드처럼 변경하는게 좋을겁니다.private void parseCommand(String input) { parseTokens(tokenize(input)); } private String[] tokenize(String input) { return input.toLowerCase().trim().split("\\\\s+"); } private void parseTokens(String[] command) { switch (commands[0]) { case "go" -> { switch (commands[1]) { case "north" -> moveNorth(); case "south" -> moveSouth(); case "east" -> moveEast(); case "west" -> moveWest(); default -> showUnknownCommand(); } } case "look" -> showRoom(); case "help" -> showHelp(); case "quit" -> stop(); default -> showUnknownCommand(); } } 결과적으로 paerseCommand() 메서드를 두 개의 작은 메서드를 호출하는 메서드로 나눌 것인가, 아니면 현재의 코드를 그대로 둘 것인가의 결정인데, 저 같은 경우에는 위 코드가 이전 코드보다 그렇게 개선된 것으로 보이지 않고 오히려 너무 세분화돼 보여서 파싱 흐름을 이해하기 어렵게 만들기 때문에 그대로 뒀다고 보시면 됩니다.만약 원칙에 따라 두 개의 작은 메서드로 나누는게 코드를 이해하기에 더 좋다고 판단된다면 나누셔도 좋습니다. 두번째로 언급하신 isRunning 메서드는 장표에 오류가 있고 코드에서는 누락되었네요.아래와 같이 isRunning() 메서드로 추출하는게 맞습니다. private boolean isRunning() { return running == true; } 너무 많은 장표를 만들다보니 확인하지 못한 부분이 많은데 확인해 주셔서 감사합니다.이 부분은 수정해서 커밋해 놓을게요. 🙂
- 1
- 3
- 34
Q&A
영화, 상영, 예매 도메인 관계에 대한 질문
동관님 안녕하세요.좋은 질문 남겨 주셔서 감사합니다. 🙂먼저 영화와 예매는 N:M 관계가 아니라 1:N 관계입니다.어떤 관계에서 다중성(multiplicity)은 하나의 객체에 대해 연결될 수 있는 다른 객체의 개수를 나타냅니다.하나의 영화에는 여러 개의 예매가 만드어질 수 있는데 반해 예매는 하나의 영화에 대해서만 생성될 수 있기 때문에 영화:예매는 1:N 관계에 해당됩니다.상영은 그 자체로 도메인에 존재하는 중요한 개념입니다.아래 그림에서 영화는 "어쩔수가없다"이고 아래에 예매 가능한 4개의 박스가 상영에 해당됩니다.(사진) 참고로 다대다 관계를 해소하기 위해 중간 테이블을 추가하는 개념은 데이터베이스 모델링의 영역에 한정되는 기법입니다.객체 관계에서는 다대다 관계가 가능하기 때문에 다대다 관계를 해소하기 위해 중간 객체를 추가할 필요가 없고 협력 관점에서 특정한 동작을 수행할 객체가 필요하거나 도메인 관점에서 명시적으로 드러내야 하는 경우에 적절한 객체를 추가하게 됩니다. 답변이 되었는지 모르겠네요. 🙂감사합니다.
- 1
- 2
- 23
Q&A
책임주도 설계 적용에 대한 간단한 질문 남겨드립니다.
Chanuk님 안녕하세요. 😊좋은 질문 남겨 주셔서 감사합니다.제가 아침부터 계속 강의를 하느라 이제서야 답글을 남기네요.답변이 늦어져서 죄송합니다. 현실적으로 DB 스키마가 이미 정해져 있거나, 기존 데이터를 마이그레이션해야 해서 새롭게 설계하기 어려운 경우, 또는 DBA가 별도로 관리하는 환경에서는 책임주도 설계를 적용하기가 쉽지 않을 것 같습니다. 이런 상황에서도 객체지향적인 설계를 현실적으로 적용할 수 있는 방법이 있을까요? (DAO를 중간 계층으로 두면 어느 정도 해결될까요? 아니면 도메인 레이어와 퍼시스턴스 레이어는 분리된 영역이니 크게 상관없을까요? 반대로, 두 레이어가 지나치게 달라지면 오히려 유지보수가 더 어려워지지는 않을까 하는 걱정도 듭니다.)근본적으로 DB 설계와 객체지향 설계는 접근방식과 목적이 다르기 때문에 신규 프로젝트라고 하더라도 두 구조 사이에 차이가 발생할 수 밖에 없습니다.이런 차이를 임피던스 불일치(impedance mismatch) 문제라고 부르는데 DB는 데이터 관점에서 중복을 최소화하는 방향으로 설계해야 하고 객체지향 설계는 강의에서 설명드렸던 것처럼 행동 관점에서 안정적인 구조를 만드는 것을 목적으로 하기 때문입니다. 임피던스 불일치 문제를 해결할 목적으로 만들어진 도구를 ORM(Object Relational Mapping)이라고 부릅니다. Java 진영의 JPA 표준과 Hibernate 구현체가 ORM의 대표적인 예라고 할 수 있습니다. ORM을 사용하면 직접 쿼리를 작성할 필요 없이 어느 정도 DB와 클래스 사이의 차이점을 상쇄시킬 수 있습니다. 물론 ORM은 만능이 아니기 때문에 모든 이슈를 다 해결해 주지는 못합니다. 따라서 실용적인 관점에서 완전한 객체지향 설계를 하려고 하시기 보다는 질문에서 언급하신 것처럼 매핑이 좀 더 수월한 방식으로 객체 구조를 선택하실 필요가 있습니다.ORM을 사용하는 경우에도 해결하기가 수월하지 않은 경우가 있는데 말씀하신 것처럼 레거시 테이블이 정규화가 안된 상태로 장기간 유지된 경우가 여기에 해당합니다. 이렇게 극단적으로 매핑이 어렵지만 객체지향 설계 방식을 선태하시는게 장점이 크다고 판단되시면 테이블과 1:1로 매핑되는 데이터 용 클래스를 만들어서 데이터베이스에서는 이 데이터용 클래스를 이용해서 데이터를 조회하신 후 객체 구조로 다시 매핑하는 방법도 있습니다. 이 방식은 테이블 스키마에 얽매이지 않고 객체지향 설계를 자유롭게 할 수 있다는 장점이 있지만 데이터용 클래스와 객체를 위한 클래스를 함께 유지해야 한다는 단점도 존재합니다. 문제의 복잡도와 유지보수 관점에서의 장단점을 기반으로 적합한 방법을 선택하시면 될 것 같아요. 그리고, 책임주도 설계가 이론적으로는 유지보수에 강하다고 하지만, 실제로는 아직 구조가 다소 복잡하게 느껴져서 오히려 유지보수성을 해칠 수도 있지 않을까 합니다. 이런 복잡함은 설계 패턴에 익숙해지면 자연스럽게 해소될까요? 말씀하신 것처럼 객체지향 설계에 대해 아직 익숙하지 않다보니 복잡하고 어렵게 느끼시는게 아닐까 싶어요. 절차적인 설계에서 객체지향 설계로 이동하는 상황을 패러다임 전환(paradigm shift)라고 부를 정도로 절차적인 사고방식을 객체지향적인 사고방식으로 바꾸는 일은 매우 어렵고 힘든 일이기는 합니다(저도 겪었던 문제구요). 절차적인 방식은 새로운 코드를 작성하거나 코드를 읽을 때는 객체지향보다 쉽게 느껴집니다. 반면에 객체지향은 기존 코드에서 수정할 부분을 찾거나 코드를 수정할 때 절차적인 방식보다 더 쉽게 느껴집니다. 다시 말해서 절차적인 방식은 새로운 코드를 작성할 때는 유리하지만 유지보수의 측면에서는 좋지 않고, 객체지향은 새로운 코드를 작성할 때는 불리하지만 유지보수 측면에서는 유리하다고 볼 수 있습니다. 제 생각에 객체지향이 복잡하다고 느끼시는 이유는 처음 코드를 작성할 때 들어가는 노력이 절차적인 방식보다 더 크기 때문이 아닐까 싶어요. 유지보수 단계에서의 장점을 이해하기 위해서는 실제로 변경이 일어날 때 코드를 수정해본 경험이 필요하기 때문에 객체지향에 익숙해지시고 요구사항 변경이 빈번하게 발생하는 상황에서 코드를 수정하는 경험이 쌓이면 유지보수 측면에서 객체지향이 유리한 이유를 이해하시게 될거라고 생각합니다.답변이 되었는지 모르겠네요. 🙂행복한 주말 보내세요!
- 1
- 2
- 45
Q&A
객체지향 설계에서 메서드를 설계할 때 궁금한 점이 있습니다.
선홍님 안녕하세요.좋은 질문 해주셔서 감사합니다. 🙂제가 아침부터 계속 강의를 하느라 이제서야 답글을 남기네요.답변이 늦어져서 죄송합니다. 객체의 메서드에는 파라미터로 식별자인 id를 전달하는 것보다는 완전한 객체를 전달하는 것이 좋습니다.객체지향에서 객체가 다른 객체를 알아야 하는 이유는 메시지를 전송하기 위해서입니다.이때 객체가 다른 객체를 영구적으로 알아야 한다면 클래스 내부의 객체 참조로 구현하고, 메서드가 실행되는 시점에만 일시적으로 알기만 하면 된다면 메서드의 파라미터로 전달해 주시면 됩니다.이 관점에서 보면 객체의 파라미터로는 객체를 전달하는게 적합하다고 할 수 있습니다. 강의에서 식별자인 id를 파라미터로 받는 클래스이 있는데 ReservationService와 DAO들이 해당됩니다.이 클래스들은 객체지향에서 말하는 상태와 행위를 함께 포함하는 객체가 아니라 예매를 수행하거나 Reservation을 조회하는 등의 행위 관점에서 만들어진 클래스들입니다.이 클래스들은 실제 객체가 아니고 내부에서 id를 이용해서 책임을 수행할 객체를 찾는 작업이 필요하기 때문에 id를 파라미터로 받는다고 보시면 됩니다. 정리하면 현재 메서드를 구현하고 있는 대상이 상태와 행위를 하나의 단위로 묶어서 특정한 책임을 수행하는 '객체'라면 메서드의 파라미터로 객체를 받도록 구현하시는게 좋습니다.그렇지 않다면 파라미터로 id를 받으셔도 무방합니다. 🙂 답변이 되었는지 모르겠네요.행복한 주말 보내세요!
- 2
- 2
- 33
Q&A
[오타제보] 6-4. 캡슐화
이상민님 안녕하세요.매의 눈으로 오류를 잡아주셔서 정말 감사드립니다. 🙂말씀하신 것처럼 LSP를 OCP로 수정하는게 맞네요.먼저 pdf 배포본을 수정해서 올려놓고 동영상은 최대한 빨리 보정해서 다시 올리도록 하겠습니다!행복한 주말 보내세요.감사합니다.
- 2
- 2
- 32
Q&A
리스코프 치환원칙에 대해 질문드립니다!
이석운님 안녕하세요.좋은 질문 남겨주셔서 감사합니다. 리스코프 치환 원칙의 의미에 대해서 잘 알고 계시네요. 🙂강의에서도 이석운님의 의견과 유사하게 설명하고 있습니다.강의의 6분 41초 부분을 보시면 "이렇게 클라이언트 입장에서 서브 타입이 슈퍼 타입을 대체할 수 있도록 설계하는 원칙을 리스코프 치환원칙이라고 부릅니다"라고 설명하고 있어요. 리스코프 치환 원칙의 핵심은 어떤 클래스가 다른 클래스의 서브타입인지 여부를 판단하기 위해서는 두 클래스만 봐서는 안되고, 이 클래스들을 사용하는 클라이언트의 관점에서행동이 호환 가능한지 여부를 살펴봐야한다는 것을 의미합니다. 말씀하신 예에 따르면 Collection 인터페이스의 add 오퍼레이션이 있고, Collection 인터페이스륵 구현한 어떤 클래스 AnyCollection에 add 메서드를 오버라이딩 했다고 해볼게요.이 때 AnyCollection 클래스가 Collection 인터페이스의 서브타입(서브클래스가 아니라)이 되려면 Collection 인터페이스 대신 AnyCollection 인스턴스가 전달되더라도 Collection을 사용하는 기존 코드가 오류 없이 정상적으로 실행되어야만 리스코프 치환 원칙을 만족한다고 말할 수 있습니다. 리스코프 치환 원칙의 의미와 리스코프 치환 원칙을 만족시키는 설계를 만들기 위한 가이드라인은 제가 만든 다른 강의인 "오브젝트 - 설계원칙편(https://inf.run/Hr6j7)"에서 깊이 있게 다루고 있습니다.강의를 통해 알고 계신 내용을 한번 복습해 보시는 것도 좋을것 같아요. 🙂 답변이 되었는지 모르겠네요.감사합니다.
- 2
- 2
- 36
Q&A
도메인 모델을 잘 정의하기 위해서 어떻게 해야할까요?
Binsk님 안녕하세요.좋은 질문 해주셔서 감사합니다. 🙂제가 아침부터 계속 강의를 하느라 이제서야 답글을 남기네요.답변이 늦어져서 죄송합니다. 약간은 의아하게 들릴 수도 있겠지만 도메인 모델을 코드와 별개의 산출물로 보기 보다는 거의 같거나 또는 같은 내용을 서로 다른 형식으로 표현한 것 정도로 바라보는게 유용하다는 말씀을 드리고 싶어요.강의에서 ‘표현적 차이’라는 개념을 설명드렸던 것처럼 코드의 구조를 통해 도메인 모델의 구조를 떠올릴 수 있도록 만드는게 좋습니다.도메인 모델에 대한 이런 관점을 기반으로 하는 방식이 “도메인 주도 설계(Domain-Driven Design)"인데 관심이 있으시다면 한번 학습해 보시기를 권해드립니다. 이 관점을 중심으로 질문에 답변 드리도록 할게요. Q: 도메인 모델은 개발 프로세스 중 어느 시점에 정의하는 것이 좋은지?A: 어느 한 시점에 도메인 모델을 정의한다기 보다는 개발하는 기간 동안(또는 유지보수 기간 동안) 지속적으로 코드와 함께 도메인 모델을 정의하고 발전시켜 나가는게 좋습니다. 초반에는 도메인에 대한 지식이 명확하지 않고 요구사항이 계속 변경될 수 있기 때문에 소프트웨어의 생명주기 전반에 걸쳐 계속해서 도메인에 대한 관점을 변경하고 개선시키게 됩니다. 따라서 도메인 모델을 어느 한 시점에 정의한다기 보다는 소프트웨어가 종료될 때까지 계속해서 도메인 모델을 개선해야 한다고 생각하시면 좋습니다. Q: 도메인 모델을 정의하는 것은 개발자가 주도적으로 이끄는 것인지 아니면 기획자와 같은 다른 팀원들과 함께 만들어 나가는 것인지?A: 도메인 모델은 도메인에 대한 관점이기 때문에 코드뿐만 아니라 요구사항을 분석하고 커뮤니케이션할 때도 영향을 미치게 됩니다. 따라서 도메인 모델을 어떤 직군이 주도적으로 만들어 간다기 보다는 서로 협력하면서 같이 성장시켜 나가는 방식이 좋습니다. 다만 개발 직군 이외의 다른 직군분들이 이런 방식에 대해 가치가 있다고 생각하지 않거나 신경을 쓸 여유가 없으실 수도 있습니다. 이런 경우에는 개발자들이 주도적으로 논의를 이끌어가면서 도메인 모델을 정리하는게 효율적일 수 있습니다. 앞 답변에서 말씀드린 것처럼 이런 논의는 일회성으로 끝나면 안되고 지속적으로 이뤄지는게 좋습니다.Q: 만들어진 도메인 모델을 토대로 설계 및 개발을 진행하다 도메인 모델을 변경해야만 하는 순간이 있는지?A: 앞에서 말씀드린 것처럼 도메인 모델은 고정적인 것이 아니라 도메인에 대해 더 잘 알게 되거나 요구사항이 변경되서 도메인에 변화가 일어난다면 그에 맞게 도메인 모델도 변경되게 됩니다.Q: 좋은 도메인 모델을 만들었는지 평가해볼만한 방법들이 있는지?A: 코드가 도메인 모델을 반영하도록 만들었다고 가정할 때 요구사항이 변경되더라도 전체적인 코드의 구조가 크게 흔들리지 않는다면 좋은 도메인 모델을 만들었다고 볼 수 있습니다. 다시 말해서 도메인의 본질에 대한 관점이 코드 안에 안정적으로 정착됐다는 것을 의미합니다.더 자세한 내용이 궁금하시다면 에릭 에반스가 쓴 “도메인 주도 설계(https://www.yes24.com/product/goods/5312881)"를 한 번 읽어보시기를 추천드립니다.에릭 에반스는 책 전반에 걸쳐서 Binsk님이 궁금해하시는 부분에 대한 답을 체계적으로 정리해 놓았습니다.(이후에 나온 대부분의 도메인 주도 설계 책들에는 이 관점이 누락되어 있는 경우가 많아서 읽기 힘드시더라도 에릭 에반스의 책을 보시는걸 추천드립니다.) 답변이 되었는지 모르겠네요. 🙂행복한 주말 보내세요!
- 1
- 2
- 44




