게시글
질문&답변
2024.09.13
2-4 강에서 제공된 수업 노트 관련 질문 드려요
Tai Sun Park님 안녕하세요. 🙂 리팩토링을 하기 전의 코드는 '2-1 절차적인 설계'의 수업 노트에 github 주소가 적혀있습니다. https://github.com/eternity-oop/object-basic-02-01 해당 코드를 fork하셔서 연습하시면 될것 같습니다. 혹시라도 필요하신 예제가 위 코드가 아니면 다시 요청 남겨 주세요. 행복한 추석 연휴 보내시고요. 감사합니다. 🙂
- 0
- 2
- 21
질문&답변
2024.08.31
[6-2] 응집도의 변경 관점에서, [속도] 관련
김지환님 안녕하세요. 속도는 변경의 빈도라고 이해하시면 됩니다. 예를 들어서 하나의 클래스 안에 A와 B라는 변경이 공존하고 있다고 해보겠습니다. A가 한달에 한번 1일에 변경이 일어나고 B가 한달에 두 번 1일과 15일에 변경이 일어난다고 가정해 보겠습니다. A와 B는 매월 1일 같은 시점에 변경되지만 A가 한달 주기로 변경되는데 비해 B는 15일 주기로 변경되기 때문에 결과적으로 A는 변경되지 않지만 B는 변경되는 상황이 발생합니다. 따라서 A와 B는 같은 시점에 변경이 일어나지만 서로 다른 주기로 변경이 일어나기 때문에(즉, 변경의 속도가 다르기 때문에) 서로 다른 클래스로 분리되어야 합니다. '동일 시점에 동일한 속도'라는 말을 동일한 주기로 함께 변경되는 코드 집합이라고 이해하시면 될 것 같아요. 감사합니다. 🙂
- 0
- 2
- 61
질문&답변
2024.08.28
객체 지향 설계 첫번째 원칙
코딩천국님 안녕하세요. 🙂 책보다 더 쉽게 이해하실 수 있도록 강의를 구성했는데 도움이 되신다니 다행이네요. 강의 들어주셔서 감사드리고 궁금한 부분 있으면 그때그때 질문 주시면 최대한 빠르게 답변드릴게요. '객체를 선택하기 전에 요청을 결정하기 때문에 코드를 수정하지 않고도 협력하는 객체를 교체할 수 있게 해준다'라는 문장을 두 개로 나눠서 답변드릴게요. 먼저 '객체를 선택하기 전에 요청을 결정한다'라는 말은 질문에 남겨주신 'calculateDiscount 행동을 먼저 정하고 이 행동을 하기에 적합한 객체를 선택한다'라는 말과 같은 의미입니다. 책임 주도 설계에서는 메시지 전송자가 협력을 위해 'calculateDiscount'를 요청할 필요가 있다는 사실(다시 말해서 메시지를 전송할 필요)을 결정한 후에 이 요청을 수행하기에 적합한 객체를 선택합니다. 결과적으로 메시지 수신자의 입장에서는 calculateDiscount라는 행동을 먼저 결정한 후에 객체를 결정하게 되는 것이죠. 아마 궁금한 부분은 '코드를 수정하지 않고도 협력하는 객체를 교체할 수 있게 해준다'라는 부분일것 같은데 이것은 바로 앞에서 설명한 '요청을 결정한 후에 객체를 선택'하기 때문에 가능한 일입니다. 요청(메시지, 행동)를 결정한 후 객체를 선택했기 때문에 메시지 전송자는 calculateDiscount 요청에 따라 적합한 행동을 제공할 수 있는 객체라면 누구와도 협력이 가능하게 됩니다. 다시 말해서 calculateDiscount라는 행동을 제공할 수 있는 객체라면 협력에 참여할 수 있기 때문에 '협력하는 객체를 교체'할 수 있는 것입니다. 일단 객체를 교체할 수 있도록 협력을 구성한 후에는 실제로 코드 레벨에서 객체를 교체할 수 있도록 구조를 만들 필요가 있습니다. 이 방법은 강의에서 아래 부분에 상세한 내용이 정리되어 있기 때문에 강의를 보시고나면 아주 쉽게 이해가 되실거에요. 5-2 메시지와 메서드의 분리 5-3 유연하고 일관적인 협력 답변이 되었는지 모르겠네요. 🙂
- 0
- 2
- 67
질문&답변
2024.08.23
객체 협력, 클래스구조와 런타임, 컴파일타임의 관계
꼬꼬록님 안녕하세요. 🙂 강의가 도움이 된다니 정말 다행이네요. 말씀 듣고나니 컴파일타임과 런타임이라는 용어에 대해 오브젝트 책에서는 간략히나마 설명해 놓았었는데 강의에서는 충분히 설명드리지 못했다는 생각이 드네요. 오브젝트 책 257페이지의 내용을 인용하도록 하겠습니다. 의존성과 관련해서 다루어야 하는 또 다른 주제는 런타임 의존성(run-time dependency) 과 컴파일타임 의존성(compile-time dependency) 의 차이다. 먼저 여기에서 사용하는 런타임과 컴파일타임의 의미를 이해할 필요가 있다. 런타임 은 간단하다. 말 그대로 애플리케이션이 실행되는 시점을 가리킨다. 컴파일타임은 약간 미묘하다. 일반적으로 컴파일타임 이란 작성된 코드를 컴파일하는 시점을 가리지키지만 문맥에 따라서는 코드 그 자체를 가리키기도 한다. 컴파일 타임 의존성이 바로 이런 경우에 해당한다. 컴파일타임 의존성이라는 용어가 중요하게 생각하는 것은 시간이 아니라 우리가 작성한 코드의 구조이기 때문이다. 또한 동적 타입 언어의 경우에는 컴파일 타임이 존재하지 않기 때문에 컴파일 타임 의존성이라는 용어를 실제로 컴파일이 수행되는 시점으로 이해하면 의미가 모호해질 수 있다. 따라서 어딘가에서 컴파일타임이라는 용어를 보게된다면 그것이 정말 컴파일이 진행되는 시점을 가리키는 것인지 아니면 코드를 작성하는 시점을 가리키는 것인지를 파악하는 것이 중요하다. 객체지향 애플리케이션에서 런타임 의 주인공은 객체 다 . 따라서 런타임 의존성이 다루는 주제는 객체 사이의 의존성이다. 반면에 코드 관점에서 주인공은 클래스 다. 따라서 컴파일타임 의존성이 다루는 주제는 클래스 사이의 의존성이다. 질문해 주신 ' 객체 협력(런타임)에서 클래스 구조(컴파일타임)의 순서로 진행 '은 말 그대로 런타임의 구조를 먼저 고민한 이후에 런타임 구조에 맞는 컴파일타임 구조를 설계해야 한다는 말입니다. 객체지향에서 런타임 구성 요소는 객체이고 (클래스 기반의 객체지향 언어의 경우) 컴파일타임 구성 요소(즉, 코드의 단위)는 클래스이기 때문에 객체의 구조와 객체 사이의 협력 관계를 먼저 결정한 후에 클래스 내부와 클래스 사이의 관계를 설계해야 한다고 이해하시면 됩니다. 답변이 되었는지 모르겠네요. 🙂
- 1
- 1
- 105
질문&답변
2024.08.18
DIP vs OCP
김민정님 안녕하세요. 궁금하신 부분에 대해 좀 더 부연 설명드리도록 하겠습니다. 🙂 의존성 역전 원칙(Dependency Inversion Priciple, DIP) 는 다음과 같이 정리할 수 있습니다. a. 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다. 둘 모두 추상화에 의존해야 한다. b. 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다. DIP의 목적은 상위 수준의 중요한 정책이 하위 수준의 메커니즘에 의해 영향을 받지 않도록 만드는 것입니다. 여기에서 상위 수준의 정책을 객체 사이의 협력으로 생각하셔도 무방합니다. 해당 질문에서 ReservationService가 CustomerDAO를 이용해서 데이터를 조회한다는 것은 상위 수준의 정책입니다. (사진) 데이터베이스에서 Customer를 어떻게 쿼리하는지를 구현한 CustomerJdbcDAO는 세부사항입니다. 이 세부사항이 변경되더라도 ReservationService가 CustomerDAO와 협력해서 데이터를 조회하는 상위 수준의 협력은 영향을 받지 말아야 합니다. 이런 이유로 ReservationService와 CustomerJdbcDAO 모두 추상화 인 CustomerDAO에 의존하도록 만든 것입니다. 여기에서 추상화 가 핵심인데 DIP는 자주 변하는 것이 아니라 자주 변하지 않는 안정적인 추상화에 의존하라고 가이드하고 있기 때문입니다. 간단히 말해서 DIP는 중요하지 않은 세부사항의 변경으로 인해 중요한 상위 정책이 영향을 받지 않도록 추상화에 의존하도록 의존성을 제어하는 원칙입니다. 개방 폐쇄 원칙(Open-Closded Principle, OCP) 은 다음과 같이 정리할 수 있습니다. 소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다. OCP는 유연한 설계란 기존 코드를 수정하지 않고도 기능을 확장할 수 있어야 한다고 이야기합니다. OCP의 목적은 코드를 수정하지 않고도 다양한 환경에서 코드를 재사용할 수 있도록 만드는 것입니다. 어떤 메커니즘을 사용하건 이 목적을 만족할 수만 있다면 개방 폐쇄 원칙을 만족한다고 할 수 있습니다. 객체지향에서 OCP를 만족시키는 가장 일반적인 방법은 다형성을 사용하는 것입니다. 일반적으로 다형성을 이용해서 OCP를 만족시키기 위해서는 DIP와 LSP(Liskov Substitution Principle)를 조합해서 코드를 작성합니다. 강의에서도 이 방식을 소개하고 있기 때문에 DIP와 OCP가 혼란스럽게 느껴지신것 같습니다. DIP를 적용했다고 해서 OCP가 보장되는 것은 아니기 때문입니다. 하지만 다형성을 통해 OCP를 달성하기 위해서는 기반 구조에 DIP를 적용할 필요는 있습니다. OCP를 만족시키는 방법이 다형성만 있는 것은 아닙니다. 예를 들어 변경사항을 외부의 데이터로 빼서 코드는 수정하지 않은채 데이터만 변경해서 애플리케이션의 행동을 확장하는 것도 OCP의 한가지 방법입니다(룰 엔진이나 게임에서 많이 사용됩니다). 아래 공유드리는 책에 OCP를 만족시키기 위해 데이터를 사용하는 설계의 예가 나옵니다. 정리하면 OCP는 기존 코드를 수정하지 않으면서 행동을 확장하는 원칙을 가리키며 DIP는세부사항의 변경에 의해 상위 정책이 영향을 받지 않도록 의존성을 제어하는 원칙입니다. 둘 모두 추상화에 의존한다는 공통점 이 있지만 의도는 조금 다릅니다. 흔히 OPC는 달성하려는 목적을, DIP는 이를 위한 메커니즘이라고 이야기 하기도 합니다. 더 상세한 내용이 궁금하시면 아래 책을 보시면 많은 도움이 되실 거에요. https://product.kyobobook.co.kr/detail/S000001875106 정리하면 질문에 적어주신 아래 내용이 다소 부정확하기는 하지만 어느 정도는 핵심을 짚으신 것이라고 할 수 있습니다. OCP는 다양한 DAO구현체를 제공하기 위한 것에 초점을 맞춘 것이고, DIP는 의존성을 끊기 위한 것에 초점을 맞춘 것이라고 이해하면 될까요 답변이 됐는지 모르겠네요. 🙂
- 2
- 1
- 155
질문&답변
2024.08.18
실제로 객체지향으로 설계를 많이 하는지 궁금합니다.
High-East님 안녕하세요. 먼저 강의 들어주셔서 감사하다는 말씀 먼저 드립니다. 🙂 강의가 도움이 되었다니 다행이네요. 저도 예전에 "그동안 절차지향적으로 코드를 작성했던 거구나!"라는 깨달음을 얻는 순간부터 설계와 유지보수에 대한 관점이 많이 바뀌고 많은 부분에서 성장했었어요. 질문하신 내용 중에서 DDD와 객체지향 사이의 관계에 관해 간단히 설명드린 후에 질문하신 내용에 대해 답변 드리도록 하겠습니다. DDD는 복잡한 도메인의 문제를 해결하기 위해 적용할 수 있는 사고 방식과 우선 순위의 모음입니다. DDD는 단순히 코드를 설계하는 방법을 넘어서서 도메인 분석, 리팩토링, 시스템 분할, 협업 방식, 조직 구조를 아우르는 거대한 패턴 언어를 의미합니다. 반면에 객체지향은 유지보수 가능한 소프트웨어를 만들기 위해 변경하기 쉽도록 코드를 배치하는 방법을 가이드하는 것이 목적입니다. 도메인라는 넓은 범위의 문제를 다루는 DDD와 달리 객체지향은 코드라는 좁은 범위의 문제를 다룬다는 점에서 차이가 크다고 할 수 잇습니다. 결과적으로 DDD를 하기 위해 반드시 객체지향이 필요한 것은 아니고, 객체지향을 한다고 해서 반드시 DDD를 해야하는 것은 아닙니다. DDD가 객체지향 커뮤니티의 영향을 받았기 때문에 객체지향 개념에서 많은 부분을 차용했기 때문에 강의에서 다루는 내용이 개념적으로 DDD와 겹치는 부분이 있기는 하지만 명확하게 말하면 강의에서 다루는 내용을 DDD라고 부르기에는 조금 애매하다는 점을 먼저 말씀드릴게요. 비슷한 질문을 많이 받다보니 "도메인 주도 설계의 사실과 오해( https://edu.nextstep.camp/c/SXgXIKdd/) "라는 오프라인 강의를 통해 혼란스러운 부분을 정리해드리고 있습니다. 이렇게 장황하게 설명드린 이유는 질문 주셨던 아티클에서 인용한 "DDD를 적용하기에 적절하지 않은"이라는 문구가 "객체지향을 적용하기에 적절하지 않은"이라는 의미가 아니라는 말씀을 드리고 싶어서에요. 앞에서 설명드린 것처럼 DDD와 객체지향은 독립적인 개념이기 때문에 DDD를 적용하기에 적절하지 않은 문제라고 하더라도 객체지향을 이용해서 해결할 수 있습니다. 블로그 글( https://eternity-object.tistory.com/26) 에서 인용한 아티클에서 이야기하고 있는 데이터 중심적이라는 이야기는 도메인의 복잡도라고 생각하시면 될것 같습니다. 인용글에서 언급한 것처럼 "매우 복잡하고 잘 정의된 비즈니스 모델에 초점을 맞추어야 하는 소프트웨어"가 아니라면, 즉 도메인의 복잡도가 높지 않다면 DDD에서 말하는 유비쿼터스 랭귀지나 도메인 모델, 바운디드 컨텍스트의 개념이 오히려 방해가 되기 때문에 DDD는 적합하지 않을 수 있습니다. 어떤 도메인은 순수하게 데이터를 처리하는 것을 목적으로 할 수도 있습니다. 이런 경우 DDD를 적용하지 않는 것이 더 좋겠죠. 다만 도메인이 데이터 중심이라고 하더라도 일부 코든 객체지향적인 방식으로 구현하고 일부 코드는 절차적인 방식으로 구현할 수 있습니다. 요약하면 "데이터 중심"이라는 말은 도메인의 특성을 의미하는 것이며 코드의 배치가 "절차적인 방식"이라는 것과는 무관하다고 말씀 드릴 수 있겠습니다. 블로그의 마지막 부분에 해당 아티클을 인용한 이유는 DDD가 모든 문제를 해결해주지 않으며 DDD는 목적이 아니라 수단이기 때문에 적용 여부를 신중하게 선택했으면 하는 마음에서 였습니다. 제가 저 글을 썼던 2009년에 비해(벌써 15년이나 흘렀네요) 현재는 DDD를 적용하기에 적합한 영역이 많이 늘어났지만 여전히 DDD의 적용 여부를 보수적으로 판단해애 한다는 사실은 변하지 않은 것 같습니다. 길이 길어졌는데 혹시 더 궁금한 부분 있으시면 댓글 부탁드립니다. 🙂 감사합니다!
- 2
- 2
- 305
질문&답변
2024.08.17
DAO 관련 질문드립니다
code-tree님 안녕하세요. 🙂 강의 재미있게 들어주시고 좋은 질문 남겨 주셔서 감사드립니다. 질문에 대한 답변을 두 가지로 나눠서 설명 드리도록 하겠습니다. 1) 다양한 영속성 메커니즘을 구현한 DAO 혼용 먼저 하나의 데이터베이스에 대해 영속성 메커니즘 별로 DAO(또는 Repository)를 나누는 것은 그렇게 좋은 방법은 아닙니다. JPA는 영속성 컨텍스를 이용해서 엔티티들을 관리하기 때문에 예제에서 사용한 JdbcClient와 같이 영속성 컨텍스트를 인지하지 못하는 기술을 혼용할 경우 예상하지 못한 결과가 나올 수 있습니다. 또한 유지보수 측면에서도 혼란스러울 수 있는데 JPQL, QueryDSL, 네이티브 쿼리를 직접 사용하는 기술 모두 동일한 기능을 구현할 수 있기 때문에 어떤 DAO를 사용해야 하는지 기준을 정하기가 애매할 수 있습니다. 따라서 하나의 엔티티를 관리하는 경우라면 하나의 인터페이스 안에 구현 메커니즘에 독립적인 오퍼레이션을 정의하고 구현 클래스에서 오퍼레이션 레벨에서 서로 다른 영속성 메커니즘을 사용하는 것은 가능합니다. 하지만 너무 많은 영속성 메커니즘을 혼용하는 것은 좋지 않기 때문에 JPA를 사용하신다면 가급적이면 EntityManagerr와 QueryDSL 두 가지 종류만 혼용해서 사용하는 것으로 한정할 수는 없는지 고민해 보시는 것이 좋습니다. 시스템이 커지고 오퍼레이션이 커지면 비즈니스 로직을 구현하기 위한 Command와 조회를 위한 Query를 분리하는 CQRS에 대한 고민이 생길 수 있는데 이 경우에는 Command와 Query를 위한 DAO를 분리해서 구현할 수는 있습니다. 이 경우에는 Command를 위한 DAO는 JPA를, Query를 위한 DAO는 QueryDSL이나 네이티브 쿼리를 사용해서 구현할 수는 있겠지만 두 DAO에 대해 기술에 중립적인 인터페이스를 정의해서 Service를 영속성 메커니즘으로부터 분리시키는 것은 여전히 좋은 설계 방법입니다. 정리하면 영속성 메커니즘에 따라 DAO를 나누지 말고 DAO를 사용하는 클라이언트의 목적에 따라 DAO를 분리한 후에 적합한 영속성 메커니즘을 선택하는 것이 좋고 가급적이면 사용하시는 영속성 메커니즘의 수를 제한하고 기준을 명확하게 정의하시는 것이 좋습니다. 2) DAO에 인터페이스를 사용하는 이유 DAO에 인터페이스를 사용하는 이유는 다양한 DAO 구현체를 제공하기 위해서가 아니라 의존성을 끊기 위해서입니다. (물론 테스트를 고려한다면 목과 스텁이라는 관점에서 여러 개의 DAO 구현체를 제공하기 위해 인터페이스를 제공한다고 말할 수 있습니다.) 의존성을 끊는 이유에 대해서는 제 블로그에 있는 아티클( 의존성 끊기와 단위 테스트-1부 , 의존성 끊기와 단위 테스트-2부 )을 참고하시면 도움이 될거에요. 강의의 예제 코드 를 보시면 Service가 영속성 메커니즘에 대해 의존하지 않도록 DAO를 인터페이스로 정의하고 DAO 구현 클래스들은 별도의 패키지 안에 구현했다는 것을 알 수 있습니다. (사진) 이렇게 인터페이스를 이용해서 Service 레이어를 영속성 메커니즘과 분리하면 관심사를 명확하게 분리할 수 있고 Service 레이어를 테스트하거나 재사용하기 용이해집니다. 이 경우 역시 의존성 역전 원칙(Dependency Inversion Principle)을 사용해서 결합도를 낮춘 예라고 할 수 있습니다. 답변이 됐는지 모르겠네요. 즐거운 하루 보내세요. :)
- 1
- 1
- 111
질문&답변
2024.08.13
수업 자료 압축파일
자니?님 안녕하세요. 강의 마지막에 "강의자료 통합본"이라는 섹션을 추가했습니다. 섹션에 들어가셔서 자료를 받으시면 됩니다. 강의 즐겁게 들으세요. 🙂
- 1
- 1
- 108
질문&답변
2024.08.11
jpa를 사용하는 경우 퍼시스턴스 처리에 대해
데이터베이스는 소유권 관점에서 공유 데이터베이스(shared database) 와 애플리케이션 데이터베이스(application database)로 분류할 수 있습니다. 공유 데이터베이스는 소유권이 없는 다른 애플리케이션이나 다른 조직과 데이터베이스를 공유하기 때문에 데이터베이스 스키마를 원하는 방식으로 설계할 수 없습니다. 반면에 애플리케이션 데이터베이스는 하나의 애플리케이션 또는 조직이 통제권을 가지는 데이터베이스로 이 경우에는 데이터베이스 스키마를 원하는 방식대로 조정할 수 있습니다. 공유 데이터베이스를 사용하기 때문에 데이터베이스 스키마를 통제할 수 없다면 별도의 데이터 객체를 만들고 이를 JPA 엔티티로 매핑하는 2)번 방식을 사용하는 것이 유지보수 관점에서 유리합니다. 애플리케이션 데이터베이스를 사용하고 있다면 데이터베이스 스키마와 도메인 객체 양쪽 모두를 조율할 수 있기 때문에 JPA 엔티티를 도메인 객체로 사용하는 1)번 방식을 선택하는 것이 유리합니다. 애플리케이션 데이터베이스를 사용하는 상황에서 JPA를 위한 데이터 객체를 사용하는 것은 불필요한 매핑과 복잡성을 추가하기 때문에 개인적으로 선호하는 방법은 아닙니다. 다만 애플리케이션 데이터베이스를 사용하는 경우에도 레거시 데이터베이스 스키마가 너무 오래되고 복잡해서 마이그레이션하기 어렵다면 데이터 객체에 데이터를 로드한 후 다시 한번 JPA 엔티티와 매핑하는 2)번 방식을 사용해야 할 수도 있습니다. 상황에 따라 가장 실용적인 방식을 선택하시면 좋을것 같아요. :) 답변이 되었는지 모르겠네요. 🙂
- 1
- 1
- 199
질문&답변
2024.08.03
IntelliJ IDEA 폰트를 알수 있을까요?
고대준님 안녕하세요. Inellij IDAE에서 사용하는 폰트는 Apple Menlo 폰트 입니다. 만약 윈도우즈를 사용하고 계시다면 Menlo 폰트를 사용하실 수 없는데 이 경우에는 Menlo를 커스터마이징한 Meslo 폰트를 사용하시면 됩니다. https://github.com/andreberg/Meslo-Font 답변이 되었는지 모르겠네요. 🙂
- 1
- 1
- 170