[Java] Abstract , Interface

zl존석동

·

2022. 1. 9. 13:23

추상클래스와 인터페이스에 대해 간단하게 알아보자


추상클래스

인터페이스

 

 

 

 

추상클래스?

 

 클래스가 설계도라면 추상클래스는 '미완성 설계도' 라고 할 수 있다.

 

클래스가 미완성이라는 말은 미완성 메소드(추상 메소드) 가 포함되어있다는 의미를 말하며

 

미완성 설계도로 제품을 완성할 수 없듯이 추상클래스로는 인스턴스화 할 수 없다.

 

public class Main{
	public static void main(String[] args){
		// 추상 클래스는 인스턴스 생성 불가
        // 라면 r = new 라면();
	}	
}

abstract class 라면 {
    int 면;
    int 스프;
    int 물;
    abstract void 조리하다();
}

 

 

추상 클래스만으로는 인스턴스가 될 수 없다는 것을 실생활에 적용해보자

 

실제 세계에 '라면' 이라는게 사실 있을까?? 사실 순수한 '라면' 이라는 '것'은 너무 추상적이지 않나 싶다.

 

어떤 '라면' 이던 신라면, 진라면, 너구리 등등 다양한 형태로 현실에 존재한다.  

 

 

애인이 '라면 먹고 갈래?' 라고 말한다고 쳐도

 

집에 지가 가지고 있는 것중 원하는 어떤 구체적인 라면으로 구현이 되는 것이다.

라면 r = new 신라면블랙();

 

 

 추상 클래스는 추상 클래스를 상속받는 자손 클래스에 의해 인스턴스화 될 수 있는데

 

자손 클래스가 갖추어야할 것들에 대해 미리 설계를 해두고 자손에서 추상 메소드 구현이 강제되는 형태가 되기 때문에

 

자손 클래스들의 생성과 활용에 대한 표준이나 일관성을 부여해줄 수 있지 않나 싶다.

 

마치 사업을 물려주는 부모님이  '이 사업을 반드시 네 것으로 만들어 확장하여라' 라고 말해주는 것 처럼

 

부모 추상 클래스로부터 상속받게 되는 추상적인 기능에 대해 반드시 각 자손마다 재정의 해야 하기 때문에 

 

새로운 자식객체가 생기거나 했을 때 해당 부분을 빼먹고 구현 안한다던가 다르게 구현할 일을 방지해 준다.

 

public class Main{
    pulbic static void main(String[] args){
    	Unit[] unitArr = {new Marine() , new DropShip() , new Marine()};
        
        for(int i = 0 ; i < unitArr.length; i++) {
        	unitArr[i].move(100 , 200);
        }
    }
}

abstract class Unit {
    int x;
    int y;
    abstract void move(int x, int y);
    void stop(){};
}

class Marine extends Unit {
    @Override
    void move(int x , int y){};
    void stimPack(){};
}

class DropShip extends Unit {
    @Override
    void move(int x , int y){}
    void load(){};
    void unLoad(){};
}

 

스타크래프트의 유닛이 움직인다고 생각해보았을 때 

 

사실 '유닛' 이라는 그 자체가 움직일 때 뭘로 얼마나 움직인다고 정의 할 수 있을까?? 

 

그렇기보다는 특정 유닛의 움직임에 대해서 정의를 해줘야 할 것이다.

 

또한 '움직인다' 라는 공통적인 기능을 추상 메소드로 지정하지 않았을 때 자손 클래스에서 깜빡하고 오버라이딩 하지 않을 일이 없다.

 

모든 유닛의 자손들은 속도 또는 이동수단이 다를 것이다. 따라서 추상 메소드로 해당 부분을 지정하고 강제로 구현시키는게 현실에 더 맞는 방법일 것이다.

 

관성이 없다는 가정하에 stop() 이라는 메소드는 모두 같을테니 Unit 클래스에서 일반 메소드로 구현이 되어있다.

 

'이동' 이라는 기능을 가진  move() 라는 공통적인 메소드 이름을 강제로 가지고 활용하기 때문에 다형성 또한 보장되는 모습이다.

 

 

사용 이유라고 한다면..??

 

자손 클래스에서의 추상메소드 구현을 강제한다.

 

  - 최소한의 수정으로 원하는 객체를 사용할 수 있고 정해진 규격이 있기 때문에 유지보수에 좋다.

  - 모든 자손들이 같은 메소드, 다른 구현을 가질 수 있는 다형성이 실현된다.

 

 

 

 

 

인터페이스?

 

 일종의 추상클래스이지만 추상화 정도가 더 높다.

 

자바8부터는 default 메소드와 , static 메소드를 구현할 수 있게 되어 지금은 아니지만

 

근본은 상수와 추상 메소드만 가지고 있는 것이였기 때문에  '기본 설계도' 라고도 한다.

 

클래스는 단 하나의 클래스를 상속받을 수 있었지만 인터페이스는 하나의 클래스에서 여러개 상속이 가능하다.

 

또한 인터페이스는 인터페이스로부터만 extends 키워드로 상속받을 수 있는데 여러개가 가능하다!

 

 

 

