여러 클래스에서 비슷한 필드와 메서드를 공통적으로 추출해서 만든 클래스
A, B, C 제조사 별로 키보드를 제작한다고 하자.
A 키보드는 누르면 불빛이 들어오고, B 키보드는 누르면 딸깍 소리가 나며, C 키보드는 살짝만 눌러도 잘 눌린다는 특징이 있다.
여기서 키보드 간의 공통점은 '누른다'라는 행동이다. 따라서, 키보드 클래스를 하나 만들고 공통으로 사용하는 '누른다' 라는 메서드를 만들고, 각 제조사 클래스는 키보드 클래스를 상속받아 메서드를 사용한다.
또한, '엔터키'와 같이 키보드 간에 공통되는 특성을 키보드 클래스에 선언하여 공통 변수로 사용할 수도 있다.
따라서, 제조사 클래스는 실체가 드러나는 실체클래스이고, 키보드 클래스는 실체클래스들의 공통적인 부분을 추출해 규격을 잡아놓은 추상클래스이다.
그렇기 때문에 실체클래스는 실제 객체를 생성할 정도의 구체성을 가지는 반면, 추상클래스는 아직 메서드와 내용이 추상적이기 때문에 객체를 생성할 수 없다.
추상클래스는 실체클래스와 IS-A 관계를 가지며, 실체클래스는 하나의 추상클래스만 상속할 수 있다(단일상속).
개발자들들이 자동차 클래스를 상속받아 각자만의 실체클래스를 구현한다면, 변수명과 메서드명은 제각기 다른 이름을 가지고 구현될 것이다.
만약, 수만줄에 이르는 코드에 A 개발자가 구현한 클래스 객체를 사용하다가 B 개발자가 구현한 클래스 객체로 교체해야 한다면, A 객체에 맞춰서 작성한 코드를 전부다 확인하여 변경해주어야한다.
따라서 이런 상황을 피하기 위해 추상클래스를 사용하여 필드와 메서드 이름을 통일한다면, 유지보수성을 높이고 통일성을 유지할 수 있다.
자동차 클래스를 설계하고 구현한다고 가정해보자. 바퀴, 백미러, 트렁크, 기어 변속 등 설계해야할 것이 많고 어떤 것을 구현해야 하는지 복잡할 것이다.
실체클래스를 구현할 때, 자동차 추상클래스를 상속받는다면 자연스럽게 자동차에 공통적으로 들어가야하는 필드와 메서드를 사용할 수 있다.
즉, 강제로 주어지는 필드와 메서드를 가지고 나만의 방식으로 구현한다면, 설계 시간이 절약되고 구현에 집중할 수 있다.
현업에서 추상클래스는 어플리케이션 아키텍트(AA, Application Architect)가 설계한다.
실체클래스를 구현할 때, 아무리 각자의 방식대로 구현한다고 해도 결국엔 규격안에서 구현해야 한다.
모두가 약속한 필드, 메서드, 설계 규칙에 맞게 구현해야 코드 수정시 영향을 적게 미치며 유지보수성을 높일 수 있다.
예를 들어 추상클래스에 정의된 추상메서드는 실체클래스에서 반드시 재정의해서 구현해야 한다. 이처럼 정해진 규격에 맞게 구현하도록 하는 것이 추상클래스의 목적이다.
추상클래스는 클래스 앞에 abstract 키워드를 붙여 사용한다.
public abstract class Animal {
public String kind;
public void breath(){
System.out.println("숨 쉰다.");
}
//추상메서드
public abstract void sound();
}
Animal 클래스를 상속받는 실체클래스들은 반드시 sound() 추상메서드를 재정의해야 한다. breath() 메서드는 추상메서드가 아니며 구현된 메서드이므로 재정의하지 않아도 된다.
public class Dog extends Animal{
public Dog(){
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("멍멍");
}
}
Animal 추상클래스를 상속받는 Dog 클래스이다. kind 변수는 추상클래스의 변수를 그대로 사용했고, sound() 추상메서드를 재정의하여 구현했다.
public class Cat extends Animal{
public Cat(){
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("야옹");
}
}
Cat 클래스 또한 Animal 추상클래스를 상속받았다. 여기서 sound() 메서드의 구현내용이 Dog 클래스와 다른 것을 볼 수 있는데, 이는 객체지향의 특징인 다형성을 의미한다. 추상클래스를 통해 Dog 클래스와 Cat 클래스의 규격이 일치하는 것을 확인할 수 있다.
다형성 : 같은 기능인데, 다른 결과를 도출할 수 있는 것
public class AnimalExample {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound(); // "멍멍"
cat.sound(); // "야옹"
Animal animal = null;
animal = new Dog(); // 자동 타입변환
animal.sound(); // Dog에 구현된 Sound()메서드 실행 -> "멍멍"
animal = new Cat(); // 자동 타입변환
animal.sound(); // Cat에 구현된 Sound()메서드 실행 -> "야옹"
animalSound(new Dog()); // 자동 타입변환 (매개변수도 가능) -> "멍멍"
animalSound(new Cat()); // 자동 타입변환 (매개변수도 가능) -> "야옹"
}
// 자동 타입변환 : 추상클래스 타입 변수는 추상클래스를 상속받은 실체클래스의 타입으로 자동 타입변환이 된다.
private static void animalSound(Animal animal) {
animal.sound();
}
}
추상클래스 객체에 추상클래스를 상속받은 실체클래스 객체를 주입하면 해당 추상클래스 변수는 자동으로 타입 변환되어 실체클래스 객체로 사용할 수 있다. 이는 타입의 다형성이라고도 한다.
[참고]