✅ OOP, 객체지향적인 설계
1주차를 마치고 여러 사람들과 의견을 주고 받으며 객체 지향적인 설계에 대해서 한 번 생각을 해보았다.
그러는 과정에서 Service레이어가 꼭 필요한가에 대해서 생각도 해보았고,
객체 지향적인 설계를 위해서는 어떠한 요소가 가장 중요한지에 대해서도 한 번 꼽씹어 보는 과정이였다.
그리고 다음과 같은 질문에 대해 생각해보았다.
🤔단순히 MVC 패턴으로 레이어를 분리하고 객체들을 만들어내면
그것은 과연 객체지향적인 설계일까?
이를 답하기 위해서는 OOP의 핵심 요소를 다시 한 번 되돌아봐야한다.
- ☑️ 추상화
: 불필요한 세부 사항을 감추고 객체의 핵심적인 특징만을 드러내는 것을 의미
=> 객체의 역할과 책임을 명확히 정의 - ☑️ 캡슐화
: 객체의 데이터를 외부에서 직접 접근하지 못하도록 보호하고,
필요한 인터페이스를 통해서만 데이터를 다룰 수 있도록 하는 원칙
=> 객체의 내부 구현을 감추고, 코드의 수정과 유지보수를 용이 - ☑️ 상속
: 기존 클래스의 특성과 동작을 새로운 클래스에 물려주는 개념
=> 코드의 재사용성을 높이고 계층적인 관계를 설계 - ☑️ 다형성
: 동일한 메시지나 메서드 호출에 대해, 객체에 따라 다르게 반응하도록 하는 특성
=> 유연한 코드 설계가 가능해지며, 확장성 있는 시스템 만들 수 있음
그리고 OOP에서 가장 중요한 구조적 사고는 다음과 같다.
객체의 역할과 책임을 명확히 정의하고, 상호작용을 통해 기능을 수행하게 하는 것
즉, 객체들간의 역할과 책임 분배를 제대로 했는가?, 객체들간에 서로 상호작용을 주고 받는가?
이러한 것들이 기본으로 OOP의 설계에 기반을 다뤄야한다는 것이다.
여기서 떠오른 말이 하나가 있었다.
"객체들과 메시지를 주고받아라"
처음에 막 OOP에 대한 설계에 대해 공부를 할 때에는 이러한 말을 제대로 이해하지 못하였는데,
1주차의 프리코스를 끝내고 다른 사람들을 리뷰할 때 이 문장이 확 생각이 났다.
만약에, 객체들간의 상호작용이 이루어지지 않는다면,
결국 해당 객체들의 메서드들이나 필드값을 활용하기 위해 어느 한 곳에서 전부 호출해야할 것이고,
이는 오히려 객체지향적인 설계보다는 함수를 불러오는 절차지향적인 설계에 가깝다는 것이다.
이렇게 답변이 돌아오고 나서 또 다시 한번 생각해볼 기회를 제공해주셨다.
🤔 객체 지향적인 설계에서는 의존관계를 줄여서 결합도를 낮추는 것이 중요한데,
그러면 독립적으로 객체를 둬야하는 게 아닌가?
의존관계를 줄이는 것은 코드의 유지보수성, 그리고 재사용성 부분에 있어서 매우 중요하다.
그러나
의존관계를 줄이는 것 = 독립적인 객체?
이 부분에 대해서는 다시 한 번 생각해보았고 그리고나서 내린 결론은 다음과 같다.
의존관계를 느슨하게 만들고 설계하는 것은 OOP에서 정말 중요한 요소이다.
하지만 독립적으로 구성하는 것과 의존 관계를 느슨하게 하는 것은 완전히 느낌이 다르다.
의존관계를 "느슨하게" 만드는 것이지 완전히 객체들간의 관계를 "끊어내는 것"이 아니기 때문이다..!!!
결국에 "어떻게 객체들간의 역할과 책임을 잘 분리하고 상호작용을 하게 할것이냐"가 가장 중요하다는 것이다!
그리고 연이어 해주신 질문에 대해서도 생각해보게 되었다.
🤔 객체 생성은 어느 레이어에서 진행되야 하는가?
이 부분도 MVC 패턴을 공부하면서 정말 많이 고민하고 다양하게 적용해보았다.
Controller는 model과 view를 이어주는 역할을 담당한다.
다시말해서 model의 값을 view로 전달하는 역할을 담당한다.
또한 view는 독립적이어야한다. view는 자기자신을 제외한 나머지들 알아서는 안되기 때문이다.
그렇다면 model이 생성되는 부분은 controller라는데에 의문의 여지가 없을 것이다.
그러나 프로젝트가 복잡해질수록 model을 전부 다 controller에서 생성해버린다면,
controller의 부담이 커질 것이고, 코드의 가독성 또한 많이 떨어질 것이다.
그래서 내린 결론은 다음과 같다.
☑️Controller의 부담이 커졌을 때에는 Service의 계층에 모델 생성을 위임하자
(본래 Service 레이어의 목적이 Controller의 부담을 줄여주는 것이므로)
☑️Controller가 한 개만 있다는 법칙은 없다!
프로젝트의 규모가 커질수록 역할을 잘 분리할 수 있는 Controller를 여러 개 만들어서 부담을 나눠갖자.
✅ 일급컬렉션, 제대로 활용하고 있는가?
1주차 미션을 진행하면서 나는 Terms 객체를 일급 컬렉션으로 두어 문제를 해결하였다.
일급 컬렉션을 사용한 이유는 다음과 같았다.
- ☑️ 코드의 명확성
: 일급 컬렉션을 사용하면 컬렉션 자체를 하나의 객체로 관리할 수 있다.
단순히 리스트나 맵 같은 컬렉션을 사용하는 대신, 이를 감싸는 클래스를 만들면 해당 컬렉션이 어떤 역할과 책임을 가지는지 명확하게 표현할 수 있다.
List<Term>라는 단순한 리스트 대신 Terms라는 일급 컬렉션 클래스를 사용하면,
이 항들의 모음임을 나타내고, 여기에 관련된 행위도 모두 이 클래스로 집중시킬 수 있습니다.. - ☑️ 불변성
: 일급 컬렉션은 내부의 컬렉션을 외부에서 직접 변경할 수 없도록 캡슐화한다.
이를 통해 데이터를 불변하게 유지하며, 의도하지 않은 데이터 변경을 방지할 수 있다.
이는 코드의 안정성과 예측 가능성을 높인다. - ☑️ 비즈니스 로직의 캡슐화
: 컬렉션에 대한 비즈니스 로직을 일급 컬렉션 클래스 내부에 캡슐화함으로써, 관련된 로직이 분산되지 않고 한 곳에 집중되도록 설계할 수 있다.
즉 이렇게 구성함으로써 Term이라는 항에서 양수만을 검증하는 역할만 담당하도록 객체를 설계할 수 있었다 - ☑️ 일관된 관리
: 일급 컬렉션은 컬렉션에 대한 일관된 인터페이스를 제공한다.
이렇게 하면 컬렉션에 대한 처리를 통일된 방식으로 관리할 수 있고, 중복된 코드나 실수를 줄일 수 있다.
이렇게 하여 Term객체 안에서는 양수만을 검증할 수 있는 validate 로직만 진행할 수 있어 캡슐화가 가능하였다!
그러나!!👀👀👀
어느 한 분이 일급 객체에 대한 뼈 같은 조언을 남겨주셨다.
나는 현재 Term이라는 객체의 필드값을 Int 형으로 선언하고 사용하기에,
Terms 일급 객체에서 사용되는 메서드들은 전부 다 Integer로 구현하였다.
그러나 만약에 계산기라는 특성상 실수의 형태로 숫자가 들어오는 경우,
코드를 이곳저곳 바꿔야 하고, 이는 유지보수성 측면에서 매우 치명적이였다.
따라서 조언대로 일급 객체나 String으로 처리하는 것이 코드의 유연함을 더해주는 것이였다!
✅ 확장적인 설계 != 미리 구현
1주차의 코드를 살펴보면, 특히 OCP의 원칙에 집중하여 구현했다는 것을 알 수 있다.
그만큼 확장에 열려있는 구조를 설계하려 했었고,
계산기의 특성상 add 이외에도 multiply, subtract 등등 여러가지를 쉽게 구현할 수 있게 설계해야한다고 생각하였다.
따라서, 다음과 같이 addition 이외에도 subtract, multiply의 기능을 구현하였다.
그러나 이에 대한 하나의 의견이 달렸다.
위와 같이, YAGNI의 원칙이라는 소프트웨어 개발 원칙을 언급해주시며,
확장성은 미리 구현하는 것이 아닌 새로운 기능이 필요해졌을 때 쉽게 추가할 수 있는 구조를 만들어놓는 것이라고 조언해주셨다!
☑️ YAGNI의 원칙?
💡 1주차 총평
🎯 객체지향적인 설계란?
🎯 객체 간의 역할과 책임을 적절하게 분배하여 상호작용을 이루었는가?
🎯 Model의 생성 위치?
🎯 일급객체를 사용할 때의 주의점
🎯 YAGNI의 법칙
코드를 짜면서 겪었던 수많은 난관들을 마주하고 극복을 하며 성장하는 내 자신을 바라볼 수 있었다.
또한, 미션이 끝난 후 다른사람들과 코드 리뷰하는 과정에서,
내가 겪었던 난관들에 대해 이야기를 나눌 수 있었으며, 지식을 더 견고하게 다지는 시간을 가질 수 있었다.
또한 다른 한편으로는 다른 사람들의 코드 짜는 방식들을 보고
하나의 문제에 대한 다양한 관점으로 바라볼 수 있었고,
그들의 시선에서 리뷰하면서 생각을 확장할 수 있는 계기가 되었다.