[그린컴퓨터] Server/JAVA(객체 지향 프로그래밍)

추상 클래스 { 추상 클래스란, 추상 클래스 구현하기, 추상 클래스를 만드는 이유 }

Ben의 프로그램 2023. 5. 31. 21:58
728x90

추상 클래스란?

  • 08장에서 배운 상속을 기반으로 이 장에서는 추상 클래스에 대해 알아보겠습니다. 
  • 추상 클래스는 완전하지 않은 클래스입니다. 
  • 완전하지 않다는 것이 부족하다는 뜻일 수도 있지만, 다른 한편으로는 완성되지 않았기 때문에 가능성이 남아 있다는 의미입니다. 
  • 이 가능성을 활용해 좀 더 확장 가능하고 다양한 프로그램을 개발할 수 있습니다. 
  • 추상 클래스에 대해서 배우기 전에, '추상적이다' 라는 말에 대해서 먼저 생각해볼까요? 
  • 추상적이라는 것은 구체적이지 않고 막연한 것을 뜻합니다. 
  • '구체적이지 않고 막연한 클래스' 라는 것은 무엇을 의미하는 것일까요?
  • 추상 클래스를 영어로 표현하면 abstract class 이고, 추상 클래스가 아닌 클래스는 concrete class 라고 합니다. 
  • 우리가 지금까지 만든 클래스는 모두 concrete class 였습니다. 
  • 추상 클래스 문법부터 배워 보겠습니다. 

추상 클래스 문법

  • 추상 클래스는 항상 추상 메서드를 포함합니다. 
  • 추상 메서드는 무엇일까요?
  • 추상 메서드는 선언부만 가지고 구현부는 가지지 않은 메서드를 의미합니다. 
  • 중괄호 { } 로 감싼 부분을 함수의 구현부(implementation) 이라고 합니다.
  • 이 부분이 없는 함수는 추상 함수(abstract function) 이고 자바에서는 추상 메서드(abstract method) 라고 합니다.
  • 추상 메서드는 다음과 같이 선언만 하며 abstract 예약어를 사용합니다. 그리고 { } 대신 ; 를 사용합니다. 

  • 참고로 int add(int x) {} 는 concrete class 입니다. 구현부에 내용이 없을 뿐 구현부가 있기 때문입니다. 
  • 정리하자면, 자바에서 추상 메서드는 abstract 예약어를 사용하여 선언만 하는 메서드입니다. 

메서드 선언의 의미

  • 우리가 코딩을 한다고 하면 뭔가 열심히 구현하는 것을 생각하기 마련입니다. 
  • 변수를 선언하고 제어문을 사용하여 로직을 만들고 기능을 구현하는 것을 프로그램 개발이라고 생각합니다.
  • 하지만 로직을 구현하는 것보다 더 중요한 것이 어떻게 구현할지를 결정하는 것입니다.
  • 이런 과정을 개발 설계라고 합니다. 
  • 설계 과정에는 다양하고 복잡한 방법들이 있습니다.
  • 예를 들어 이런 경우를 생각해 보겠습니다. 
int add(int num1, int num2);
  • 위 코드를 보겠습니다.
  • 선언한 메서드를 보면 두 개의 정수를 입력받은 후 더해서 그 결가 값을 반환한다는 것을 유추할 수 있습니다.
  • 즉 이 메서드의 선언부(declatration)만 봐도 어떤 일을 하는 메서드인지 알 수 있다는 겁니다. 
  • 함수의 선언부 즉 반환 값, 함수 이름, 매개변수를 정의한다는 것은 곧 함수의 역할이 무엇인지, 어떻게 구현해야 하는지를 정의한다는 뜻입니다. 
  • 따라서 함수 몸체를 구현하는 것보다 중요한 것은 함수 선언부를 작성하는 것입니다. 

추상 클래스 구현하기

  • 구체적으로 구현하지 않은 추상 메서드를 어떻게 사용하는지 하나씩 살펴보겠습니다. 

  • 위의 클래스 다이어그램을 살펴보겠습니다. 
  • 클레스 다이어그램 맨 위쪽에는 클래스 이름을 씁니다. 
  • 그리고 아래쪽에 변수 이름을 쓰고 그 다음에 메서드 이름을 씁니다. 
  • 추상 클레스와 추상 메서드는 기울임꼴로 표시합니다. 
  • Computer 클래스는 추상 클래스입니다.
  • 이를 상속 받은 두 클래스 중 DeskTop 클래스는 일반 클래스이고 NoteBook 클래스는 추상 클래스입니다. 
  • NoteBook 클래스를 상속받은 MyNoteBook 클래스는 일반 클래스입니다. 
  • Computer 클래스가 제공하는 메서드 중 두 개는 기울임꼴 서체로 표시했습니다. 즉 2개는 일반 메서드, 2개는 추상 메서드라는 것을 의미합니다. 
  • 위 다이어그램을 토대로 프로그램을 구현해봅시다. 
