본문 바로가기

Web/JAVA

인터페이스와 추상클래스

오늘은 추상클래스와 인터페이스에 대해 좀 더 깊게 알게 되었다.

대학교에서 처음 개념을 잡을때와는 깨달음과 재미의 깊이가 달랐다.


이전까지 추상클래스는 단순히 클래스를 추상화하여 구현없는 껍데기를 정의해놓은 클래스,

인터페이스는 클래스와 달리 인스턴스 변수를 가질 수 없는 즉, 더욱 추상화된 껍데기이며 다중 구현이 가능.

이정도의 개념으로만 이해하고 스프링에서 사용했다.(사실상 복붙)


이후 자바의 정석에서 이 부분을 다시 공부하는데

스타크래프트라는 게임에 비유해서 인터페이스의 강력함을 알게되었다.

확실히 전혀 모르는 상태에서 개념서를 보는것과 1~2독을 하거나 최소한의 개념을 갖춘 상태에서

다시 개념서를 볼 떄 이해력과 재미가 달라지는 것 같다.


추상클래스


추상클래스 자체로 클래스(설계도)의 역할은 다하지 못한다.

단지 새로운 클래스(설계도)를 작성할 때, 아무것도 없는 상태에서 시작하지 않고

어느 정도 틀을 갖춘 상태(추상클래스)를 이용하기 위함이다.

물론 추상클래스도 일반 클래스와 같이 구현, 생성자, 초기화 블럭의 사용이 가능하다.

다만, 인스터스의 생성은 불가능하다.


하나의 큰 카테고리에서 하위의 카테고리들의 설계는 60~80%는 비슷할 것이다.

같은 크기의 냉장고, Tv라고 하더라도 여러 종류의 모델이 있지만

냉장고, Tv의 종류가 다르다고해서 전혀 다른 역할은 하지 않기 때문이다.


* 그냥 조상클래스에 빈 메서드를 만들어 놓고 오버라이딩 하면 되지 않나?

=>  추상클래스는 구현된 메서드와 추상메서드를 모두 정의할 수 있다.

      굳이 abstract키워드를 사용해서 추상메서드를 사용하는 이유는

      상속된 클래스에서 해당 추상메소드의 구현을 강제받기 때문이라고 한다.





인터페이스


인터페이스는 일종의 추상클래스라고 한다. 

추상클래스처럼 추상메서드를 갖지만 추상화의 정도가 보다 높다.

추상화가 더 높기 때문에 추상메서드와 상수를 제외하고는 어떠한 요소도 가질 수 없다.

추상클래스 = 부분적으로 완성된 설계도(미완성) => 어느 정도는 완성 되어있다.

인터페이스 = 밑그림만 끄려져 있는 기본 설계도 => 아예 완성되지 않았다.


* 인터페이스는 Object와 같은 최상위 조상은 없다.

* 인터페이스도 추상클래스와 마찬가지로 인스턴스의 생성이 불가능하다.


1) 다중 상속

Ex)  두 가지 자동차를 이용해서 새로운 자동차를 만들고싶다.

새로운 자동차는 두 가지 자동차의 상태와 기능을 모두 가지면서 새로운 기능을 추가해서 만들고 싶다.


=> 대부분 알고있듯이 자바에서 클래스는 다중 상속을 허용하지 않는다.

     다만 인터페이스를 통해 자둥 상속이 가능하지만, 다중상속을 구현하는 경우는 거의 없다.


=> 기존의 자동차가 A,B이고 새로 만들려는 자동차가 C일 때,

     B에 정의된 기능과 일치하는 인터페이스(IB)를 정의한다.

     이후 C를 만들 때 A를 상속받고 IB를 구현하도록 하면 된다.


 => IB를 구현하기 위해 에 메서드를 새로 작성해야하지만,

      B에 에어컨 기능이 있다고 할 때 IB에 에어컨 기능을 선언하고

      C의 멤버변수로 B클래스의 인스턴스를 포함시킨 뒤

      C에서 IB를 구현하고 구현부에 B의 에어컨 기능만 호출하도록 하면 된다.



2) 다형성




Unit

 -------------------------

GroundUnit                AirUnit

    ------------------                 ----

Scv   Tank    Marine        Battle



스타크래프트에서 테란에는 유닛이 있다.

이 유닛은 다시 지상유닛과 공중유닛으로 구분된다.

지상유닛에는 Scv, 탱크, 마린과 같은 유닛들이 있으며

공중유닛에는 배틀크루저와 같은 유닛들이 있다.


Unit은 Obejct 밑의 최상위 클래스이며 GroundUnit, AirUnit의 조상 클래스이다.

다시 GroundUnitScv, Tank, Marine의 조상 클래스이며

AriUnitBattle의 조상 클래스이다.


