일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- spring boot
- 이펙티브 자바
- Spring Batch
- @componentScan
- OSIV
- 불변 객체
- Request flow
- mavenCentral
- Batch
- 참조 타입
- 필드 주입
- @Configuration
- Handler Adepter
- View Resolver
- 생성자 주입
- open session in view
- @FunctionalInterface
- Open EntityManager In View
- 싱글 스레드
- 메서드 주입
- 스프링 빈
- 컴포넌트스캔
- 가변 객체
- @Bean
- 일괄처리
- 빈
- 익명 함수
- Spring Framework
- Dispatcher Servlet
- open-in-view
- Today
- Total
보다 더 나은 내일의 나를 위해
클래스를 래핑 한다는 것 본문
서론
여러분은 프로그래밍을 하면서 '객체를 래핑 한다', '클래스를 래핑 한다'라는 말을 들어본 적 있나요? 만약 들어보지 못했다고 해도 괜찮습니다.
오늘은 클래스를 래핑 하는 방법과 사용하는 이유에 대해서 알아볼 것입니다.
Wrapping
wrapping을 직역하면 어떤 뜻인가요? 말 그대로 '싸다', '포장하다' 정도로 해석할 수 있습니다.
그렇다면 '클래스를 래핑 한다'라는 말은 어떻게 해석할 수 있을까요? 이 역시 클래스를 포장해서 사용한다고 볼 수 있습니다.
그럼 이 래핑은 어떻게 할까요?
기본적으로 다음과 같이 래핑 할 수 있을 것입니다.
class Food {
}
class FoodWrapper {
private final Food food;
public FoodWrapper() {
this.food = new food;
}
}
Food 클래스를 FoodWrapper 클래스로 포장했습니다.
이 방식을 사용하면 Food 클래스에서 제공하는 메소드를 사용하기 위해서는 위임을 통해 해결할 것입니다.
class Food {
public String favor() {
return "hot";
}
}
class FoodWrapper {
// ...
public String favor() {
food.favor();
}
}
또한 상속을 받아 자식 클래스로써도 활용할 수 있을 것입니다.
class Food {
public String favor() {
return "hot";
}
}
class FoodChild extends Food {
}
그렇다면 굳이 클래스를 래핑해서 사용하는 이유는 무엇일까요?
캡슐화
캡슐화를 통해서 구현 로직을 은닉할 수 있습니다.
만약 타겟 클래스에 인스턴스를 수정하는 메소드가 있고, 개발자가 해당 기능의 사용을 막고 싶을 때 사용할 수 있을 것입니다.
class Food {
public void 수정클래스() {
// ...
}
}
class FoodWrapper {
// ...
protected void 수정클래스() {
food.수정클래스();
}
}
기능의 확장성
또한 이렇게 사용한다면 필요에 따라 추가되어야 할 로직을 개발자가 손쉽게 다룰 수 있습니다.
이것은 위임을 통하면 메소드에 쉽게 기능을 추가할 수 있을 것입니다.
class Food {
public void printKg(String s) {
System.out.println(s);
}
}
class FoodWrapper {
// ...
public void printKg(String s) {
s += " Kg";
food.printKg(s);
}
}
class FoodChild extends Food {
@Override
public void printKg(String s) {
s += " Kg";
super.printKg(s);
}
}
마치 프록시 패턴처럼 사용하는 것입니다.
또한 기존 메소드를 개발자의 편의성을 더 올려주도록 만들 수도 있습니다.
class Food {
public void makeFood(String 재료1, String 재료2, String 재료3 ...) {
// ...
}
}
class FoodIngredientDto {
String 재료1;
String 재료2;
...
}
class FoodWrapper {
// ...
public void makeFood(FoodIngredientDto ingredient) {
// ...
}
}
위 예시에서는 여러 인자를 따로 받는 makeFood() 메소드를 Dto 하나만을 활용해 해결할 수 있도록 하는 편의 메소드를 래퍼에 만들었습니다.
이 외에도 사용 방법에 따라 기능, 편의성 등을 추가할 수 있을 것입니다.
레이어 관리
이 생각은 다음 영상을 보며 생각했습니다.
토스ㅣSLASH 22 - 지속 성장 가능한 코드를 만들어가는 방법
아예 다른 레이어 또는 패키지에 있는 클래스를 래핑 클래스로 묶어서 관리한다면 래이어 침범을 막을 수 있습니다. 따라서 레이어 구성에도 용이할 것입니다.
또한 라이브러리 같은 아예 다른 패키지를 사용하는 유틸리티 클래스를 사용하는 상황에서, 비즈니스 로직을 작성하는 클래스에서 import 부분을 살펴볼 때 좀 더 비즈니스 종속적으로 관리할 수 있습니다.
종속성
마지막으로 종속성 관리입니다.
만약 현재 프로젝트에서 문자열을 변환해주는 유틸리티 라이브러리를 사용하고 있습니다. 보통 비즈니스 로직에 해당 유틸리티 라이브러리를 바로 사용했을 것입니다. 하지만 이 상황에선 라이브러리에 문제가 생겨 교체 혹은 직접 만든 로직을 사용하려고 할 때 비즈니스 로직 전체를 수정해야 하는 경우가 발생할 수 있습니다.
이런 상황을 방지하기 위해서 유틸리티 라이브러리를 래핑 해서 사용할 수 있습니다.
래핑을 사용한다면 기존 비즈니스 로직에서 사용하던 유틸리티 함수들의 명세는 그대로 두고, 문제가 되는 비즈니스 로직만 수정해 사용할 수 있습니다. 또는 라이브러리 교체를 쉽게 할 수도 있죠.
package ekfmsep;
// 외부 라이브러리
class Library {
public String makeSomeFoods() {
return "hamburger, pizza, chicken"
}
}
package durl;
import ekfmsep.Library;
class Service {
private Library library;
public String cooking() {
return library.makeSomeFoods();
}
}
위 코드를 보면 ekfmsep에 존재하는 외부 라이브러리를 Service 내에서 사용하고 있습니다.
만약 해당 라이브러리의 makeSomFoods()에서 리턴해 주는 값이 한식이 필요하다고 피드백이 들어왔습니다. 따라서 한식을 리턴해 주는 또 다른 라이브러리로 교체하려고 합니다.
package aksgdlekfmsrj;
class KoreanFoodLibrary {
public String getFoods() {
return "bulgogi, yukgaejang"
}
}
package durl;
import aksgdlekfmsrj.KoreanFoodLibrary;
class Service {
private KoreanFoodLibrary library;
public String cooking() {
return library.getFoods();
}
}
결국 aksgdlekfmsrj에 존재하는 KoreanFoodLibrary를 가져다 사용했습니다.
이 라이브러리를 사용하기 위해서 import 문도 바꾸고, 전역 변수 객체도 변경하고, 로직에서 함수 명도 변경하였습니다. 솔직히 이 정도 변경은 금방 할 수 있습니다. 하지만 이전 라이브러리를 사용하던 클래스가 많거나 이전 라이브러리의 함수를 많이 사용하고 있었다면 얼마나 수정해야 했을까요? 참 귀찮았을 것입니다.
그렇다면 이번엔 래핑을 해보겠습니다.
package lib;
import ekfmsep.Library;
class FoodLibrary {
private Library library;
public String makeSomeFoods() {
return library.makeSomFoods();
}
}
package durl;
import lib.FoodLibrary;
class Service {
private Food library;
public String cooking() {
return Food.makeSomeFoods();
}
}
Library를 래핑해서 FoodLibrary를 만들었습니다.
Service에서는 Library를 래핑 한 FoodLibrary를 사용해서 Library의 함수를 호출하고 있죠.
그럼 여기서도 KoreanFoodLibrary로 변경해봅시다.
package lib;
import aksgdlekfmsrj.KoreanFoodLibrary;
class FoodLibrary {
private KoreanFoodLibrary library;
public String makeSomeFoods() {
return library.getFoods();
}
}
package durl;
import lib.FoodLibrary;
class Service {
private Food library;
public String cooking() {
return Food.makeSomeFoods();
}
}
FoodLibrary라는 래핑 클래스는 수정되었지만, Service 클래스는 수정된 사항이 없습니다.
만약 위와 같이 한 번만 쓰거나 많이 쓰지 않는다면 굳이 래핑 클래스를 사용한다는 것은 낭비일 것입니다.
하지만 비즈니스 로직에서 여러 번 사용한다면 래핑 클래스를 사용했을 때 래핑 클래스만 수정하고, 비지니스 로직쪽은 전혀 수정할 부분이 없기 때문에 편의성과 확장성은 극대화될 것입니다.
두 가지 방식 중
앞에서 알아봤던 방식에는 2가지가 있었습니다. 그렇다면 어떤 방법을 사용하는 것이 좋을까요?
당연히 상황에 맞춰서 사용할 것입니다. 그럼 어떤 상황에서 사용하면 좋을지 봅시다.
필드 래핑
첫 번째로 소개한 래핑 방법입니다.
우선 이 방법을 사용하면 필요한 메소드만 제공할 수 있습니다.
개발 상황에서 꼭 모든 메소드를 제공해야 하는 것은 아닙니다. 따라서 메소드 사용에 제한을 하려고 할 때 필요한 메소드만 위임하면 되므로 손쉽게 제한할 수 있습니다.
또한 타겟 클래스의 필드에 대한 접근도 막을 수 있습니다. 클라이언트는 타겟 클래스를 래핑 한 클래스를 사용하기 때문에 래핑 클래스에서 따로 가능하게 해주지 않는 이상 타겟 클래스에 접근할 수 없게 됩니다.
필드 래핑 방법은 타겟 클래스에 접근을 제한하고 싶을 때 유용하게 사용될 것입니다.
상속
두 번째 방법인 상속입니다.
이 방법을 사용하게 되면 타겟 클래스에서 제공하는 메소드들을 그대로 상속받기 때문에 굳이 자식 클래스에 작성할 필요 없이 바로 사용할 수 있습니다.
또한 필요에 따라 오버라이딩을 통해 로직을 원하는 대로 수정할 수 있습니다. 필요에 따라 로직을 수정한다는 것은 필드 래핑 방법에서도 충분히 할 수 있습니다. 굳이 타겟 클래스에게 위임하는 것이 아닌 함수를 새로 작성하는 것으로 말이죠. 하지만 오버라이딩을 활용하는 것이 타겟 클래스에서 제공하던 기능이라는 것을 보다 더 직관적으로 알려줄 수 있습니다.
상속 방법은 타겟 클래스의 메소드를 손쉽게 제공하고 싶을 때 유용하게 사용할 수 있습니다.
만약 다른 의견이나 지적할 부분은 댓글로 알려주세요 :)
'개발' 카테고리의 다른 글
오버라이딩, 오버로딩 그 차이 (2) | 2022.08.16 |
---|---|
dependency 찾기 (0) | 2022.07.30 |
제약있는 설계가 좋을까 나쁠까 (0) | 2022.07.04 |