티끌모아 태산

다형성2 본문

백엔드/JAVA

다형성2

goldpig 2024. 6. 1. 17:30
728x90

다형성을 왜 사용하는지 그리고 장점은 무엇인지 알아보자. 

위와 같은 객체들이 있고, sound() 함수를 정의했다고 하자. 이때, 개, 고양이, 소 말고 다른 동물을 추가하면 어떻게 될까? 새로운 클래스를 만들고 sound() 함수를 정의해야한다. 하지만 그렇게 될경우 중복되는 부분이 존재하게 된다. 그러면 중복되는 부분을 어떻게 제거할 수 있을까? 메서드 또는 배열? 안된다. 개, 고양이, 소 모두의입이 서로 다르기 때문에 중복되는 부분을 제거할 수 없다. 

그렇다면 Dog, Cat, Caw가 모두 같은 타입을 사용할 수 있다면 메서드와 배열을 활용해서 코드의 중복을 제거할 수 있다는 것이다. ❗다형성의 핵심인 다형적 참조와 메서드 오버라이딩을 활용하면 Dog, Cat, Caw가 모두 같은 타입을 사용하고, 각자 자신의 메서드도 호출할 수 있다. 

다형성 활용1

다형성을 활용하기 위해 Animal(동물) 부모 클래스를 만들고 sound() 메서드를 정의한다. 그리고 이 메서드는 자식 클래스에서 오버라이딩 된다. 

        Dog dog = new Dog();
        Cat cat = new Cat();
        Caw caw = new Caw();

        soundAnimal(dog);
        soundAnimal(cat);
        soundAnimal(caw);
        }
    private static void soundAnimal(Animal animal){
        System.out.println("동물소리 테스트 시작");
        animal.sound();
        System.out.println("동물소리 테스트 종류");
    }

  • 예를들어, soundAnimal(dog)를 호출하면 soundAnimal (Animal animal)에 Dog 인스턴스가 전달된다.
  • Animal animal = dog로 이해하면 되고, 부모는 자식을 담을 수 있다. Animal은 dog의 부모다.
  • 그러면 메서드 안에서 animal.sound() 메서드가 호출 된다. 
  • animal 변수의 타입은 Animal 이므로 Dog 인스턴스에 있는 Animal 클래스 부분을 찾아서 sound() 메서드를 실행한다. 그런데 하위 클래스인 Dog에서 sound() 메서드를 오버라이딩 했다. 따라서 오버라이딩한 메서드가 우선권을 갖는다. 

⭐핵심은 Animal animal 코드이다. 다형적 참조로 인해 animal 변수는 자식인 Dog, Cat, Caw의 인스턴스를 참조할 수 있다. (부모는 자식을 담을 수 있다.) 또한 메서드 오버라이딩으로 인해 animal.sound()를 호출해도 Dog.sound, Cat.sound, Caw.sound()와 같이 각 인스턴스의 메서드를 호출할 수 있다. 

추상 클래스1

클래스를 설계도에 비유한다면, 추상 클래스는 미완성 설계도에 비유할 수 있다. 따라서 실체인 인스턴스가 존재하지 않는다. 추상클래스는 상속을 통해 자식 클래스에 의해서만 완성될 수 있다

  • 상속을 목적으로 사용
  • 부모 클래스 역할을 담당
abstract class AbstractAnimal {...}
  • 추상 클래스를 생성할 때, 추상이라는 의미의 abstract 키워드를 붙여 준다.
  • 추상 클래스는 new AbstractAnimal()와 같이 직접 인스턴스를 생성하지 못한다
// 추상클래스 생성 불가
AbstractAnimal animal = new AbstractAnimal();

 

추상 메서드 -> 미완성 메서드

메서드는 선언부와 구현부(몸통)로 구성되어 있다. 이때, 선언부만 작성하고 구현부는 작성되지 않은 채로 남겨둔 것이 추상메서드이다. 이는 부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야하는 메서드이다. 

