java generic을 쓸 때 type을 range로 제공할 수 있는 방법이 있을까요?

2015-08-24 12:23

예전부터 고민하던 부분인데,

서로 캐스팅은 안되는데, 추상화하기도 좀 애매한. 그런 경우에 generic 을 쓸 때 어떻게 하나요?

예를 들어,

Map<K, V> askWallet(User user)

가상으로 지갑이라는 개념을 조회하는 함수입니다.

지갑 안에는 카드(Card)가 있을 수 있고, 개인 아이덴티티(IDCard)가 있을 수 있고, 화폐(Money)가 있을 수 있습니다.

종류는 단 3가지로 정의된다고 가정합니다.

이때 반환타입을 정의할 때.

Map<K, V> 를 어떻게 정의하면 좋을까요?

Card, IDCard, Money 를 가지고 위에 무엇으로 추상화시키기엔 좀 애매하잖아요?

보통 Map<String, Object> 코드를 종종 보아왔는데.

Map<String, Card | IDCard | Money> 이렇게 range 로 정의할 수는 없나 생각해봅니다...

덧1) 하나의 프로퍼티로 가질 수 없다는 가정을 내립니다. 예를 들어 WalletItem이란 클래스가 있고 그 안에 Card, IDCard, Money 를 프로퍼티로 정의할 수 없다고 가정합니다. (경우가 다르다고 생각해주시면 감사하겠습니다..)

덧2) 위의 WalletItem 이란 클래스가 있고 Wallet 안의 것들을 프로퍼티로 가질 수 없다는 가정을 내린 이유를 적어봅니다. 예에서 든 askWallet 함수는 지갑안의 항목들에 대해 그것이 실재하든, 실재하지 않든 조회하는 항목입니다. Wallet 안에는 Card, IDCard, Money 가 있을 수 있지만, 반드시 있으리란 보장은 없습니다. 전 이런 경우에 대해선 bean 보다는 list, map 같은 collection 을 선호합니다. 또한 Card, IDCard, Money 외에 영수증이라던가 다른 항목이 추가될 부분에 대해서도 bean은 꽤 유지보수가 비싼 코드가 되어집니다. 위의 Map<String, Card | IDCard | Money> 에 대해서는 또 다른 OR 조건만 추가가 되면 되는 데에 반해서요. 어쩜 예전에 있었던 bean - collection 에 대해 옳다 그르다 의견을 불러일으킬 수 있겠지만... 개인적으론 container 성격에 대해선 collection 이 옳다고 보며, 코드의 안정성에 대해선 bean 이 매우 옳다고 생각합니다.

BEST 의견 원본위치로↓
2015-09-02 13:43

@한석봉 덕분에 많이 배우는건 너 혼자 알아서 잘 배운듯. ㅋㅋ 고민하다보니 내 질문 자체가 너무 케이스바이케이스에 따라 확연히 달라지는 답변들이 될 것 같구나. interface, annotation, 혹은 WalletItem 이라는 컨테이너 성격의 클래스(bean 이라고 볼 수 있겠지?) 모두 다 가능한듯.... 상황을 덜 고민하고 질문한게 이 질문의 문제인듯 ㅋㅋ 비온다~ 파전에 막걸리가 땡기는구만. 다음주에 분당 사람들 꼬드겨서 한 잔 하자.

9개의 의견 from SLiPP

2015-08-24 16:53

Card, IDCard, Money가 bean으로 등록되어야 하는 성격인가요? 개인적인 의견으로는 WalletItem이란 인터페이스로 추상화가 가능하지 않을까 싶습니다. 각각의 개체의 필드를 외부에서 알아야할 필요가 없이, 인터페이스를 통해 호출이 가능해보여서요!! 유지보수의 측면에서도 어떤 개체가 Wallet에 속할 수 있다면 단순히 implements만 해주면 되니까 크게 부담될것 같지도 않구요~ 만약 각각의 개체가 달리 구분되어야 하는 특수한 상황이라면 개인적인 취향으로는 Enum 클래스를 활용하구요 :)

2015-08-24 20:21

