[OOP] abstract 클래스 와 interface 는 무슨 차이임?

728x90

 

서론


문득 Java 공부를 하면서 abstract 로 선언된 클래스랑 interface 를 알게되었습니다.

둘이 하는 역할이 비슷한데 왜 하나로 통합하지 않고 각각을 사용할까? 라는 고민이 들었습니다.

 

그래서 역시나 생각할 시간에 직접 만들어서 내가 비교를 해보면 개념을 파악하는데

도움이 될 걸 알기 때문에 직접 해보고 공부한 내용을 바탕으로 글을 써 봤습니다.

(부족한 내용은 내 개발 인생 동반자인 자바의정석 과 ChatGPT 를 참고 했습니다)

 

본론


객제치향 관점에서 abstract 와 interface 는 추상화 라는 개념을 사용하는 도구로 쓰입니다.

 

위 '추상화' 를 통해 컴파일 시점 의존성과 런타임 시점 의존성이 달라지는걸 볼 수있죠.

위 방법을 '동적 바인딩' 이라고 합니다.

 

이제 abstract 랑 interface 에 대해 자세히 알아보겠습니다.

 

일반 Java 클래스에 abstract 키워드를 사용하면 추상 클래스를 생성하게 됩니다.

[접근제어자] abstract [반환 타입] 클래스명 or 메소드명

 

추상 클래스는 아래와 같은 특징을 가지고 있습니다.

 

1. 메소드 구현 불가

 

추상 클래스는 추상 메소드라는 특별한 메소드를 포함할 수 있습니다. 추상 메소드는 메소드 선언만 존재하고 구현은 하지 않는 메소드입니다. 즉, 추상 클래스는 어떻게 구현해야 할지는 정의하지만, 실제 구현은 하지 않는다는 의미입니다. 

(이런 의미에서 제가 인터페이스랑 조금 헷갈렸습니다. 인터페이스도 대충 위 내용이랑 비슷함)

// 추상 클래스
abstract class Animal {

    // 추상 메소드
    abstract void makeSound();

    // 일반 메소드
    void eat() {
        System.out.println("먹습니다.");
    }
}
 

위 예시에서 Animal 클래스는 makeSound() 메소드를 추상 메소드로 선언했습니다.

추상 메소드는 반드시 abstract 키워드를 사용해야 하며, 메소드 선언 뒤에 세미콜론(;)을 붙여야 합니다.

 

왜냐면 추상 메소드는 상속을 받은 클래스에서 구현을 하는 것이기 때문에 추상 클래스에서 abstract 키워드가 붙어

있으면 메소드 body 부분을 구현할 수 없습니다.

-> (이것도 인터페이스랑 비슷)

 

 

2. 인스턴스 생성 불가

 

추상 클래스는 직접 인스턴스를 생성할 수 없습니다. 즉, new 연산자를 사용하여 추상 클래스 객체를 직접 생성하는 것은 불가능합니다. 추상 클래스는 하위 클래스에서 상속받아 구체화되어야만 사용할 수 있습니다.

// Animal 클래스는 추상 클래스이기 때문에 직접 인스턴스 생성 불가능
Animal animal = new Animal(); // 오류 발생
 

(인터페이스 또한 인스턴스를 생성할 수 없다)

 

인스턴스를 생성하게 되면

public static void main (String[] args) {	
	Animal animal = new Animal() {
    	@Override
        public void makeSound() {
        	// 구현
        }
    }

}

 

위 처럼 내가 알고 있는 인스턴스 형식이 아닌 익명 클래스 형식으로 밖에 생성이 안됩니다.

그래도 생성이 되긴 되네? 라고 생각할 수도 있습니다.

 

이 내용은 자바의 객체지향 과 관련이 있습니다.

 

 

익명클래스란?

 

  • 익명 클래스는 이름이 없는 클래스로, 새로운 클래스를 정의하면서 동시에 그 클래스의 인스턴스를 생성할 수 있습니다.
  • 추상 클래스의 모든 추상 메서드를 구현하여 인스턴스를 생성할 수 있게 합니다.

 

즉 추상클래스를 인스턴스 생성을 하게되면
익명 클래스로 변경되면서 추상 클래스가 익명 클래스로 바뀌어서 인스턴스화 할 수 있다는 뜻입니다.

 

하지만 단점이 너무 뚜렷하죠, 여러분이 사용하고 싶으면 그때 마다 매번 생성 해야 합니다^^

 

재사용도 불가능하고, 코드가 생각보다 복잡해 보임(로직은 쉬울수도 있긴한데 비즈니스에 따라 많이 다를듯..) 

 

 

3. 하위 클래스에서 상속

 

