슴털이
10
2019-05-28 14:36:24 작성 2019-05-28 15:30:38 수정됨
5
233

스프링 프레임워크에서 사용할 클래스 설계 질문입니다.


현재 웹 페이지 중에서 게시판을 만들려고 합니다 .

게시판 기능에는 전체 게시판 조회 기능과 상세 게시판 조회 기능이 존재합니다 .

이를 위해서 ResultService 라는 인터페이스를 만들어 두고 

전체 조회 기능을 위한 getResultTotalList라는 메소드를 만들고

상세 게시판 조회를 위해 getResultList 라는 메소드를 인터페이스에 만들어 두었습니다.


자 이제 ResultService 인터페이스를 구현할 클래스를 만들었습니다. 


전체 게시판 조회를 구현한 ResultServiceImpl 와 상세 게시판 조회를 구현한 ResultDocServiceImpl(문서함),  ResultAppServiceImpl(전자결재) 등등...

상세 게시판 조회 기능인 부분이 자주 변경 되는 부분이라 저렇게 분리하여 설계를 했습니다 .


문제는 상세 게시판 조회 기능을 구현한 클래스에서 getResultTotalList라는 메소드는 전체 조회 게시판에서만 사용되므로 한번만 사용됩니다. 그러나 ResultService 인터페이스를 구현하므로 어쩔 수 없이 return 값이 null인 메소드를 쓸데없이 구현해야 합니다. 


이러한 상황에서 전체 조회 기능을 위한 인터페이스를 하나더 만들어서 분리하는게 나을까요?

아니면 저렇게 return 이 null 인 메소드를 구현한 것으로 만족해야 하나요?

아니면 어뎁터 클래스를 만들어서 해결해야 하나요?



현재 위와 같이 구현하긴 했습니다...





