다형성과 스프링의 기본 개념에 대해서

이정빈·2024년 4월 25일

TIL

목록 보기
4/8

상속

상속이라는 것은 부모가 자식에게 물려주는 행위를 말합니다.

  • extends , implements 등으로 상속을 할 수 있고, 공통 클래스를 위로 올리고 한번에 정리하고 수정이 가능하기에 자주 사용합니다.
  • 상속이라는 것을 하나의 확장의 개념으로 이해를 해야합니다.
    • 부모 클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 이를 상속받아 사용할 수 있다.
    • 부모 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 어떠한 영향도 받지 않습니다.
    • 따라서, 자식 클래스 멤버 개수는 부모 클래스보다 같거나 많습니다.

상속의 포함 관계

  • 즉, 클래스 간의 관계를 분석하여 관계설정을 해줄 수 있습니다. (상속관계 : is -a / 포함관계 : has - a)
public class Car {

    static final String company = "GENESIS"; // 자동차 회사
    String model; // 자동차 모델
    String color; // 자동차 색상
    double price; // 자동차 가격

    double speed;  // 자동차 속도 , km/h
    char gear = 'P'; // 기어의 상태, P,R,N,D
    boolean lights; // 자동차 조명의 상태

    Tire[] tire;
    Door[] door;
    Handle handle;

    public Car(String model, String color, double price) {
        this.model = model;
        this.color = color;
        this.price = price;
    }

		// 가변 길이의 배열을 받기 위함입니다.
    public void setTire(Tire ... tire) {
        this.tire = tire;
    }

    public void setDoor(Door ... door) {
        this.door = door;
    }

    public void setHandle(Handle handle) {
        this.handle = handle;
    }

    public double gasPedal(double kmh, char type) {
        changeGear(type);
        speed = kmh;
        return speed;
    }

    public double brakePedal() {
        speed = 0;
        return speed;
    }

    public char changeGear(char type) {
        gear = type;
        return gear;
    }

    public boolean onOffLights() {
        lights = !lights;
        return lights;
    }

    public void horn() {
        System.out.println("빵빵");
    }
}

단일 상속과 다중 상속

  • 다중상속

    Java 는 기본적으로 다중상속을 허용하지 않습니다.

    Why?

    • 만약 자식 클래스에서 여러 부모 클래스를 가지게 되면, 관계가 어마무시하게 복잡해져서 구별할 수 있는 기능을 상실하기 때문입니다.

    final

    • final 은 수정할 수 없는 값으로 선언을 하는 것이다. 하지만, 여기서 의문을 가질 수 있다. 상속 받은 클래스에서 변형을 하지 않으면 사용할 수 있지 않을까?

      정답은, 아닙니다! 상속은 무조건 Overriding 이라는 개념이 같이 따라 다닙니다. 그래서 final 은 상속 자체가 불가 합니다.


@Overriding & Super()

  • Overriding

    @Overriding 이라는 것은 부모 클래스로 부터 상속 받은 것을 재정의 하는 과정을 말합니다.

    • 선언부가 부모 클래스와 동일해야 합니다.
    • 접근 제어자를 좁혀서 재정의 불가합니다.
    • 예외는 부모 클래스의 메서드 보다 많이 선언 할 수 없습니다.
  • super

    super 는 생성자와 관련이 있는 녀석입니다. 즉, 부모 클래스의 멤버를 참조할 수 있는 키워드 입니다.

    • 객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해서 사용할 수 있습니다.

    • 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같은 경우 이를 구분하기 위해서 사용됩니다.

      public class SportsCar extends Car{
          String engine;
      
          String model = "Ferrari"; // 자동차 모델
          String color = "Red"; // 자동차 색상
          double price = 300000000; // 자동차 가격
      
          public SportsCar(String engine) {
              this.engine = engine;
          }
      
          public void booster() {
              System.out.println("엔진 " + engine + " 부앙~\n");
          }
      
          public void setCarInfo(String model, String color, double price) {
              super.model = model; // model은 부모 필드에 set
              super.color = color; // color는 부모 필드에 set
              this.price = price; // price는 자식 필드에 set
          }
      
          @Override
          public double brakePedal() {
              speed = 100;
              System.out.println("스포츠카에 브레이크란 없다");
              return speed;
          }
      
          @Override
          public void horn() {
              booster();
          }
      }

    Untitled

  • super()

    super() 자신이 상속받은 부모의 생성자를 호출하는 메서드입니다.

    • 상속에서의 생성자는 상속되지 않는 유일한 멤버함수이다. 따라서 부모클래스의 멤버를 초기화하기 위해서는 당연히 부모클래스의 생성자를 호출해야 할 것 입니다. 즉, 자식클래스 생성자를 호출할 때 부모클래스 생성자도 동시에 호출 해야합니다. 이때, super() 를 사용하여 부모 생성자를 호출해주는 메서드를 추가해줍니다.
    • 단, 부모클래스에 기본 생성자가 아닌 매개변수를 가지는 생성자가 있다면(=부모클래스에서 생성자가 오버로딩이 가능할 경우) 자동으로 추가되지 않습니다. (=묵시적 제공을 하지 않음)