이러한 상황에서 실제 게임처럼 SCV는 기계 유닛을 수리할 수 있어야 한다..

위에서 기계 유닛은 탱크, SCV와 배틀로 총 3개의 유닛이 있다.

평소라면 그냥 오버로딩을 이용해 SCV의 인스턴스 메서드 3개를 만들었을 것이다.

repair(Tank t){ }; repair(SCV s); repair(Battle b); 와 같이....


위처럼 만들었다면 객체지향의 장점인 재사용성, 코드의 중복성 제거와 어울리지 않게 된다.

중복을 최소화하고 재사용성을 높이는 방법이 무엇이 있을까?


1. Scv와 Tank는 지상유닛, Battle은 공중 유닛이므로 메서드를 2개로 줄인다.

=> 하나의 메서드로 만들 순 없을까?


2. Repairable이라는 인터페이스를 만들어 수리가 가능한 유닛들이 이를 구현하도록 한다.

interface Repairable { };

class Tank implements Repairable { .......... }

class Battle implements Repairable { .......... }

class Scv implements Repairable { 

.........

void repair(Repairable r) {

if(r instanceof Unit) {
    Unit u = (Unit)r; // 조상 클래스인 Unit에 정의된 인스턴스 변수를 사용하기 위해 업캐스팅

    ------------ hp 회복 로직 -----------------

}

}

..........

}


인터페이스를 이용해 수리가 가능한 유닛들은 Repairable을 구현하도록 한 것 같다.

나는 이전까지 다형성과 오버로딩을 같은 개념으로 이해했다.

다형성은 "여러 가지의 형태를 가질 수 있는 능력" 이라고 한다.

이는 한 타입의 참조 변수가 여러 타입의 객체를 참조할 수 있다는 의미라고 한다.

이는 상속 관계에서만 허용된다. 즉, 조상클래스 타입의 참조변수로

자손클래스의 인스턴스를 참조할 수 있다는 의미다. (형변환)


위 코드에서는 Repairable의 참조 변수로 Scv, Tank, Battle를 사용할 수 있다는 의미다.

이렇게 repair메서드의 인자로 Repairable의 참조변수를 넘겨 받는 이유는

해당 참조변수와 상속관계에 있지 않은 인스턴스가 넘어오면 오류를 내기 위해서다,


이후에 게임에 새로운 유닛이 추가될 때, 해당 유닛이 수리가 가능하게하려면

단순히 Repairable만 구현하도록 하면 되는 것이다.




public interface BoardService {

EgovMap select(BoardVo boardVo) throws Exception;

}


@Service("boardService")

public class BoardServiceImpl implements BoardService {

@Resource(name="boardMapper")

BoardMapper boardMapper;


public EgovMap select(BoardVo boardVo) throws Exception {

return boardMapper.select(boardVo);

}

}


@Controller

@RequestMapping("/web/board")

public class BoardController {

@Resource(name="boardService")

BoardService boardService;


...

... boardService.select(boardVo)호출

...

}



비교적 최근에 작성한 코드를 일부 확인해봤다.

그저 인터넷 어디선가 작성된 코드를 보고 그대로 만들었을 뿐인 코드다.


Controller에서 Mapper(혹은 DAO)를 바로 이용하지 않고 

인터페이스와 그 구현체인 service, serviceimpl을 둔 이유는 무엇일까..?


1. 클래스를 사용하는 쪽과 클래스를 제공하는 쪽.

2. 메서드를 사용하는 쪽에서는 사용하려는 메서드의  선언부만 알면된다.

    (즉, 구현부를 알 필요가 없다.)


여기서 만약 컨트롤러가 DAO를 직접 사용하면

컨트롤러와 DAO는 직접적인 관계에 있다고 한다.

이러한 직접적인 관계에서 클래스를 제공하는 DAO의 메서드가 변경되었다면?

이를 사용하는 클래스인 컨트롤러도 변경되어야 한다.

이와 같이 제공자가 변경되면 사용자도 변경되야 하는 단점이 있다.


그러나 컨트롤러가 DAO를 직접 사용하지 않고

서비스라는 인터페이스를 매개체로 한다면,

컨트롤러가 서비스인터페이스를 통해서 DAO의 메서드에 접근하게 된다.(간접적인 관계)

즉, 제공자인 DAO가 변경되거나 대체되어도 사용자인 컨트롤러는 전혀 영향이 없다.



매개변수를 통해서 Service인터페이스를 사용할 경우에는 그저 Service를 구현한 클래스의 인스턴스만 넘겨주면 된다.

위에서 serviceImpl을 두는 이유는 service는 인스턴스를 생성할 수 없기 때문에 구현체를 두는 것 같다.



'Web > JAVA' 카테고리의 다른 글

상속과 포함관계  (0) 2017.01.17