public abstract void sound();
  • 추상 메서드는 선언할 때, abstract 키워드를 붙여주면 된다.
  • ❗추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다. -> 그렇지 않으면 컴파일 오류 발생한다.
  • ❗추상 메서드는 상속 받는 자식 클래스가 반드시 오버라이딩 해서 사용해야 한다

추상클래스는 왜 사용할까? 

  1. 추상 클래스 덕분에 실수로 부모(Animal) 인스턴스를 생성할 문제를 근본적으로 방지해준다.
  2. 추상 메서드 덕분에 새로운 동물의 자식 클래스를 만들 때, 실수로 sound()를 오버라이딩 하지 않을 문제를 근본적으로 방지해준다. 

추상 클래스2

순수 추상 클래스: 모든 메서드가 추상 메서드인 추상 클래스

AbstractAnimal의 모든 기능을 자식 클래스에서 오버라이딩 해야한다. 대상 타입을 찾을 때는 호출자의 타입을 기준으로 선택한다. 처음 호출자의 타입으로 선택 후 오버라이딩을 호출한다. 

모든 메서드가 추상 메서드인 순수 추상 클래스는 코드를 실행할 바디 부분이 없다.

public abstract class AbstractAnimal {
    public abstract void sound();
    public abstract void move();
}
  • 인스턴스를 생성할 수 없다.
  • 상속시 자식은 모든 메서드를 오버라딩 해야 한다.
  • 주로 다형성을 위해 사용한다. 

상속하는 클래스는 모든 메서드를 구현해야 한다. 이러한 특징은 상속받는 자식 클래스 입장에서 어떤 규격을 지켜서 구현해야 하는 것처럼 느껴진다. 이것은 일반적으로 인터페이스와 같이 느껴진다. 이런 순수 추상 클래스 개념은 프로그래밍에서 매우 자주사용되고, 이를 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공한다.  

인터페이스

자바는 순수 추상클래스를 더 편리하게 사용할 수 있는 인터페이스 개념을 제공한다. interface 키워드 사용

// 순수 추상 클래스
public interface interfaceAnimal {
    void sound(); // public abstract 생략
    void move(); // public abstract 생략
}

인터페이스는 순수 추상 클래스와 같다. 여기에 약간의 편의 기능이 추가된 것 뿐이다.

  • 인터페이스의 메서드는 모두 public, abstract 이다.
  • 메서드에 public abstract 를 생략할 수 있다. 참고로 생략을 권장한다. 
  • 인터페이스는 다중 구현(다중 상속)을 지원한다.
public interface InterfaceAnimal {
    public static final double MY_PI = 3.14;
   }
   
public interface InterfaceAnimal {
    double MY_PI = 3.14; // public static final  생략가능
   }
  • 인터페이스에서 멤버 변수는 public, static, final 이 모두 포함되어 있다고 간주하고 생략 가능하다. final은 변수의 값을 한번 설정하면 바꿀 수 없다는 뜻이다. 

인터페이스는 class 대신에 interface 키워드를 사용한다. 그리고 인터페이스를 상속받을 때는 extends 대신에 implements라는 구현 키워드를 사용한다. 

클래스, 추상 클래스, 인터페이스는 모두 똑같다. 

  • 클래스, 추상 클래스, 인터페이스는 프로그램 코드, 메모리 구조상 모두 똑같다. .class로 다 이뤄진다. 인터페이스를 작성할 때도 .java에 인터페이스를 정의한다. 그렇기 때문에 인터페이스는 추상 클래스와 비슷하다고 생각하면 된다.

상속 vs 구현

부모 클래스의 기능을 자식 클래스가 상속 받을 때, 클래스는 상속 받는다고 표현하지만 부모 인터페이스의 기능을 자식이 상속 받을 때는 인터페이스를 구현한다고 표현한다. 상속은 부모의 기능을 물려받는 것이 목적이다. 하지만 인터페이스는 모든 메서드가 추상 메서드 이기때문에 물려받을 기능이 없고 오히려 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩해서 기능을 구현해야 한다. 따라서 인터페이스는 상속 대신 구현이라고 표현한다.