다형성과 InstanceOf()

다형성이란, 여러 가지 형태를 가질 수 있는 능력을 말합니다.

  • 자동 타입변환
    • 서로 다른 두개의 클래스가 있는데 a 가 b를 상속 받은 경우를 생각해봅시다.
      • 이 경우에는 b num = new a(); 가 가능하다는 의미입니다.
      • 하지만, a 안에 있는 메서드를 부를 수 없습니다. 그리고, 반대의 경우는 불가능합니다.
  • 강제 타입 변환(Casting)
    • 단, 자동 형변환이 된 객체들만 다시 강제 타입변환을 통해서 원래의 형태로 돌아갈 수 있는 것입니다.
    • B num = new a();A num = new a()

다형성의 기능으로 인해 해당 클래스 객체의 원래 클래스 명을 체크하는 것이 필요한데 이때 사용할 수 있는 명령어가 instanceOf() 입니다.

// 다형성

class Parent { }
class Child extends Parent { }
class Brother extends Parent { }

public class Main {
    public static void main(String[] args) {

        Parent pc = new Child();  // 다형성 허용 (자식 -> 부모)
        Parent p = new Parent();

        System.out.println(p instanceof Object); // true 출력
        System.out.println(p instanceof Parent); // true 출력
        System.out.println(p instanceof Child);  // false 출력

        Parent c = new Child();

        System.out.println(c instanceof Object); // true 출력
        System.out.println(c instanceof Parent); // true 출력
        System.out.println(c instanceof Child);  // true 출력

    }
}

Abstract

미완성된 설계도라고 표현할 수 있다. 즉, 형식만 정의하고 자식 클래스에서 구현해서 사용하는 방식입니다.

public abstract class ClassName{

}
  • 추상 클래스는 추상 메서드를 포함할 수 있고, 추상 메서드가 없어서 추상 클래스로 선언이 가능합니다.
  • 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성 될 수 있습니다.
  • 추상 클래스는 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 만들 수 있습니다.

Interface

두 객체의 다리 역할을 해주는 형식입니다.

상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메서드를 구현해야할 때 인터페이스는 구현 클래스들의 동인한 사용 방법과 행위를 보장해 줄 수 있습니다.

public interface InterfaceName{

}
  • 모든 변수가 public static final 이어야 합니다. 다만, 없어도 알아서 추가 해줍니다.
  • 해당 인터페이스를 implemens 라는 키워드로 받아서 구현합니다.
  • 만약, 인터페이스의 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경해주면 됩니다.
  • 인터페이스 간의 상속도 가능합니다. 이때는 extends 키워드를 사용합니다. 이것은 기존 클래스와 다르게 다중 상속이 가능합니다!

Calculator 구현

우선 요구사항은 다음과 같습니다.

  • 간단한 계산기 구현으었으나, 연산자는 따로 빼서 Operator 라는 Interface의 operate(int a, int b)라는 메서드를 정의하여 각각의 클래스를 생성하여 구현했습니다.

  • AddOperator<class>

package weekOne.calculatorTwo.operator;

public class AddOperator implements Operator{

    @Override
    public double operate(int a, int b) {
        return a+b;
    }
}

위 같은 구현체를 Operator라는 Interface로 Overriding을 했기 때문에 위의 연산자를 사용하는 OperateCalculator 클래스에 Operator 객체를 생성했습니다.

  • OperateCalculator<class>
