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

728x90

 

서론

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

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

 

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

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

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

 

본론

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

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

 

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

 

1. 메소드 구현 불가:

 

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

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

// 추상 클래스
abstract class Animal {

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

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

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

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

 

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

메소드를 구현할 수 없습니다.

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

 

 

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