public class Computer {
	public void display(); // 오류 발생
	public void typing();
	
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}
	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • display() 메서드와 typing() 메서드에서 오류가 발생하고 있는 것을 알 수 있습니다. 
  • 오류를 해결하는 2가지 방법은 자연스럽게 impletation (body) 부분을 작성하거나 이 메서드를 추상 메서드로 바꾸는 것입니다. 
  • abstract 예약어를 추가하면 추상 메서드를 생성할 수 있습니다. 
public class Computer { // 오류 발생
	public abstract void display(); 
	public abstract void typing();
	
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}
	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • 하지만 이번에는 메서드와 클래스 이름에 모두 오류가 표시됩니다. 
  • 왜냐하면 추상 메서드가 속한 클래스를 추상 클래스로 선언하지 않았기 때문입니다. 
  • 이 오류를 해결하려면 클래스를 abstract 예약어와 함께 추상 클래스로 설정하거나 추상 메서드를 일반 메서드로 만들면 됩니다. 
  • Computer 클래스를 추상 클래스로 만들면 오류가 해결 됩니다. 
public abstract class Computer { 
	public abstract void display(); 
	public abstract void typing();
	
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}
	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • Computer 클래스를 이와 같이 구현한 것은 'Computer 를 상속받는 클래스 중 turnOn( ) 과 turnOff( ) 구현 코드는 공통이다. 하지만 display( ) 와 typing( ) 은 하위 클래스에 따라 구현이 달라질 수 있다. 그래서 Computer 에서는 구현하지 않고, 이 두 메서드 구현에 대한 책임을 상속받는 클래스에 위임한다'라는 의미입니다. 
  • 따라서 Computer 클래스의 추상 메서드는 추상 클래스를 상속받은 DeskTop 과 NoteBook 에서 실제로 구현하게 됩니다. 
  • DeskTop 클래스를 만들어 보겠습니다. 
public class DeskTop extends Computer{ // 오류 발생

}
  • 상속 받은 DeskTop 클래스에 오류가 발생합니다.
  • 오류 메시지를 보면 2가지 오류가 있다고 말해줍니다. 
  • 구현 되지 않은 메서드가 있습니다 (Add unimplemented methods)
  • DeskTop 클래스를 추상 클래스로 만드세요 (Make type 'DeskTop' abstract)
  • 원래 Computer 는 추상 클래스입니다. 
  • 추상 클래스를 상속받은 클래스는 추상 클래스가 가진 메서드를 상속받습니다. 
  • 따라서 상속받은 클래스는 추상 메서드를 포함합니다.
  • 그렇기 때문에 추상 메서드를 모두 구현하여 추상 메서드가 없는 상태로 만들거나, 추상 클래스를 상속받은 하위 클래스도 추상 클래스로 만들어야 합니다. 
public class DeskTop extends Computer{

	@Override
	public void display() {
		System.out.println("DeskTop display()");
	}

	@Override
	public void typing() {
		System.out.println("DeskTop typing()");
	}

}
  • 오류 메시지 중 Add unimplemented methods 를 클릭하면 자동으로 구현해야 하는 추상 메서드들이 생겨납니다.
  • 그 다음 구성하고 싶은 몸체 코드를 작성하면 됩니다. 
  • 마찬가지로 NoteBook 클래스도 구현해봅시다. 
public abstract class NoteBook extends Computer{
	@Override
	public void display() {
		System.out.println("NoteBook display( )");
	}
}
  • NoteBook 클래스에서는 상속받은 추상 메서드를 모두 구현하지 않았습니다. (다이어그램에서 설계한 것에 따라)
  • 그러므로 NoteBook 클래스는 상위 클래스인 추상 클래스로부터 상속받은 추상 메서드가 하나 남아있습니다.
  • 따라서 NoteBook 클래스는 자연스럽게 추상 클래스가 됩니다. 
  • NoteBook 클래스를 상속받는 MyNoteBook 클래스도 구현해보겠습니다. 
public class MyNoteBook extends NoteBook {

	@Override
	public void typing() {
		System.out.println("MyNoteBook typing()");
	}
	
}
  • MyNoteBook 클래스는 상속받은 추상 메서드를 모두 구현했으므로 일반 클래스가 됩니다. 

모든 추상 메서드를 구현한 클래스에 abstract 예약어를 사용한다면? 

public abstract class AbstractTV {
	public void turnOn() {
		System.out.println("전원을 켭니다.");
	}
	