0
0
  • 답변 5

  • Frudy
    3k
    2019-05-28 14:52:26

    인터페이스의 기본은,

    인터페이스 하나에 이거 저거 메소드 다 넣는게 아니라,


    하나의 인터페이스에 너무많은 용도가 들어있을경우

    인터페이스 여러개로 분리하도록 되어있습니다.


    그래서 인터페이스는 다중상속이 가능합니다.

    분리하는게 좋을거같아요.

    0
  • 제타건담
    6k
    2019-05-28 14:56:22

    흠..전체적인 상황을 몰라서 머라 말씀드리긴 어렵지만..조심스레 첨언을 하자면..

    일반적으로 게시판은 CRUD 베이스 기능은 있습니다..거기서 좀더 추가하자면 전체레코드 갯수 세는 메소드가 있을수 있고..조회 또한 리스트 보기와 상세 보기가 있죠..

    이것은 전체 게시판이든 상세 게시판이든..다 동일한겁니다..(사실 전제 게시판과 상세 게시판이 멀 말하는건지도 모르겠지만..)

    그러면 이 두가지 모두 공통적인 것을 하나의 인터페이스로 잡는거죠..예를 들어 이것을 BaseBoard라 하죠..

    그러면 이제 전체 게시판에만 존재하는 기능과 상세 게시판에만 존재하는 기능 이렇게 2가지가 있을겁니다..

    여기서 관점은 이 해당 게시판에만 존재하는 기능을 인터페이스로 해야 하나..아니면 클래스로 해야 하나..요 관점일꺼에요..

    만약 해당 게시판에만 존재하는 기능을 구현하는 것이 단일한 경우라면 그냥 바로 클래스로 구현해도 됩니다..즉  BaseBoard 인터페이스를 구현한 클래스에 메소드를 추가하는 식으로 구현해도 되죠..

    그러나 단일한 경우가 아닌 경우..즉 이 해당 메소드를 구현해야 하는 방법이 여러개로 나와야 할 상황이 있다..그러면 BaseBoard 인터페이스를 상속받아 다시 새로운 인터페이스를 만드는 겁니다..

    인터페이스가 다시 인터페이스를 상속받는다 해서 특별히 문제될 것은 없어요..실제 스프링에서 제공되는 인터페이스들도 상속받아 구현된 인터페이스가 많이 있습니다..

    이렇게 공통된것끼리 묶고 그것을 계층화를 시켜 나가면 확장도 용이하죠..

    그리고 전자결재게시판..아마 추측엔 전자결재 기능이 있는 게시판인거 같은데..

    이것은 그렇게 구현할 것이 아니라..게시판 안에 결재 기능을 injection 받아서 구현하는게 낫습니다..

    왜냐면 결재와 게시판은 서로 다른 성격의 것인데 그것을 인터페이스로 묶어버리면 강하게 결합되는 상황이 나와서..확장에도 불리해집니다..더군다나 그런 게시판이 여러개 만들어질 경우 결재부분에 대한 수정이 발생했을때 관련 게시판 전부를 수정해야 하는 상황도 오는거죠..

    그렇기때문에 결재는 게시판에서 별도로 작성한 결제 모듈을 injection 받아 구현하는게 낫다고 생각합니다..


    1
  • 슴털이
    10
    2019-05-28 16:03:10

    제타건담님

    그러나 단일한 경우가 아닌 경우..즉 이 해당 메소드를 구현해야 하는 방법이 여러개로 나와야 할 상황이 있다..그러면 BaseBoard 인터페이스를 상속받아 다시 새로운 인터페이스를 만드는 겁니다..

    와 같이 말씀해 주셨는데 


    와 같이 구성해야 한단 말씀이신가요?

    그렇다면 컨트롤러에서 저 서비스를 자동 주입할 때(@Autowired) 어떻게 사용해야 하나요?

    0
  • 제타건담
    6k
    2019-05-28 17:14:56

    인터페이스를 주입받으면 됩니다..조금 더 구체적으로 설명을 드릴께요..

    게시판 얘기가 나왔으니 게시판 스타일로 해보죠..


    public interface ResultService<T, ID> {
        List<T> list(int pageno);
        T view(ID idx);
        void create(T t);
        void update(T t);
        void delete(ID idx);
    }
    
    public interface ResultService1 extends ResultService<T, ID>{
        T myjob1(ID idx);
        void myjob2(ID idx);
    }
    
    public interface ResultDetailService2 extends ResultService<T, ID>{
        T myjob3(ID idx);
        T myjob4(ID idx);
    }
    
    public interface ResultService3 extends ResultService<T, ID>{
        void myjob5(ID idx);
    }
    
    public class ResultService1Impl extends ResultService<Board, Long>{
        List<Board> list(int pageno){}
        Board view(Long idx){}
        void create(Board board){}
        void update(Board board){}
        void delete(Long idx){}
        Board myjob1(Long idx){}
        void myjob2(Long idx){}
    }
    
    public class ResultDetailService2Impl extends ResultDetailService2<Board, Long>{
        List<Board> list(int pageno){}
        Board view(Long idx){}
        void create(Board board){}
        void update(Board board){}
        void delete(Long idx){}
        Board myjob3(Long idx){}
        Board myjob4(Long idx){}
    }
    
    public class ResultService3Impl extends ResultService3<Board, Long>{
        List<Board> list(int pageno){}
        Board view(Long idx){}
        void create(Board board){}
        void update(Board board){}
        void delete(Long idx){}
        void myjob5(Long idx){}
    }


    사진에서의 계층 구조 형태로 한번 제가 소스코드를 대강 짜봤습니다..

    이 구조에서 보면 인터페이스 ResultService는 5개의 메소드가 있고 이를 상속한 인터페이스들도 1개에서 2개 정도 자신만이 갖고 있는 고유 인터페이스가 있으며 이를 Impl 클래스에서 구현했죠..

    그러면 이를 @Autowired로 받을때는 인터페이스로도 받을 수 있고 클래스로도 직접 받을수 있습니다..

    예를 들어

    @Autowired

    ResultService1 resultService1;


    이렇게 받았다면 실제로는 ResultService1 인터페이스의 구현체인 ResultService1Impl 클래스가 injection이 되어서 ResultService1Impl 클래스가 사용할 수 있는 7개(5 + 2)의 메소드를 사용할 수 있습니다..

    이경우는 


    @Autowired

    ResultService1Impl resultService1; 


    이렇게 한 것과 같은거죠..즉 @Autowired를 걸때 걸어주는 타입이 사용할 수 있는 메소드는 쓸 수 있는겁니다..만약 그것이 interface면 interface에 정의된 메소드들은 사용할 수 있는거죠..

    다음과 같이


    @Autowired

    ResultService3 resultService3;


    요러면 ResultService3 인터페이스를 구현한 클래스인 ResultService3Impl 클래스가 Injection이 되어서 총 6개(5+1)의 메소드를 사용할 수 있습니다


    그러면 이러면 어떨까요?

    @Autowired

    ResultService resultService;


    이때는 에러가 발생합니다..왜냐면 ResultService 인터페이스를 구현한 클래스가 현재 3개가 있기 때문에(모든 인터페이스가 ResultService 인터페이스를 상속받았고 이 인터페이스에 대한 구현체가 모두 존재하기 때문) 어떤 클래스를 Injection 해줘야 할지 모르기 때문입니다..Spring에서 이런 내용과 비슷한 에러문구가 뜰거에요..

    그럴때는 @Qualifier란 어노테이션을 같이 사용해줘서 어떤것을 Injection 해주라고 명시해줘야 합니다..


    Injection의 핵심은 내가 어떤 작업을 하기 위해 어떤 클래스를 받아야 하는지..그것을 인터페이스로 받아야 할 경우 어떤 인터페이스로 받아야 하는건지..

    이런거에 대한 것만 분명히 생각해두면 크게 어려운 일이 아닙니다..

    0
  • 로그인을 하시면 답변을 등록할 수 있습니다.