public class OperateCalculator{
    // 생성자로 받아온 변수를 저장하기 위한 용도
    private int a;
    private int b;
    private char oper;
    
    // 연산자를 받아와 쓰기 위한 Operator 객체 생성
    private Operator operator;
    
    // 계속 누적되지 않고 호출마다 db를 비워주기 위해 여기서 선언 및 정의
    private List<Double> data = new ArrayList<>();
    
    // 생성자
    public OperateCalculator(int a, int b, char oper) {
        this.a = a;
        this.b = b;
        this.oper = oper;
    }

    // 입력되는 연산자에 따라 Operator 객체를 그 안에 구현되어 있는 내부 연산 객체와 연결
    public List<Double> calculate(char oper) {
        switch (oper) {
            case '+':
                operator = new AddOperator();
                break;
            case '-':
                operator = new SubtractOperator();
                break;
            case '*':
                operator = new MultiplyOperator();
                break;
            case '/':
                operator = new DivideOperator();
                break;
        }
        // 해당 클래스 내의 List에 데이터를 저장 후 return
        data.add(operator.operate(a, b));
        return data;
    }
}
  • Calculator<class>
package weekOne.calculatorTwo;


import java.util.List;

public class Calculator implements Service{

    // 기본적인 exit, removeFirst, inquiry 동작을 따로 Service로 구현해 상속받아
    // 해당 클래스에 Overriding 한다.
    // mode의 Interface
    private List<Double> data;

    // 어떤 모드를 실행할지를 결정 받고, 그에 따른 계산 결과를 생성자를 두개를 만들어 설계
    public Calculator(int a) {
        this.data = new CircleCalculator(a).calculate();
    }
    public Calculator(int a, int b, char oper) {
        this.data = new OperateCalculator(a, b, oper).calculate(oper);
    }
    
    // 얻은 data에 방금 들어간 요소를 main에서 출력하기 위한 것  
    public double getAns() {
        return data.getLast();
    }

    @Override
    public void exited() {
        System.out.println("This system will be shutdown.");
        System.exit(0);
    }

    @Override
    public void removeFirst() {
        System.out.println("Remove " + data.getFirst() + ".");
        data.removeFirst();
    }

    @Override
    public void inquiry() {
        System.out.print("DataBase : ");
        for (Double ele : data) {
            System.out.print(ele + " ");
        }
        System.out.println("\n");
    }
}
  • main.java

public class App {
    static Calculator calc;
    static Scanner scanner = new Scanner(System.in);
    static int swit;
    public static void main(String[] args) throws CustomException {

        // 원 넓이 / 사칙연산 선택 및 나머지 조건 입력
        while(swit != 0){
            swit = 0;
            System.out.print("Which mode do you want to play? (Circle/Operators) : ");

            String mode = upper(scanner.nextLine());
            label:
            while (true){
                if(mode.equals("circle")){
                    System.out.print("반지름을 입력하세요 : ");
                    int num1 = Integer.parseInt(scanner.nextLine());

                    calc = new Calculator(num1);
                }else if(mode.equals("rect")){
                    System.out.print("첫번째 정수를 입력하세요 : ");
                    int num1 = Integer.parseInt(scanner.nextLine());
                    System.out.print("두번째 정수를 입력하세요 : ");
                    int num2 = Integer.parseInt(scanner.nextLine());
                    System.out.print("연산자를 입력하세요 : ");
                    char operator = scanner.nextLine().charAt(0);

                    calc = new Calculator(num1, num2, operator);
                }

                System.out.println("Answer : " + calc.getAns());

                System.out.print("How do you want to play? (change/exit/remove/inquiry) ");
                String query = upper(scanner.nextLine());

                switch (query) {
                    case "exit":
                        calc.exited();
                        swit++;
                        break;
                    case "remove":
                        calc.removeFirst();
                        break;
                    case "inquiry":
                        calc.inquiry();
                        break;
                    case "change":
                        break label;
                }
            }
        }
    }

    public static String upper(String mode){
        return mode.toLowerCase();
    }
}

Inversion of Control & Dependency Injection

IoC란 무엇일까?

