추상화 Abstraction란?
클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업을 말합니다.
상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 추상화는 기존 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것이라고 할 수 있습니다.
- 추상화 적용 전
class Marine {
int x, y;
void move(int x, int y) {
//지정된 위치로 이동
...
}
void stop() {
//현재 위치에 정지
...
}
void stimPack() {
//스팀팩 사용
...
}
}
class Tank {
int x, y;
void move(int x, int y) {
//지정된 위치로 이동
...
}
void stop() {
//현재 위치에 정지
...
}
void changeMode() {
//공격모드로 변환
...
}
}
class Dropship {
int x, y;
void move(int x, int y) {
//지정된 위치로 이동
...
}
void stop() {
//현재 위치에 정지
...
}
void load() {
//물건을 실음
...
}
void unload() {
//물건을 내림
...
}
}
- 추상화 적용 후
//추상 클래스
abstract class Unit {
int x, y;
abstract void move(int x, int y); //추상 메서드
void stop() {
//현재 위치에 정지
...
}
}
class Marine extends Unit {
//추상메서드 오버라이딩
void move(int x, int y) {
//지정 위치로 이동
...
}
void stimpack() {
//스팀팩 사용
...
}
}
class Tank extends Unit {
//추상메서드 오버라이딩
void move(int x, int y) {
//지정 위치로 이동
...
}
void changeMode() {
//공격모드로 변환
...
}
}
class Dropship extends Unit {
//추상메서드 오버라이딩
void move(int x, int y) {
//지정 위치로 이동
...
}
void load() {
//물건을 실음
...
}
void unload() {
//물건을 내림
...
}
}
class AbstractTest {
public static void main(String args[]) {
Unit[] group = new Unit[3];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new Dropship();
for (int i=0; i<group.length; i++) {
group[i].move(100, 200);
}
}
추상화를 통해 공통 조상 클래스인 Unit을 정의함으로써 Unit 클래스 참조변수 배열을 통해 서로 다른 인스턴스를 하나의 묶음으로 다룰 수 있습니다.
다형성을 활용해서 조상 클래스타입의 배열에 자손 클래스의 인스턴스를 담을 수 있습니다.
추상화한 공통조상이 없었다면 이렇게 하나의 배열로 다룰 수 없습니다.
Object 클래스 타입의 배열로 묶을 수 있지만, Object 클래스에는 move()메서드가 정의되어 있지 않기 때문입니다.
추상 클래스 Abstract Class란?
추상메서드를 포함하고 있는 클래스를 추상 클래스라고 합니다.
클래스를 설계도로 비유한다면, 추상 클래스는 미완성 설계도라 할 수 있습니다.
따라서 추상 클래스로 인스턴스를 생성할 수 없으며, 상속을 통해서 자식 클래스에 의해서만 완성될 수 있습니다.
클래스 선언부 앞에 abstract 제어자를 붙여 추상 클래스로 지정할 수 있습니다.
* 추상메서드를 포함하고 있지 않은 클래스이더라도 선언부 앞에 abstract를 지정해서 추상 클래스로 지정할 수도 있습니다. 이 역시 클래스의 인스턴스를 생성할 수 없습니다.
추상 메서드 Abstract Method란?
메서드의 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨둔 메서드입니다.
즉, 메서드에 대한 설계만 해두고 수행될 내용은 작성하지 않은 상태입니다.
메서드의 내용이 상속받는 클래스 따라 달라질 때 추상 메서드를 사용합니다.
부모 클래스에서는 선언부만 작성하고, 주석을 통해 어떤 기능을 수행할 목적인지만 명시하고, 실제 내용은 상속받는 자식 클래스에서 구현하도록 비워둡니다.
메서드 선언부 앞에 abstract 제어자를 붙여 추상 메서드를 지정할 수 있습니다.(구현부 {} 없음)
/* 주석: 어떤 기능을 수행할 목적으로 작성되어야 하는지를 명시*/
abstract 반환타입 메서드이름();
추상 클래스로부터 상속받는 자식 클래스는 오버라이딩을 통해서 추상 클래스의 추상 메서드를 모두 구현해줘야 합니다.
추상메서드 하나라도 구현되지 않으면 자식 클래스 역시 추상 클래스로 지정해줘야 합니다.
//추상 메서드를 포함하는 추상 클래스
abstract class Player {
/* 추상메서드: pos에서 재생을 시작하는 기능이 수행하도록 작성해야 한다. */
abstract void play(int pos);
/* 추상메서드: 재생을 멈추는 기능이 수행하도록 작성해야 한다. */
abstract void stop();
}
//추상 클래스를 상속받는 자식 클래스
class AudioPlayer extends Player {
//오버라이딩
void play(int pos) {
...
}
//오버라이딩
void stop() {
...
}
}
//추상 클래스를 상속받는 자식 클래스, stop() 추상 메서드를 오버라이딩 하지 않았기 때문에 이 역시 추상 클래스
abstract class AbstractPlayer extends Player {
//오버라이딩
void play(int pos) {
...
}
}
* 추상 메서드의 사용 목적
아래와 같이 구현부를 비워놓아도 되는데 추상 메서드를 사용하는 이유가 궁금해집니다.
class Player {
void play(int pos) { }
void stop() { }
}
abstract를 굳이 사용하는 이유는 자손 클래스에서 추상 메서드를 반드시 구현하도록 강요하기 위해서입니다.
위와 같이 빈 구현부로 정의하면 상속받는 자식 클래스는 이 메서드들이 모두 구현된 것으로 인지하고 오버라이딩을 하지 않을 수도 있기 때문입니다.
추상 메서드로 지정하면 자식 클래스에서 해당 메서드를 필수로 오버라이딩 해줘야하기 때문에 자신의 클래스에 알맞는 구현부를 작성할 것입니다.
인터페이스 Interface란?
일종의 추상 클래스로, 추상 클래스보다 추상화 정도가 더 높습니다.
인터페이스는 구현부를 갖춘 일반 메서드나 멤버변수를 가지지 않으며, 오직 추상 메서드와 상수만 멤버로 가집니다.
인터페이스는 interface 키워드로 지정하며, 클래스처럼 public, default 접근제어자를 사용할 수 있습니다.
추상 클래스를 미완성 설계도라 한다면, 인터페이스는 밑그림만 그려져 있는 기본 설계도라고 할 수 있습니다.
인터페이스 또한 추상 클래스처럼 그 자체만으로 사용되지 않고 다른 클래스를 작성하는데 도움을 주기 위해 사용됩니다.
- 인터페이스의 조건
interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(메서드 매개변수);
}
- 모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있습니다.(모든 곳에서 사용되는 상수인 클래스 변수)
- 모든 메서드는 public abstract이어야 하며, 이를 생략할 수 있습니다.(모든 곳에서 사용되는 추상 메서드)
* JDK 1.8부터 인터페이스에 static 메서드와 default 메서드의 추가를 허용합니다.
생략된 제어자는 컴파일러가 컴파일 시에 자동적으로 추가해줍니다.
- 인터페이스의 장점
1. 개발시간을 단축시킬 수 있습니다.
메서드를 호출하는 쪽에서는 메서드 내용에 관계없이 선언부만 알면 호출할 수 있고, 동시에 다른 쪽에서는 인터페이스를 구현하는 클래스를 작성하여 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 진행할 수 있습니다.
2. 표준화가 가능합니다.
프로젝트에 사용되는 기본 틀을 인터페이스로 작성하고 개발자들에게 인터페이스를 구현해서 프로그램을 작성하도록 함으로써 일관되고 정형화된 프로그램 개발이 가능합니다.
3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
서로 상속관계에 있지도 않고, 같은 조상 클래스를 가지고 있지 않은 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있습니다.
4. 독립적인 프로그래밍이 가능하다.
인터페이스를 이용하면 클래스의 선언과 구현을 분리시킴으로써 실제 구현에 독립적인 프로그램을 작성하는 것이 가능합니다.
클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않도록 독립적인 프로그래밍이 가능합니다.
만약 특정 데이터베이스를 사용하기 위한 클래스를 사용해서 프로그램을 작성했다면, 다른 종류의 데이터베이스를 사용하기 위해서는 전체 프로그램 중에서 데이터베이스와 관련된 모든 코드를 수정해줘야 합니다.
그러나 데이터베이스 관련 인터페이스를 정의하고 이를 이용해서 프로그램을 작성한다면, 데이터페이스의 종류가 변경되더라도 프로그램을 변경하지 않도록 할 수 있습니다.
자바에서는 여러 데이터베이스와 관련된 다수의 인터페이스를 제공하기 때문에 이 인터페이스를 사용해서 프로그램이 하면 특정 데이터베이스에 종속되지 않는 프로그램을 작성할 수 있습니다.
- 인터페이스의 상속
인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중 상속이 가능합니다.
인터페이스는 static 상수만 정의할 수 있기 때문에 부모 클래스의 멤버변수와 충돌하는 경우가 거의 없으며, 충돌되더라도 클래스 이름을 붙여서 구분이 가능합니다.
* 인터페이스는 클래스와 달리 Object 클래스와 같은 최고 조상은 따로 없습니다.
interface Movable {
/* 지정된 위치로 이동하는 기능의 추상 메서드*/
void move(int x, int y); //public abstract void move(int x, int y);
}
interface Attackable {
/* 지정된 유닛을 공격하는 기능의 추상 메서드*/
void attack(Unit u); //public abstract void attacj(Unit u);
}
interface Fightable extends Movable, Attackable {
}
* 참고: 인터페이스의 다중 상속 http://www.tcpschool.com/java/java_polymorphism_interface
- 인터페이스의 구현
인터페이스도 추상 클래스처럼 그 자체로는 인스턴스를 생성할 수 없고, 추상메서드를 구현해주는 클래스를 작성해줘야 합니다.
인터페이스를 구현한다는 의미에서 implements 키워드를 사용합니다.
구현하는 인터페이스의 메서드 중 일부만 구현할 수 있으며, 이 경우 abstract를 붙여서 추상클래스로 선언해줘야 합니다.
class Fighter1 implements Fightable {
public void move(int x, int y) {
...
}
public void attack(Unit u) {
...
}
}
abstract class Fighter2 implements Fightable {
public void move(int x, int y) {
...
}
}
상속과 구현을 동시에 할 수 있습니다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y) {
...
}
public void attack(Unit u) {
...
}
}
인터페이스를 구현하는 클래스에서는 인터페이스에 public abstract 제어자가 생략되어 있다고 하더라도, 오버라이딩할 때에는 조상의 메서드보다 같거나 넓은 범위의 접근 제어자를 지정해야하기 때문에 public을 반드시 접근 제어자로 명시해줘야 합니다.
- 인터페이스를 이용한 다형성
인터페이스는 이를 구현한 자식 클래스의 조상입니다.
따라서 인터페이스 타입의 참조변수로 이를 구현한 자식 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능합니다.
Fightable f = (Fightable) new Fighter(); //Fightable f = new Fighter();
또한, 인터페이스는 메서드의 매개변수의 타입으로도 사용될 수 있습니다.
인터페이스 타입의 매개변수는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 함을 의미합니다.
void attack(Fightable f) {
...
}
인터페이스를 메서드의 반환타입으로 지정하는 것도 가능합니다.
리턴타입이 인터페이스라면 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미합니다.
Fightable method() {
...
Fighter f = new Fighter();
return f;
}
참고
- 참고 도서: Java의 정석 - 남궁성 지음
'Java' 카테고리의 다른 글
내부 클래스 Inner Class (0) | 2022.05.24 |
---|---|
다형성 Polymorphism (0) | 2022.05.23 |
제어자 Modifier (0) | 2022.05.20 |
super와 this (0) | 2022.05.20 |
상속 Inheritance (0) | 2022.05.20 |