[면접대비]_추상클래스와 인터페이스의 차이

워니·2024년 8월 18일
0

신입(혹은 저연차) 개발자의 단골 질문! 추상클래스와 인터페이스의 차이...! 많은 개발자들이 이 차이에 대해 단순히 표로만 비교한 정보를 가지고 답하겠지만 이들 중에는 이에 대해 제대로 이해하고 있는 사람은 드물다고 생각한다. 필자를 포함해서. 그래서 이번에 빠삭하게 정리해 보도록 하겠다. 핵심만 보려면 3번째 탭 '인터페이스와 추상클래스의 비교'만 보기를 추천드린다.
그리고 이 블로그 글과 검색 등을 이용해 정리했는데, 원글이 정리가 너무 잘 되어있어서 꼭 한번 보기를 바란다.
인터페이스 vs 추상클래스 차이점 완벽 이해하기


인터페이스

정의

  • 그 객체의 내부 구현이 어떻든 깊이 학습할 필요없이 원하는 메소드만 호출하고 결과 값을 제대로 받게 해주는 간편한 상호작용 기능
  • 클래스와 유사하지만, 메서드의 구현을 포함하지 않고 메서드의 시그니처(이름, 매개변수, 반환 타입)만을 정의한다
  • 상속 및 추상 메서드 강제 구현 외에도, 다양한 프레임워크에서 클래스끼리 통신하는데 자주 사용되며, 객체 지향(OOP) 프로그래밍의 전략에서 결합도(Coupling)을 낮춰 유지보수성을 늘리는 디자인 패턴으로서의 역할도 병행한다

역할

  1. 계약 정의: 구현할 메서드를 정의하여, 해당 인터페이스를 구현하는 클래스가 이 메서드들을 반드시 구현하도록 강제한다.
  2. 다중 상속 지원: 클래스는 여러 인터페이스를 구현할 수 있어 다양한 기능을 혼합하여 사용할 수 있다.
  3. 다형성: 서로 다른 클래스들이 동일한 메서드를 호출할 수 있어 다양한 클래스 간의 상호 운용성을 제공한다.
  4. 느슨한 결합: 코드의 결합도를 낮추어 프로그램의 유지 보수성과 확장성을 향상시킨다.

기본 문법

  • 인터페이스를 작성하는 것은 추상 클래스를 작성하는 것과 같다고 보면 된다. (추상 메서드 집합)
  • 인터페이스도 필드를 선언할 수 있지만 변수가 아닌 상수(final)로서만 정의할 수 있다. 
  • public static final 과 public abstract 제어자는 생략이 가능하다.
  • 일반적으로, 한 클래스에 인터페이스를 구현한다면 그 인터페이스의 모든 메서드를 구체적으로 구현해야 하며, 모든 메서드를 구현하지 않을 때에는 클래스를 추상클래스(abstract class)로 선언해줘야 한다.
  • 인터페이스는 다중 상속이 가능하다
interface TV{
	int MAX_VOLUME = 10; // public static final 생략 가능
    int MIN_VOLUME = 10; 
    
    void turnOn(); // public abstract 생략 가능
    void turnOff();
    void changeVolume(int volume);
    void changeChannel(int channel);
}

인터페이스의 다양한 활용

1. 인터페이스 다형성

인터페이스 타입으로 변수를 구현하게 되면 모든 객체는 간단히 인터페이스만 구현한 객체이면 되기 때문에 좀 더 시스템이 유연해질 수 있다.

interface Keyboard{}
class Logitec_Keyboard implements Keyboard { }
class Samsung_Keyboard implements Keyboard { }
class Apple_Keyboard implements Keyboard { }

public class Main {
	public static void main(String[] args) {
    // 인터페이스 타입 배열로 여러가지 클래스들을 한번에 타입 묶음을 할 수 있다.
    	Keyboard[] k = {
        	new Logitec_Keyboard(),
            new Samsung_Keyboard(),
            new Apple_Keyboard()
        };
    }
}

또한, 적합한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드 전체를 전부 인터페이스 타입으로 선언하면 좋다.

  • 객체는 인터페이스를 사용해 참조하라
  • 적당한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드 전부 인터페이스 타입으로 선언하라
  • 객체의 실제 클래스를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다
  • 매개변수 타입으로는 클래스보다 인터페이스를 활용하라

