Language/JAVA

[JAVA] 추상 클래스와 인터페이스

gangintheremark 2023. 7. 24. 13:26
728x90

상속은 객체지향 프로그래밍의 핵심 기능으로서 상속을 적용하면 코드의 재사용성 및 다형성, 오버라이딩 메서드 등과 같은 객체지향적인 프로그램 기법을 적용할 수 있다. 하지만 하위클래스에서 부모의 메서드를 상속받아서 사용하지 않고 자신만의 메서드를 작성하여 사용한다면 상속을 사용하는 장점을 얻을 수 없다. 상속은 강제성이 없기 때문이다.

 

따라서 객체지향 특징인 재사용성 및 유지보수를 향상시키기 위해서 하위클래스에서 반드시 부모클래스의 메서드를 사용하도록 강제할 필요가 있다. 자바에서는 추상클래스와 인터페이스를 통해 자식클래스들에게 부모의 메서드를 반드시 사용하도록 강제할 수 있다.

추상클래스 (Abstact Class)

추상 클래스(Abstact Class)는 추상 메소드를 선언해 놓고 상속을 통해 자식 클래스에서 메소드를 완성하도록 강제하는 클래스이다.

  • abstract 키워드를 사용
  • 인스턴스화가 불가능한 클래스
  • 추상클래스는 추상 메소드외에도 일반적인 변수, 메소드, 생성자 사용 가능
  • 다중 상속이 불가능하며 단일 상속만 가능
  • 같은 추상화인 인터페이스와 다른 점은, 추상클래스는 클래스 간의 연관관계를 구축하는 것에 초점

추상메소드는 무슨 작업을 할 지 미정인 메소드라고 생각하면된다. 즉, 메소드의 선언부만 작성하고 구현부는 미완성인 채로 남겨둔 메소드이다. 추상 클래스 안의 메소드를 미완성을 남겨두는 이유는 부모(추상) 클래스에서 메소드를 선언부만을 작성하고, 구현부는 상속받는 자식 클래스에서 구현하도록 하기 위해 일부러 비워두는 것이다.

따라서 추상 클래스를 상속받는 자식 클래스는 부모의 추상 메소드를 재정의하여 사용한다.

 

🔔 추상클래스의 사용 목적 : 모든 자식클래스에서 특정 메소드를 사용하도록 강제할 때 추상클래스 사용

public abstract class Pet {
    String name;
    int age;

    // 자식 클래스에서 꼭 사용하고자 하는 메소드를 추상 메소드로 만듦.
    public abstract void eat(); 
    public abstract void sleep();
}

class Dog extends Pet {
    @Override
    public void eat() {
        System.out.println("Dog.eat");
    }

    @Override
    public void sleep() {
        System.out.println("Dog.sleep");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog d = new Dog();

        d.eat();
        d.sleep();

        // Pet 객체를 인스턴스화는 불가능하지만 변수의 데이터 타입으로는 사용 가능하다 
        // Pet p = new Dog();  error

        // 추상 클래스를 상속받는 자식 클래스를 인스턴스화
        Pet p = new Dog(); 성
    }
}

인터페이스 (Interface)

인터페이스는 클래스에서 특정 메소드를 구현하도록 강제하는 기능이다.

  • interface 키워드를 사용
  • 추상클래스와는 마찬가지로 인스턴스화가 불가능
  • 상수추상메소드, default 메소드, static 메소드 사용 가능 (변수, 생성자, 일반 메소드 ❌ )
    • 상수는 public static final 타입
    • 추상메소드는 abstract public 타입
  • 클래스에 다중 구현이 가능
  • 인터페이스끼리는 다중 상속이 가능
  • 인터페이스는 부모-자식 관계에 얽매이지 않고, 공통 기능이 필요할 때 추상 메소드를 정의하고 implements 키워드를 사용해 지정하고 오버라이딩 하여 구현
  • 클래스들간의 의존성 감소

🔔 인터페이스를 사용하는 목적 : 특정 메소드의 구현을 강제할 때 사용

interface Pet {
    public static final String name = "동물"; 

    public abstract void eat();
    public abstract void sleep();
}

class Cat implements Pet{

    @Override
    public void eat() {
        System.out.println("Cat.eat");
    }

    @Override
    public void sleep() {
        System.out.println("Cat.sleep");
    }
}
/* ==================================== */

interface Y {
    public abstract void x(); // 추상 메소드
}
interface Z {
    public abstract void x2(); // 추상 메소드
}

// 💡 구현
class K implements Y, Z { // 다중 구현

    @Override
    public void x() {

    } 

    @Override
    public void x2() {

    }
}

// 💡 인터페이스 간 상속
interface X extends Y, Z { // 다중 상속

}

// 💡 상속 및 구현
class K2 extends Object implements Y, Z { 

    @Override
    public void x() {

    }
    @Override
    public void x2() {

    }    
}

인터페이스를 이용한 디커플링 (decoupling)

프로그래밍에서 디커플링이란 모듈 사이의 연결고리(의존 관계)를 인터페이스를 사용하여 두 모듈의 의존성을 감소하여 개발 및 유지보수가 용이하도록 처리하는 방법을 의미한다. 즉, 모듈 사이를 인터페이스로 연결하고 구현체를 분리하는 것을 말한다.

객체들 간에 결속력이 강하면 그 프로그램의 구성 요소 중 하나를 수정하게 되면 연관된 구성 요소를 새로 수정해야한다. 그러나 인터페이스를 이용한 설계가 잘된 경우는 수정하고자 하는 요소만 수정하여 재배포 하면 된다.

728x90