IoC(제어 역전)은 프로그램의 개체나 부분에 대한 제어를 컨테이너나 프레임워크로 전송하는 소프트웨어 엔지니어링의 원칙입니다. 이러한 원칙은 OOP(객체지향 프로그래밍) 기법에서 자주 사용되는 개념입니다.

기존의 라이브러리에 요청을 하여 코딩하는 방법들과는 상반되게 IoC는 Framework를 우리의 프로그램의 흐름에 맞게 그리고 우리 코드에 맞게 요청을 하여 컨트롤 할 수 있습니다. 이러한 기능은 구현하기 위해서, Frameworks는 추가 동작이 내장된 추상화를 사용합니다. 만약 우리가 특정 동작(기능)을 추가하고 싶으면, 우리는 프레임워크 혹은 플러그인 클래스들을 우리 확장해서 사용해야 합니다.

이렇게 IoC 구조의 장점을 아래와 같습니다.

  • 작업의 실행을 구현에서 분리합니다
  • 다른 구현 간에 쉽게 전환할 수 있습니다
  • 프로그램의 더 큰 모듈성
  • 구성 요소를 분리하거나 종속성을 조롱하고 구성 요소가 계약을 통해 통신할 수 있도록 하여 프로그램을 테스트하는 것이 더 쉬워집니다.

즉, 다른 객체를 직접 생성하거나 제어하는 것이 아니라 외부에서 관리하는 객체를 가져와 사용하는 것을 말하며 아래 코드의 예시를 들겠습니다.

  • 클래스A에서 클래스B 객체 생성 예시
public class A{
	b = new B();
}
  • 스프링 컨테이너가 객체를 관리하는 방식
public class A{
	private B b;
}

이렇게 스프링은 직접 생성하는 것이 아니라, 어딘가에서 받아와서 사용합니다. 여기서 위 같은 객체를 가져오는 곳은 Spring Container입니다.

DI는 뭘까?

Dependency Injection은 우리가 IoC를 제어가 역전된, 즉 불러와서 사용하는 객체의 의존성들을 설정하는 패턴 중 하나입니다. 그러면 어떻게 Dependencies 를 주입하는지에 대해서 알아봅시다.

public class Store{
	private Item item;
    
    public Store(){
    	item = new ItmeImpl();
    }
}

위의 예시를 보면 구현체의 상위에 있는 Interface를 Container에서 불러와서 주입하여 주는 방식입니다. 하지만, DI를 사용하면 아래와 같이 다시 작성해볼 수 있습니다.

public class Store{
	private Item item;
    public Store(Item item){
    	this.item = item;
    }
}

위의 내용도 간단하게 정리를 하면, DI는 어떤 클래스가 다른 클래스에 의존한다는 의미입니다.

public class A{
	@Autowired
    B b;
}

바로 위의 예시에서 사용하는 @Autowired는 애너테이션 중에 하나로, 스프링 컨테이너에 있는 빈이라는 것을 주입하는 역할을 합니다. 여기서 빈은 스프링 컨테이너에서 관리하는 객체를 말합니다.

여기서 그러면 Spring Bean은 뭘까? 🫘

빈은 스프링 컨테이너가 생성하고 관리하는 객체를 말합니다. 위에서 @Autowired로 주입받은 B객체가 바로 빈입니다. 스프링 빈은 스프링 컨테이너에 등록하기 위해 XML 파일에 설정하거나, 애너테이션으로 등록하는 등의 방법을 제공합니다. 예시를 들어 보겠습니다.

  • 간단한 예시(1)
public class A{
	@Autowired
    B b;
}
@Bean
public class B{
}
  • 간단한 예시(2)
@Configuration
@ComponentScan("com.baeldung.constructordi")
public class Config {

    @Bean
    public Engine engine() {
        return new Engine("v8", 5);
    }

    @Bean
    public Transmission transmission() {
        return new Transmission("sliding");
    }
}
@Component
public class Car {

    @Autowired
    public Car(Engine engine, Transmission transmission) {
        this.engine = engine;
        this.transmission = transmission;
    }
}

이 부분은 조금 더 공부를 합시다....

Reference
1. https://velog.io/@rhdmstj17/java.-super와-super-완벽하게-이해하기
2. IoC / DI
3. IoC / DI

profile
백엔드 화이팅 :)

0개의 댓글