	public void turnOff() {
		System.out.println("전원을 끕니다.");
	}
}
  • 문법상으로 모든 메서드를 구현했어도 abstract 예약어를 사용하면 추상 클래스입니다. 
  • 이 클래스는 모든 추상 메서드를 구현한 클래스입니다. 
  • 하지만 이것으로는 완벽한 TV 기능이 구현된 것이 아니고 TV의 공통 기능만을 구현해 놓은 것입니다.
  • 이 클래스는 생성해서 사용할 목적이 아닌 상속만을 위해 만든 추상 클래스입니다. 
  • 이 경우에 new 예약어로 인스턴스를 생성할 수 없습니다. 

추상 클래스를 만드는 이유

  • 지금까지 추상 클래스를 어떻게 정의하고 구현하는지 이야기했습니다.
  • 그렇다면 이런 추상 클래스는 어디에 사용하기 위해 만드는 걸까요?
  • 예제를 보면서 얘기하겠습니다.
public class ComputerTest {

	public static void main(String[] args) {
		Computer c1 = new Computer(); // 오류 발생
		Computer c2 = new DeskTop();
		Computer c3 = new NoteBook(); // 오류 발생
		Computer c4 = new MyNoteBook();
	}

}
  • Computer 클래스형으로 인스턴스를 4개 생성했습니다. 
  • 코드를 보면 Computer 와 NoteBook 인스턴스에서 오류가 납니다. 
  • 오류 메시지를 보면 다음과 같습니다.

    Cannot instantiate the type Computer : 해당 클래스를 인스턴스로 생성할 수 없습니다.

추상 클래스는 인스턴스로 생성할 수 없다

  • 추상 클래스는 모든 메서드가 구현되지 않았으므로 인스턴스로 생성할 수 없습니다. 
  • 예를 들어 오른쪽과 같은 ABC 클래스가 있다고 가정해봅시다. 
abstract class ABC {
	abstract void a();
	void b() {
		System.out.println("b( )");
	}
}
  • ABC 클래스는 추상 클래스이며 a( ) 추상 메서드를 가지고 있습니다. 
  • 만약 ABC 클래스를 생성할 수 있어서 

    ABC abc = new ABC();
  • 위 문장이 가능하다면 abc.a( ) 메서드를 호출했을 때 어떤 코드가 수행될까요? 
  • 정답은 아무것도 수행되지 않습니다. 구현된 코드가 없으니까 당연하겠죠.
  • 따라서 이런 문제를 예방하기 위해 추상 클래스는 인스턴스를 생성할 수 없도록 되어있습니다.
  • 다만, 추상 클래스라도 상위 클래스로 묵시적 형 변환은 가능합니다.

추상 클래스에서 구현하는 메서드 (★ 추상 클래스의 사용 목적, 추상 메서드의 사용 목적)

  • 생성할 수 없는 추상 클래스는 어디에 쓰는 걸까요?
  • 추상 클래스는 상속을 하기 위해 만든 클래스입니다. 
  • 그렇다면 어떤 메서드를 구현하고, 어떤 메서드를 구현하지 않고 추상 메서드로 남겨두는 걸까요? 
  • 추상 클래스에서 구현하는 메서드는 하위 클래스에서도 사용할 메서드입니다. 즉 하위 클래스 모두가 사용할 공통 메서드를 의미합니다. 
  • 반대로 추상 클래스에서 구현하지 않은 추상 메서드는 하위 클래스에서 각자 다른 내용으로 구현해야 하는 메서드를 의미합니다. 이렇게 추상 메서드로 남겨두어 구현 내용을 하위 클래스에서 구현 하도록 위임하는 것을 의미합니다.
  • 표로 정리해서 보면 아래와 같습니다.
구현된 메서드 하위 클래스에서 공통으로 사용할 메서드, 하위 클래스에서 재정의할 수도 있다.
추상 메서드 하위 클래스가 어떤 클래스냐에 따라 구현 코드가 달라질 메서드, 하위 클래스에서 구현하도록 위임
되었다.

 

추상 클래스와 프레임워크

  • 실제로 추상 클래스는 많은 프레임워크에서 사용하고 있는 구현 방식입니다. 
  • 예를 들어 안드로이드를 생각해 보면, 안드로이드 앱을 만들 때 안드로이드 라이브러리에서 제공하는 많은 클래스를 사용합니다. 
  • 이들 클래스 중에는 모두 구현된 클래스도 있지만, 일부만 구현되어 있어서 상속을 받아 구현하는 경우도 많습니다. 
  • 이때 안드로이드에서 구현해 놓은 코드는 내부적으로 사용하거나 상속받은 모든 클래스가 공통으로 사용할 메서드입니다. 
  • 그리고 구현을 미루어 놓은 메서드(추상 메서드)는 실제로 앱에서 어떻게 만드냐에 따라 다르게 구현해야할 내용으로 앱에서 구현하도록 선언만 해둔 것을 의미합니다.