@한석봉 음.. 물론 정답이 없고 서로 개인의 취향에 따를 수 있을듯 합니다만, 현재의 java 스펙에 있어서는 Bean 을 만드는 방법뿐이 없겠다 싶네요. 본문엔 싫다고 했으나 코드의 안정성을 위해서라면요...ㅠㅠ Enum 은 Map 의 Key 로 저도 종종 쓰고 있습니다. 편하고, 효율적이란 생각이 들어서요 ㅎㅎ 답변 감사합니다. ㅎㅎ

2015-08-26 01:31

@kimmunsu 혹시 어떤 상황인지 조금만 더 설명해주실 수 있을까요? 재미있어 보여서요~ ㅎㅎ; 캐스팅을 통해 외부에서 어떤 로직을 처리해야 하는지 궁금합니다!

만약에, 외부에서 특수하게 처리해야하는 로직이 있다면 Interface를 통해서 내부동작방식을 주입할 수 있지 않을까 싶어서요~

askWallet이라는 메소드명을 미루어 비추어볼때 내부적인 상태정보값을 리턴해야한다고 할 경우, (리턴 데이터는 Json이 될지, 어떤 특수데이터가 될지는 모르겠습니다) 객체내부에서 비즈니스로직을 충분히 수행 가능할거 같아서요 :)

2015-08-28 11:05

@한석봉 음... 상황은.. 예문과 같습니다 ㅎㅎ 현재 자바 스펙상에선 안되는걸 억지를 피우는 얘기이긴 합니다만... 숲을 보지 않고 나무만 본다는 가정을 하고(그리고 자바코드 안에서만을 가정하고), 썰을 풀자면 아래와 같습니다. 현실에선 지갑안에 넣을 수 있는게 매우 다양합니다. (영수증, IDCard, CreditCard, 종이화폐, 동전, 기타..) 지갑을 자주 조회하는 사람이야, 지갑안에 무엇이 있을지 알 수 있겠지만. 지갑정리를 안하고 혹은 떨어진 지갑을 주운 사람이 지갑을 열었을 때 무엇이 있을지 예상은 할 수 있지만 반드시 있으리란 보장은 없죠. 이런 상황을 askWallet() 이란 메소드로 구현한다고 하면 이 메소드가 반환해야하는 타입은 무엇이 있을까 고민했을때, 전 Map이라 하고 싶었습니다. 무엇이, 혹은 그것이. 있으리란 보장이 없기때문에 iterator 를 쓰고 싶었죠. ... 음, 적다보니 저런 현실을 반영하기 위해선 말씀처럼 interface 를 고려할 수 있겠군요. 물론 구현은 말씀과 다르게 가겠지만... class 영수증 implements 지갑에넣을수있는 class IDCard implements 지갑에넣을수있는 class CreditCard implements 지갑에넣을수있는, ... Map<key, 지갑에넣을수있는> 이렇게요.

그런데 너무 억지스러운 코드같기도 하네요. 그나마 억지를 피하려면.

class WalletItem { private 영수증 영수증 private IDCard idCard private CreditCard creditCard, ... }

이렇게 컨테이너와 같은 클래스를 만드는 방법이 있을텐데.

이러한 방법밖에 없을지... 묻고 싶었습니다. ㅎㅎ;;;

2015-09-01 17:11

@kimmunsu 아!! 답변감사합니다 :)

아직 많이 부족하고 경험도 없는지라, 궁금한 내용을 질문드린건데 자세히 답변주셔서 감사합니다 ^^ (문수형^^)

제가 궁금했던것은 askWallet() 메서드를 호출하는 곳에서, Map형태로 리턴받은 데이터를 어떻게 활용하는지가 궁금했습니다. 리턴데이터를 어떻게 활용하는지에 따라서 추상화 전략이 달라질거 같아서요!

질문을 한 이유는 다음과 같습니다.

  • 현재 자바문법을 이용해서 외부에서 정확한 타입으로 캐스팅이 필요할 경우 리턴된 Map데이터를 iterator로 변환해서 for루프를 돌면서 모든 데이터를 처리할수는 있을겁니다. for loop안에서 instanceof와 같은 키워드로 구체적인 클래스타입을 if else과정을 통해 알아내서 캐스팅하고 로직을 수행할 수 있긴 하겠지만.. 중복코드나, Wallet에 추가되는 아이템이 발생할때마다 if-else에 비교로직이 들어갈것 같습니다. 유지보수의 어려움이 생길듯 하구요 ^^