추상 클래스는 하위 클래스를 통해 구체화됩니다. 하위 클래스는 추상 클래스의 추상 메소드를 구현하고, 추가적인 메소드와 필드를 정의할 수 있습니다.

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("왈왈 짖는다!!!!!!");
    }

    void wagTail() {
        System.out.println("간식을 달라고 꼬리를 흔든다!!!.");
    }
}
 

위 예시에서 Dog 클래스는 Animal 추상 클래스를 상속받아
makeSound() 메소드를 구현하고, wagTail() 메소드를 추가적으로 정의했습니다.

 

 

4. 객체지향 프로그래밍과의 관련성

 

추상 클래스는 객체지향 프로그래밍의 중요한 개념인 추상화를 구현하는 데 사용됩니다. 추상화는 핵심적인 개념이나 기능에 집중하고, 구체적인 구현은 숨기는 프로그래밍 기법입니다. 추상 클래스는 추상 메소드를 통해 핵심적인 기능을 정의하고, 하위 클래스는 구체적인 구현을 제공함으로써 추상화를 구현할 수 있습니다.

 

5. 알아야 할 것:

  • 추상 클래스는 최소 하나 이상의 추상 메소드를 포함해야 합니다. 추상 메소드가 하나도 없는 경우, 일반 클래스로 선언해야 합니다.
  • 추상 클래스는 인터페이스와 유사하지만, 인터페이스는 추상 메소드만 포함할 수 있는 반면, 추상 클래스는 추상 메소드와 일반 메소드, 필드를 모두 포함할 수 있습니다.

 

그러면 interface 랑 거의 같은데 이름만 다른거 아님?

 

위 이야기만 듣고 나서는 인터페이스랑 거의 싱크로률 99% 라고  생각할 수도 있습니다.

왜냐? 하는 역할이 비슷하닌까 충분히 헷갈릴 수 있습니다.

 

추상 클래스와 인터페이스는 객체 지향 프로그래밍에서 중요한 역할을 하는 개념이지만, 몇 가지 핵심적인 차이점이 존재합니다.

 

한번 abstract 와 interface 를 비교해 봅시다.

 

1. 추상 메소드(abstract method):

  • 추상 클래스: 추상 메소드와 일반 메소드를 모두 포함할 수 있습니다.
  • 인터페이스: 추상 메소드만 포함할 수 있으며, 일반 메소드나 필드를 포함할 수 없습니다.

2. 구현:

  • 추상 클래스: 추상 메소드는 하위 클래스에서 반드시 구현해야 하지만, 일반 메소드는 하위 클래스에서 선택적으로 구현하거나 재정의할 수 있습니다.
  • 인터페이스: 모든 메소드는 구현체 클래스에서 반드시 구현해야 합니다. (default 제외)

3. 인스턴스 생성:

  • 추상 클래스: 직접 인스턴스를 생성할 수 없습니다.
  • 인터페이스: 직접 인스턴스를 생성할 수 없습니다.

4. 상속:

  • 추상 클래스: 하나의 추상 클래스로부터 여러 개의 하위 클래스를 상속받을 수 있습니다. -> extends
  • 인터페이스: 하나의 인터페이스로부터 여러 개의 구현체 클래스를 상속받을 수 있습니다. 다중 상속도 가능합니다. -> implements (extends 아님)

5. 사용 사례:

  • 추상 클래스: 공통적인 기능과 추상 메소드를 제공하여 하위 클래스에서 구체화하는 데 사용됩니다.
  • 인터페이스: 서로 다른 클래스 간의 공통적인 기능을 정의하고, 구현 방식은 구현체 클래스에서 자유롭게 결정하는 데 사용됩니다.

6. 비유:

  • 추상 클래스: 건축 설계도와 비교할 수 있습니다. 설계도는 건물의 기본적인 구조와 레이아웃을 정의하지만, 실제 건축은 설계도를 기반으로 하여 이루어집니다.
  • 인터페이스: 요리법과 비교할 수 있습니다. 요리법은 특정 요리를 만드는 데 필요한 재료와 단계를 정의하지만, 실제 요리는 요리법을 기반으로 하여 개인의 취향에 따라 조절될 수 있습니다. 그리고 실제로 요리를 시켜먹는 우리는 요리사가 요리를 어떻게 하는지 알 필요가 있을까요? 아닙니다. 우리는 그냥 돈을 지불하고 음식을 사먹기만 하면 되는 것이죠.
  • 위 이야기에서 객체의 책임과 역할 또한 이야기가 나옵니다.

 

위 내용을 보고 확실히 알게 된게 있을 겁니다. 둘다 인스턴스 생성을 못하네?