인터페이스를 사용해야하는 이유 -> 좋은 프로그램은 제약이 있는 프로그램

  • 제약: 인터페이스를 만드는 이유는 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현하라는 규약(제약)을 주는 것이다. 
  • 다중 구현: 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다. 반면에 인터페이스는 부모를 여러명 두는 다중 상속이 가능하다. 

인터페이스 - 다중 구현

자바는 다중 상속을 지원하지 않는다. AirplaneCar입장에서 move()를 호출할 때, 어떤 부모의 move()를 사용해야할지 애매한 문제가 발생하는데 이를 '다이아몬드' 문제라고한다. 그리고 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해 질 수 있다. 그렇기 때문에 자바는 클래스의 다중 상속을 허용하지 않는다. 대신 인터페이스의 다중 구현을 허용하여 이러한 문제점을 예방한다. 

인터페이스 자신은 구현을 갖지 않는다. 대신 인터페이스를 구현하는 곳에서 해당 기능을 모두 구현해야 한다. 여기서 interfaceA, interfaceB는 같은 이름의 methodCommon()를 제공하지만 이것의 기능은 Child가 구현한다. 그리고 오버라이딩에 의해 Child에 있는 methodCommon()이 호출된다. 결과적으로 어떤 부모의 methodCommon()을 선택하는 것이 아니라 그냥 인터페이스를 구현한 Child에 있는 methodCommon()이 사용된다. 이러한 이유로 다이아몬드 문제가 발생하지 않는다.

        interfaceA a = new Child(); // 부모는 자식을 담을 수 있다.
        a.methodA();
        a.methodCommon();
    
        interfaceB b = new Child();
        b.methodB();
        b.methodCommon();

  1. a.method()Common()을 호출하면 먼저 x001 Child 인스턴스를 찾는다.
  2. 변수 a가 InterfaceA 타입이므로 해당 타입에서 methodCommon()을 찾는다. 
  3. methodCommon()은 하위 타입인 Child에서 오버라이딩 되어 있다. 따라서 Child의 methodCommon()이 호출된다.

  1. b.method()Common()을 호출하면 먼저 x002 Child 인스턴스를 찾는다.
  2. 변수 b가 InterfaceB 타입이므로 해당 타입에서 methodCommon()을 찾는다. 
  3. methodCommon()은 하위 타입인 Child에서 오버라이딩 되어 있다. 따라서 Child의 methodCommon()이 호출된다.

클래스와 인터페이스 활용

클래스 상속과 인터페이스 구현을 함께 사용해보자

  • AbstractAnimal은 추상 클래스다. -> 상속 목적
  • Fly는 인터페이스이다. -> 구현 목적
    • Bird, Chicken은 날 수 있는 동물이다. fly() 메서드를 구현해야한다. 
public class Bird extends AbstractAnimal implements Fly

 

extends를 통한 상속은 하나만 할 수 있고, implements를 통한 인터페이스는 다중 구현할 수 있기 때문에 둘이 함께 나온 경우 extends가 먼저 나와야 한다. 

  • animal.sound()를 호출하면 참조 대상인 x001 Bird 인스턴스를 찾는다. 
  • 호출한 animal 변수는 AbstractAnimal 타입이다. 따라서 AbstractAnimal.sound()를 찾는다. 해당 메서드는 오버라이딩 되었기 때문에 Bird.sound()가 호출된다. 

  • 메서드 안에서 fly.fly()를 호출하면 참조 대상인 x001 Bird 인스턴스를 찾는다.
  • 호출한 fly 변수는 Fly 타입이다. 따라서 Fly.fly()를 찾는다. 해당 메서드는 Bird.fly()에 오버라이딩 되어 있다. 
  • Bird.fly()가 호출된다. 
728x90

'백엔드 > JAVA' 카테고리의 다른 글

⭐다형성과 설계  (0) 2024.06.02
10. 다형성(1)  (0) 2024.05.31
9. 상속  (0) 2024.05.30