void getWalletInfo(User user) {
    Iterator walletItems = user.askWallet().entrySet().iterator();
    while(walletItems.hasNext()){
        Object object = walletItems.next();
        if (object instanceof Card) {
            //카드로직
        } else if (object instanceof IDCard) {
            //아이디카드 로직
        }
        ...
    }
}
  • Range로 자바문법을 지원해줄 경우 만약, Map<String, Card | IDCard | Money>와 같은 문법을 자바에서 지원해준다고 해도 저 데이터를 활용하는 입장에서는 1번과 같은 비교구문을 수행해야하는데, 유지보수 측면에서 알아야 할 내용이 필요이상으로 많을것 같습니다.

개인적인 의견

그래서 말씀해주신것처럼 "지갑에 넣을 수 있는"이라는 항목으로 WalletItem이라는 인터페이스로 아래와같이 추출하고 자료구조가 Map형태가 아닌 List로 바뀐다면 좋지 않을까 생각해봤습니다.

public interface WalletItem {
    //예시
    void doLogic();
}

구현클래스들(Car, IDCard, Money)은 implements 받아서 필요한 로직을 설정하면 되지 않을까 싶었습니다. 만약 구현로직을 추상화하기 힘들다면 Visitor 패턴같은걸 활용할 수도 있을것 같구요 :)

2015-09-02 11:28

@한석봉 흠... 컨테이너의 입장에서만 생각했었는데 클라이언트 코드에겐 꽤 무자비한 생각을 하고 있었네요? ㅋ 사실 클라이언트의 비즈니스에 대해 고민은 안하고 있었던터라... 단지 컨테이너 안의 요소들을 최대한 안정적으로 넣고 싶다란 생각만 했습니다. 그런데, 클라이언트의 비즈니스가 무엇일줄 모르는 상황에서 그 안의 요소가 어떻게 안정적으로 담길지 고민하는거 자체가 좀 애매한 기분이네요? ㅋ 말씀처럼 interface 외엔 딱히 좀 생각나질 않네요.

다만... annotation 을 달아준다면 어떨까 생각해봅니다. @walletItem 이라던가... ㅋㅋ 만약 그렇다면 고민의 집중이 컨테이너가 아니라 클라이언트 코드와 각 요소 개체들 자체에 집중하게 되겠죠?

새로운 생각을 하게 되네요. 컨테이너는 그냥 컨테이너일 뿐이지, 그 안의 요소들의 방향은 요소 자체가 정의(annotation)하고, 그 컨테이너의 요소들을 필요로 하는 클라이언트 코드에서 그 방향에 맞추어 코드를 작성한다는 느낌?

고마워요, (윤성이냐?)

2015-09-02 12:32

@kimmunsu 네형 ㅋㅋㅋ 이런데 올리는 글은 호칭을 어떻게 해야할까 싶어서 존댓말썼는데 어색하네요~ 저는 안드로이드 개발하면서 GCM에서 전달되는 Json데이터를 Usercase별로 어떻게 처리할까 고민했던 기억이 있어서 이것저것 많이 물어봤네요 ㅎ 덕분에 많이 배웠어요~~!

2015-09-02 13:43

@한석봉 덕분에 많이 배우는건 너 혼자 알아서 잘 배운듯. ㅋㅋ 고민하다보니 내 질문 자체가 너무 케이스바이케이스에 따라 확연히 달라지는 답변들이 될 것 같구나. interface, annotation, 혹은 WalletItem 이라는 컨테이너 성격의 클래스(bean 이라고 볼 수 있겠지?) 모두 다 가능한듯.... 상황을 덜 고민하고 질문한게 이 질문의 문제인듯 ㅋㅋ 비온다~ 파전에 막걸리가 땡기는구만. 다음주에 분당 사람들 꼬드겨서 한 잔 하자.

의견 추가하기

연관태그

← 목록으로