그럼 단위 테스트 할때 어떻게 함?? 이라는 생각을 할 수 있습니다.

 

그래서 준비 했습니다. 다음과 같은 방법을 활용하여 테스트를 수행할 수 있습니다.

 

 

1. 하위 클래스 또는 구현체 클래스 사용:

  • 추상 클래스의 경우, 하위 클래스를 생성하여 테스트 대상으로 사용합니다. 하위 클래스는 추상 메소드를 구현해야 하기 때문에 테스트를 통해 구현 내용을 검증할 수 있습니다.
public class AnimalTest {

    @Test
    public void testMakeSound() {
        Dog dog = new Dog();
        assertEquals("멍멍!", dog.makeSound());
    }
}
 
  • 인터페이스의 경우, 인터페이스를 구현하는 구현체 클래스를 생성하여 테스트 대상으로 사용합니다. 구현체 클래스는 인터페이스의 모든 메소드를 구현해야 하기 때문에 테스트를 통해 구현 내용을 검증할 수 있습니다.
public class ButtonTest {

    @Test
    public void testOnClick() {
        Button button = new Button();
        button.simulateClick(); // 버튼 클릭 시뮬레이션
        // 다른 로직
    }
}

 

 

그럼 클래스 말고 메소드에 붙어 있는 abstract 는 뭐임? 

  • 추상 메소드는 반드시 추상 클래스에 선언해야 하며, 일반 클래스에는 선언할 수 없습니다.
  • 추상 메소드는 abstract 키워드를 사용해야 하며, 메소드 선언 뒤에 세미콜론(;)을 붙여야 합니다.
  • 추상 메소드는 추상 클래스의 하위 클래스에서 반드시 구현해야 합니다.

 

추상 메소드는 객체 지향 프로그래밍에서 중요한 개념인 추상화를 구현하는 데 사용됩니다. 추상화는 핵심적인 개념이나 기능에 집중하고, 구체적인 구현은 숨기는 프로그래밍 기법입니다. 추상 메소드는 추상적인 기능을 정의하고, 하위 클래스에서 구체적인 구현을 제공함으로써 추상화를 구현할 수 있습니다.

 

그러면 인터페이스에서도 추상 메소드 사용할 수 있나?

그래서 직접 해봤습니다.

public interface AbstractTest {


	// 인터페이스는 메소드 선언시 어차피 자동으로 public 으로 간주함
	// 인터페이스에 선언된 메서드는 abstract 이 보이지 않지만 붙어 있음.
	void test(); // 우리는 이렇게 사용하면 됌
	public void abstractTest(); // public ㄴㄴ
	public abstract void abstractTest2(); // abstract ㄴㄴ


	// Illegal combination of modifiers 'default' and 'abstract'
	default abstract void abstractTest4() {
		// 애초에 JDK 에 defualt 랑 abstract를 연결해서 쓰는 건 없는 조합이므로 안됌
		// default 를 빼던가 abstract 를 뺴던가 해야함
	}

    
	// Illegal combination of modifiers 'abstract' and 'private'
	private abstract void test4 () {
	// private 을 뺴던가 abstract 를 뺴던가 해야함
	// 인터페이스는 추상화 하기 위해서 만든건데 private 으로 메소드 만들면 구현 어케함? ㅋㅋ 인스턴스 생성도 못하는데
	}
    
    
	default void abstractTest3() {
		System.out.println("hi? 이건 디폴트임 너가 재구현 하던지 아님 그냥 쓰던지 알아서 하길");
	}

}

 

주석으로 설명을 달아놨습니다.

 

 

결론


추상 클래스와 인터페이스는 서로 유사하지만, 몇 가지 핵심적인 차이점이 존재합니다.

둘다 추상화를 하는 도구로 아주 유용하게 사용이 된다✨

 

추상 클래스는 공통적인 기능과 추상 메소드를 제공하여 하위 클래스에서 구체화하는 데 사용되고, 인터페이스는 서로 다른 클래스 간의 공통적인 기능을 정의하고 구현 방식은 구현체 클래스에서 자유롭게 결정하는 데 사용됩니다. 상황에 맞게 적절한 도구를 선택하는 것이 중요합니다.

 

사실 위 내용을 잘 이해했다고 하더라도, 실무 비즈니스를 제대로 이해하지 못하면 객체지향적으로 코드를 짜기는 너무 어려운것 같다.

일단 위 내용을 잘 숙지하고 있으면 언젠가는 분명히 잘 써먹을 날이 올 것이다..

 

위 내용을 실무 비즈니스에 적용할 수 있는 날이 오기를 기원하며..

728x90