예를들어 중복이 없는 집합 자료형을 사용하기 위해 LinkedHashSet 클래스를 초기화 하려할때, 이때 객체의 타입을 똑같이 LinkedHashSet 으로 하지말고 인터페이스인 Set 타입으로 설정하여 선언하는 것이다.
이런식으로 코드 구현을 해놓는다면, 나중에 변수에 담긴 구현 클래스를 다른 Set 자료형 클래스로 교체하고자 할때 그저 새 클래스의 생성자를 다시 호출해주기만 하면 되어 간편해진다.

// 나쁜 예) 클래스를 바로 타입으로 사용했다
LinkedHashSet<Object> s = new LinkedHashSet<>();
// 좋은 예) 인터페이스를 타입으로 사용했다
Set<Object> s = new LinkedHashSet<>();
// 좋은 예를 사용시 다른 Set자료형 교체가 쉽다
s = new TreeSet<>();

2. 형제 관계를 만들어줌

기본적으로 자바의 클래스 상속 구조는 부모-자식 관계로만 가능하고, 단일 상속의 원칙을 가진다. 하지만 인터페이스는 클래스 상속관계와는 달리, 하나의 기능에 대한 약속이기 때문에 다중 상속에 제약받지 않으며, 일부만 묶고 싶은 클래스들을 implements키워드로 등록시키면, 각기 다른 부모클래스를 상속하고 있는 자식 클래스에 인터페이스를 구현(상속)시켜줌으로써 아무 관계도 없는 클래스들의 관계를 맺어줄 수 있다.
그림을 보면 쉽게 이해할 수 있다

예를 들어 각기 다른 부모 클래스를 상속하고 있는 Soccer 클래스와 BassGuitar 클래스를 하나의 타입으로 묶어서 사용할 필요가 있을때, 인터페이스를 implements 함으로써 마치 Soccer 와 BassGuitar 클래스를 묶은 형제 클래스 타입 IBehavior 를 만든것과 같다.

3. 타입 접근 제한

파라미터로 접근 가능한 타입을 제한할 수 있다

이 부분은 바로 코드 예시를 보는 것이 훨씬 이해하기 쉬울 수 있다.
다음과 같이 스타크래프트를 구성하기 위해 Marine, SCV, Tank 클래스를 만들고 이들을 공통으로 묶을 부모 클래스 GroundUnit 클래스로 상속 관계를 맺어 주었다.
그리고 repair() 메서드에서 중복을 줄이기 위한 다형성 기법으로 매개변수 타입을 GroundUnit 부모 클래스 타입으로 받도록 설정 하였다.

class GroundUnit { }

class Marine extends GroundUnit{ }
class SCV extends GroundUnit{ }
class Tank extends GroundUnit{ }

public class Main {
    public static void main(String[] args) {   
        repair(new Marine());
    }
    
    static void repair(GroundUnit gu) {
        // 마린은 기계가 아니기 때문에 수리는 불가능 하다. 하지만 상속 관계상 마린 클래스 타입이 들어와 실행될 수 있는 위험성이 존재한다.
    }
}

하지만 위의 코드의 문제점은 기본적으로 repair 기능은 기계 유닛만 가능하여 SCV와 Tank 클래스 타입만 들어와야 되는데 생물 유닛인 Marine 클래스 타입도 상속 관계에 의해 들어 올수 있다는 것이다.
따라서 별도의 Machine 이라는 인터페이스를 선언하고 SCV, Tank 클래스에 implements 시킨다.
그렇게 3개의 자식중 2개의 자식만 머신 이라는 타입으로 형제 타입 관계를 맺어주면서 동시에 다른 타입의 접근 제한 역할도 해낸 것이다.

interface Machine { } // SCV, Tank 클래스를 통합한 타입으로 이용하는 인터페이스

class GroundUnit { }

class Marine extends GroundUnit{ }
class SCV extends GroundUnit implements Machine{ }
class Tank extends GroundUnit implements Machine{ }

public class Main {
    public static void main(String[] args) {
        repair(new Marine()); // ! ERROR
    }

    static void repair(Machine gu) {
        // SVG와 탱크 타입만 받을 수 있게 인터페이스를 타입으로 하여 다형성을 적용
    }
}

4. 의존성 제거

클래스의 관계를 상속(extends)가 아닌 구현(implements)으로 인터페이스로 확장시킨다면, 반환 타입이나 매개변수 타입으로 다른 객체와 소통하는 구간에 인터페이스 타입으로 사용함으로써, 객체간 의존성이 줄어들어 자신과 소통하는 객체의 변화에 강한 클래스를 만들 수 있게 된다.

