Abstract Class, Interface

ChoiDevv·2023년 1월 5일
0

Before

객체지향의 5대 개념, Solid에서 공부하는데 OCP 파트 공부 중 인터페이스를 통한 개념이 나와서 안그래도 이 둘의 차이를 제대로 느끼지 못해 애를 먹고 있었는데 이 참에 정리하고자 하려고 한다.

Abstract Class

추상 클래스에 대해 정리하기 전에 간단히 예제 코드를 만들어보자.

public class Dog {

    private String name;
    private int age;

    public Dog() {

    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void move() {
        System.out.println("걷는다.");
    }

    public void drink() {
        System.out.println("마신다.");
    }

    public void bark() {
        System.out.println("멍멍");
    }
}

다른 사람들이 동물을 비교해서 만들었는데 이게 이해가 제일 쉬운듯하다. 먼저 Dog 클래스를 생성해준다.

public class Cat {

    private String name;
    private int age;

    public Cat() {

    }

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void move() {
        System.out.println("걷는다.");
    }

    public void drink() {
        System.out.println("마신다.");
    }

    public void bark() {
        System.out.println("야옹");
    }
}

Cat 클래스도 만들어준다.

public class Main {

    public static void main(String[] args) {

        Dog dog = new Dog();
        dog.bark();

        Cat cat = new Cat();
        cat.bark();
    }
}

결과적으로 메인 클래스에서 실행시키면 다음과 같다. 출력은 정상적으로 되지만 여기에 특정 행동을 추가한다거나 공통적으로 메소드 변화가 필요하면 일일이 해당 클래스를 들어가서 수정해줘야 하는 번거로움이 생긴다.

이처럼 공통적인 부분을 미리 뼈대로 만들어주는 것이 추상 클래스의 개념이다. 한 번 코드로 만들어보자.

public abstract class Animal {

    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Animal() {

    }

    public void move() {
        System.out.println("걷는다.");
    }

    public void drink() {
        System.out.println("마신다.");
    }

    public abstract void bark();
}

Animal 이라는 추상 클래스를 생성한다. 이 때, 각 클래스마다 넣어줬던 공통적인 메소드들을 모두 기재한다. 다만, bark() 라는 메소드는 출력이 달랐지만 그 역할은 비슷했기 때문에 추상 메소드로 지정해준다.

public class Cat extends Animal{

    public Cat() {
        super();

    }

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void bark() {
        System.out.println("야옹");
    }
}

Cat 클래스를 다음과 같이 수정한다. Animal 이라는 추상 클래스를 상속 받기 때문에 추상 클래스의 추상 메소드를 오버라이딩해서 재정의한다. 그리고 super() 메소드를 통해서 생성자에 대한 접근도 열어준다. 이제 결과를 실행해보면 동일하게 출력되는 것을 확인할 수 있다.

Interface

인터페이스도 마찬가지로 코드로 먼저 알아보자.

public interface Animal {

    public abstract void move();
    public abstract void eat();
    public abstract void bark();
}

Animal 이라는 인터페이스를 먼저 만들어주고 위에서 적었던 메소드들을 껍데기만 적어준다. 지금보니 drink가 eat으로 바뀌었다.

public class Cat implements Animal {

    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Cat() {

    }

    @Override
    public void move() {
        System.out.println("걷는다.");
    }

    @Override
    public void eat() {
        System.out.println("먹는다.");
    }

    @Override
    public void bark() {
        System.out.println("야옹");
    }
}

Animal 인터페이스를 구현체로 받으면 다음과 같이 오버라이딩을 해서 재정의한다.

인터페이스에 추상 메소드들을 모두 오버라이딩 해야하는데 오버라이딩이 되어있지 않다고 친절하게 인텔리제이가 알려주는 모습이다. 메인 클래스에서 똑같이 실행해보면 같은 결과를 얻는다.

차이가 뭐지?

두 개를 아무리 봐도 비슷한 역할을 하는 거 같은데 대체 왜 이렇게 나눈 건지 솔직히 정리가 안됐었다.

근데 하다보니까 조금 차이점이 보였다. 추상 메소드 같은 경우는 사람들이 "복제" 라고 생각하라했는데 보니까 멤버변수부터 비슷한 부분을 모두 미리 만들어두고 자식 클래스가 이를 상속 받아서 구현한다.

그런데 인터페이스는 메소드의 껍데기만 만들어두고 이를 구현체로 받았을 때 전부 오버라이딩해서 사용해야 한다는 것을 알 수 있었다. 이 정도의 차이까지는 금방 이해했는데 스프링부트에서 현업 코드로 이를 적용시킨다는 것에 아직까지 궁금증이 해소되지는 않았다.

확실한 건 잘만 이해하면 객체 지향적으로 구성할 수는 있겠구나 싶었다.

profile
기억보단 기록을

0개의 댓글