왜 사용할까??

 

 추상클래스보다 사용이 유연하다.

 

위의 스타크래프트 예시에서 Marine 클래스는 스팀팩이라는 메소드를 가지고 있다.

 

Firebat 이라는 Unit 의 자손 클래스를 만들 때 스팀팩이라는 메소드를 또 정의해야 하는데 ..

 

class Marine extends Unit {
    @Override
    void move(int x , int y){}
    void stimPack(){}
}

class FireBat extends Unit {
	@Override
    void move(int x , int y ){}
    void steamFack(){}
}

 

 

인터페이스를 사용하지 않았을 때

 

협업하고 있었다면 위의 코드처럼 메소드 이름이 달라지거나 해서 혼란이 올 수 있고

 

그렇다고 부모 클래스에 해당 메소드를 넣어서 상속받아 사용하기에는

 

스팀팩 기능이 없는 다른 유닛들에게도 스팀팩 메소드가 상속되어버려 의도하지 않은 버그를 발생시킬 수도 있다.

 

상속관계에서는 이런식으로 서로간의  결합도 를 상승시키기 때문에 반드시 속해있는 관계(IS-A) 간에만 설정을 해주는 것이 좋을 것이다.

 

class Marine extends Unit implements Dopable {
    @Override
    void move(int x , int y){}
    @Override
    void stimPack(){}
}

class FireBat extends Unit implements Dopable {
    @Override
    void move(int x , int y ){}
    @Override
    void stimPack(){}
}

interface Dopable {
    public abstract void stimPack();
}

 

이런식으로 인터페이스를 통해 메소드의 선언만을 정의하고 구현해야 할 클래스에서 구현해줄 수 있다.

 

마린과 파이어벳이 가져야할 스팀팩 기능의 구현부분이 다르다면 위처럼 각각 구현해서 사용해줄 수 있고

 

같다면 자바8 부터 가능한 default 메소드를 인터페이스에 구현해서 굳이 재정의 하지 않고도 사용할 수 있다.

 

 

다중 상속이 되지 않는 자바의 특성상 인터페이스는 반드시 필요한 개념이기도 한 것 같다.

 

또 스타크래프트로 예시를 들어보겠습니다.

 

사실 스타크래프트는 C++ 로 구현했지? 싶지만 굳이 자바로 확인해봅시다.

 

class SCV extends Unit implements Mechanic, Bionic {
	
}

class SiegeTank extends Unit implements Mechanic {

}

class Marine extends Unit implements Bionic, Dopable {

}

class CommandCenter extends Building imeplements Fixable {

}

interface Mechanic extends Fixable {

}

interface Bionic extends Curable {

}

interface Fixable {

}

interface Curable {

}

 

스타를 해본 사람이라면 바로 이해되었을 것 같습니다.

 

SCV라는 유닛은 바이오닉이면서 메카닉이라 수리도할 수 있고 치료도 가능합니다.

 

커맨드 센터라는 건물수리가 가능합니다. 하지만 메카닉은 아닙니다. 유닛이 아니죠.

 

다중 상속을 통해 하나의 클래스에서 다양한 표준화 된 기능을 받아 사용할 수 있고

 

상속관계가 아닌 클래스들에도 공통된 인터페이스가 구현됨으로써 또 다른 관계가 생길 수 있다 라는 것을 보여주지 않나 싶습니다. 

 

 

 

대략 이런 관계이지 싶다

 

 

 

 

장점이라고 한다면..??

 

 

유지보수 좋아짐, 클래스 간 의존성을 감소 시킴 

 

- 인터페이스가 이미 정의되어 있으면 메소드 호출하는 부분에서는 해당하는 선언부만 알고 있으면 되기 때문에 개발 시간이 단축된다.

- 클래스간 관계를 인터페이스로 연결하면 내부적으로 구현된 부분에 대해 알 필요가 없어 서로 영향을 주지 않는다.

- 구현에 대해 내부적으로 변경이 있어도 외부에 영향을 주지 않아 변경,개선이 자유로워짐 

 

 

표준화

 

- 다수가 작업할 때 인터페이스를 통해 구현이 강제되기 때문에 유사한 객체들에 대해 일관성과 정확성을 줄 수 있다. 

 

 

 관계없는 클래스들 사이에 관계를 준다.

 

- 클래스들이 서로 상속관계가 아니어도 하나의 인터페이스가 공통적으로 구현될 수 있다.

 

 

 

 

 

Ref

 

 

 

Java의 정석 기초편

교육현장에서 뒤쳐지는 학생들을 위해 쓰고, 학생들에게 직접 검증받고 호평받은 책. 코딩을 처음 배우는 사람도 자바를 쉽게 배울수 있게 도와준다.

book.naver.com

 

'Java' 카테고리의 다른 글

[Java] 올바른 Map Iteration  (0) 2022.01.23
[Java] 예외란 뭘까  (0) 2022.01.10
[Java] 람다식이란  (0) 2022.01.06
[Java] 중첩 클래스  (0) 2022.01.05
[Java] 자바는 Call by Value (feat. C++)  (0) 2022.01.04