오늘은 평소에 사소하게 넘어갈 수 있는 부분인 Java의 접근 제한자에 대해서 얘기해보겠습니다.
제가 이 주제에 대해 블로깅을 하기로 결정한 이유는 '내가 여태까지 사용해온 접근제한자 활용 패턴이 잘못 됐을 수도 있겠다. 더해서 우리 팀의 코드를 봤을 때 정말 접근제한자를 제대로 활용하고 있는지 모르겠다.' 라는 생각이 들었기 때문입니다.
이 글은 Clean Architecture 라는 책의 Simon Brown씨 기고문에 있는 '컴포넌트 기반 패키지' 를 읽고 깨닫고 느낀점을 기반으로 작성했습니다.
우선 접근 제한자는 무엇일까요?
접근 제한자는 OOP 의 특징으로 캡슐화를 지원하기 위한 기능입니다.
예를 들어, 'A 클래스의 메소드를 다른 클래스에서 사용하지 못하게 하고 싶다' 할 때, 자바에서는 private 접근 제한자를 사용해서 다른 클래스가 해당 메소드의 존재를 모르게 할 수 있습니다. 이 개념이 '캡슐화' 입니다!
이렇게 접근 제한자는 특정 메소드 또는 클래스의 존재를 다른 클래스가 알 수 없도록 범위(scope)를 설정하는데 사용됩니다.
위의 예시에서는 private 접근 제한자를 사용했지만, 자바에서는 아래의 4가지 접근 제한자를 제공하고 있습니다.
- public : 어디서든 접근 가능
- protected : 같은 패키지 또는 상속받은 자식 클래스에서 접근 가능
- default : 같은 패키지 내에서만 접근 가능
- private : 자기 자신(클래스) 내에서만 접근 가능
근데 캡슐화 자체는 왜 필요한걸까요?
굳이 열심히 만들어놓은 클래스와 메소드들을 왜 다른 클래스가 모르게 해야 하는 걸까요?
결론은, 캡슐화는 관계가 전혀 없는 다른 클래스B가 자신(클래스A)의 속성을 함부로 변경하지 못하게 하기 위함입니다.
또한 아키텍처 관점에서는 컴포넌트 간의 경계를 설정할 수 있게 된다는 이점이 있습니다!
예를 들어, 사람이 나이가 드는건 시간의 흐름에 의해서만 가능하죠.
우리집 강아지가 한 번씩 짖을 때마다 나이가 1살 씩 늘어 날 수도 있나요? 아니겠죠?!
그래서 '사람 class'의 'age' 필드변수는 '시간 class'에게는 공개되어 있어야 하지만, '강아지 class'는 모르도록 해야만 합니다.
좋은 예시가 아닌 듯 싶지만.. 그림으로 표현해보면 아래와 같이 됩니다.
![](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
이렇게 캡슐화를 통해서 예기치 못한 경로로부터의 데이터 변경을 막을 수 있고, 코드를 통해 '강아지 클래스에서는 사람 클래스의 나이에 접근하지 말아주세요' 라는걸 표현할 수 있게 됩니다.
자, 제가 여태까지 어떤식으로 접근 제한자를 사용해왔는지에 대해서 말해볼까요.
1. 클래스에 정의된 public 메소드의 동작의 일부인 '세부사항 메소드'은 private 로 설정.
2. private 으로 숨기고 싶은 세부사항이지만, 로직의 중요성이 커서 테스트로 남겨놓고 싶은 메소드는 default 접근 제한자로 설정.
2.1. 이 정도 레벨의 캡슐화의 이점을 잃는 것보다, 테스트를 하지 않음으로써 얻는 피해가 크다고 생각했기 때문. (+ 경험)
3. 특정 그룹군의 클래스에서 메소드를 공유하고 싶은 상황은 protected 또는 default 접근 제한자를 설정.
3.1. 설명이 좀 애매하지만.. 이건 경험상 추상클래스에는 protected를 많이 사용했던 것 같고, 일반적으로는 default를 이용했지만 메소드의 쓰임새에 따라 달라지기 때문에 '이럴 땐 이거!' 라고 딱 정의할 수가 없네요.
4. inner class를 제외한 클래스의 접근제한자는 아무 생각도 하지 않고 public 으로 설정
다시 본론으로 돌아와서,
Simon Brown씨 기고문에 있는 '컴포넌트 기반 패키지' 를 읽고 느낀점을 공유해보겠습니다.
저의 생각에 대해 경험을 공유해주실 수 있는 분이 있으면 함께 고민해보고 토의해볼 수 있었으면 좋겠네요!
Simon Brown씨가 '컴포넌트 기반 패키지' 에서 제시한 문제는 'Controller → Service → Repository 의 계층기반의 구조를 갖춘 프로젝트가 있을 때, 어떤 프로그래머가 Controller에서 Service를 호출하지 않고, 직접 Repository를 호출하는 것을 막을 수 있는 방법이 있는가?' 입니다.
그림으로 봐볼까요?
![](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
그럼 결국 이 구조로는 Controller가 Repository에 접근하지 못하도록 강제할 수 있는 방법이 없습니다.
책에서는 이 문제에 대한 해결 방안으로 Service와 Repository를 같은 패키지에 위치시킨 후, Repository의 접근 제한자를 default로 설정하도록 하였습니다. 이렇게 되면, Controller에서는 Repository의 존재를 알 수 없으니 접근할 방법이 없겠죠?
그러면 어떤 프로그래머가 코딩을 하더라도 Controller에서는 Service만을 통해서 DB 데이터를 조작하도록 강제할 수 있습니다.
만약 Controller에서 Repository를 호출한다면 컴파일러한테 혼나겠죠.
![](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
이 구조를 코드로도 구현해봤습니다!
https://github.com/flowertaekk-dev/general/tree/main/java/playground
Simon Brown씨 기고문을 읽기 전의 저였다면, 음... ServiceImpl 클래스 내부에 inner class를 private로 만들어서 Controller는 Repository를 모르게 하는 방법도 있었겠지만, 이렇게 하면 업무로직(Service)와 DB(Repository)와의 결합이 생겨버리고, 또 다른업무 로직에서 같은 DB에 접근하기 위한 코드 중복이 불가피하기 때문에..
모두 public으로 해놓고 다른 프로그래머가 제가 의도한대로 코딩해주기를 믿지 않았을까 싶습니다.
이 기고문의 '컴포넌트 기반 패키지' 파트를 읽으면서, '당연하다고 생각해왔던 public 의 사용이 결국 남용이 아니었을까?' 라는 생각과 함께 많은 고민을 하게 되었습니다.
물론 이러한 위의 구조가 절대적으로 옳다고는 생각하지 않고, 설계자의 상황과 의도에 따라 다양한 아키텍처를 만들어 낼 수 있다고 생각하지만, 평소에 'inner class 가 아닌 class의 접근 제한자는 당연히 public이다' 라고 당연히 생각해왔던 것에 대해 반성과 다시 한 번 접근 제한자에 대해 고민해볼 수 있는 좋은 시간이었던 것 같습니다.
'개발' 카테고리의 다른 글
target="_blank" 다른 탭으로 링크 이동하기 (0) | 2021.11.12 |
---|---|
Jest 사용법 (Usage) (0) | 2021.05.30 |
프론트엔드 vs. 백엔드 테스트 (0) | 2021.05.22 |
concourse 테스트 (0) | 2021.05.05 |
circle-ci 테스트 (0) | 2021.04.30 |