추상클래스

정의

클래스에 추상화를 접목시켜 보다 구조적이게 객체를 설계하고, 프로개름의 유지보수성을 올려주며, 만일 프로그램에 어떠한 기능을 업그레이드한다고 하면 수정/추가에 유연하게 해주는 기능의 클래스

추상클래스의 활용

1. 공통 멤버의 통합으로 중복 제거

메서드가 겹치는 클래스를 부모 추상클래스로 묶으면, 상속 특징을 이용하여 코드의 중복제거, 코드 재사용성 증대 효과를 누릴 수 있다

2. 구현의 강제성을 통한 기능 보장

3. 규격에 맞는 설계 구현

실제 프로젝트에서 어플리케이션 아키텍처가 설계해 놓은 추상 클래스를 상속받으면, 개발자는 프로젝트에서 필요하고 공통적으로 들어가야 하는 필드와 메서드를 오버라이딩해서 큰 설계를 생각할 필요 없이 구현만 하면 된다.
정리하자면 추상클래스를 상속받아서 미리 정의된 공통 기능들을 구현하고, 실제 클래스에서 필요한 기능을 클래스별로 확장시키면서 소스 수정시 다른 소스의 영향도를 적게 가져가면서 변화에는 유연하게 만들 수 있다.

인터페이스와 추상클래스의 비교

인터페이스 정리

  • 내부의 모든 메서드는 public abstract로 정의(default 메서드 제외)
  • 내부의 모든 필드는 public static final 상수
  • 클래스에 다중 구현 지원
  • 인터페이스끼리는 다중 상속 지원
  • 인터페이스에도 static, default, private 제어자를 붙여 클래스같이 구체적인 메서드를 가질 수 있음
  • 인터페이스는 부모 자식 관계인 상속에 얽매이지 않고, 공통 기능이 필요할 때마다 추상 메서드를 정의해 놓고 구현(implements) 하는 식으로 추상클래스보다 자유롭게 떼었다 붙였다 가능
  • 인터페이스는 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 초점
  • 다중 구현이 된다는 점을 이용해 내부 멤버가 없는 빈 껍데기 인터페이스를 선언하여 마커 인터페이스로서 이용 가능
  • 보통 xxxable 이런 형식으로 인터페이스 네이밍 규칙을 따름

추상클래스 정리

  • 추상클래스는 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
  • 추상클래스는 다중 상속이 불가능하여 단일 상속만 허용됨
  • 추상클래스는 추상 메서드 외에 일반 클래스와 같이 일반적인 필드, 메서드, 생성자를 가질 수 있다
  • 이러한 특징으로, 추상클래스는 추상화(추상 메서드)를 하면서 중복되는 클래스 멤버들을 통합 및 확장할 수 있다
  • 같은 추상화인 인터페이스와 다른 점은, 추상클래스는 클래스간의 연관 관계를 구축하는 것에 초점을 둔다

추상클래스를 사용하는 경우

이를 상속할 각 객체들의 공통점을 찾아 추상화시켜 놓은 것으로, 상속관계를 타고 올라갔을 때 부모클래스가 상속하며 부모클래스가 가진 기능을 구현해야 할 경우 사용한다

  • 상속 받을 클래스들이 공통으로 가지는 메소드와 필드가 많아 중복 멤버 통합을 할때
  • 멤버에 public 이외의 접근자(protected, private) 선언이 필요한 경우
  • non-static, non-final 필드 선언이 필요한 경우 (각 인스턴스에서 상태 변경을 위한 메소드가 필요한 경우)
  • 요구사항과 함께 구현 세부 정보의 일부 기능만 지정했을 때
  • 하위 클래스가 오버라이드하여 재정의하는 기능들을 공유하기 위한 상속 개념을 사용할 때

인터페이스를 사용하는 경우

클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용

  • 어플리케이션의 기능을 정의해야 하지만 그 구현 방식이나 대상에 대해 추상화 할 때
  • 서로 관련성이 없는 클래스들을 묶어 주고 싶을때 (형제 관계)
  • 다중 상속(구현)을 통한 추상화 설계를 해야할때
  • 특정 데이터 타입의 행동을 명시하고 싶은데, 어디서 그 행동이 구현되는지는 신경쓰지 않는 경우
  • 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용

출처 :
인터페이스 vs 추상클래스 차이점 완벽 이해하기
인터페이스(Interface) 문법 & 활용 - 완벽 가이드

profile
매일, 조금씩 나아가는중

0개의 댓글