Spring

[스프링] 다형성 SOLID , DI, IoC 란? - 스프링 프레임워크 강의 1강

곽코딩루카 2024. 10. 8. 13:39
반응형

 

 

 

 

▶ 다형성이란?

 

자바에서 **다형성(Polymorphism)**은 객체 지향 프로그래밍의 중요한 개념 중 하나로, 하나의 객체가 여러 가지 형태를 가질 수 있는 능력을 의미합니다. 다형성은 코드의 재사용성과 유연성을 높이는 데 기여하며, 크게 두 가지로 구분됩니다: **컴파일 시간 다형성(Compile-time Polymorphism)**과 런타임 다형성(Runtime Polymorphism). 각각의 의미와 예시는 다음과 같습니다.

1. 컴파일 시간 다형성 (Compile-time Polymorphism)

이 유형의 다형성은 **오버로딩(Overloading)**을 통해 구현됩니다. 동일한 메서드 이름을 사용하되, 매개변수의 타입이나 개수를 다르게 정의함으로써 다형성을 실현합니다. 컴파일 시점에 어떤 메서드가 호출될지가 결정되기 때문에 정적 다형성이라고도 불립니다.

메서드 오버로딩(Method Overloading)

메서드 오버로딩은 동일한 메서드 이름을 가지지만, 다른 파라미터를 사용하는 경우입니다.

위의 코드에서 add 메서드는 여러 개 정의되어 있지만, 매개변수의 개수와 타입이 다릅니다. 호출 시점에 적절한 메서드가 선택됩니다.

2. 런타임 다형성 (Runtime Polymorphism)

런타임 다형성은 **메서드 오버라이딩(Method Overriding)**을 통해 구현되며, 주로 **상속(Inheritance)**과 **인터페이스(Interface)**를 통해 발생합니다. 부모 클래스의 참조 변수가 자식 클래스의 객체를 참조할 때, 자식 클래스에서 오버라이딩한 메서드가 실행되는 방식입니다. 이 때문에 동적 다형성이라고도 불립니다.

메서드 오버라이딩(Method Overriding)

오버라이딩은 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하는 것입니다.

 

위의 예제에서 myDog와 myCat은 Animal 타입의 참조 변수를 사용하지만, 실제로는 각각 Dog 객체와 Cat 객체를 참조하고 있습니다. 실행 시점에 해당 객체의 오버라이딩된 메서드가 호출되므로 런타임 다형성이 발생합니다.

 

다시 말해, 하나의 부모 클래스 타입의 참조 변수로 여러 가지 자식 클래스의 객체를 참조할 수 있으며, 실제 객체에 따라 다른 메서드가 호출되는 것을 의미합니다. 이는 **동적 바인딩(Dynamic Binding)**이나 **지연 바인딩(Late Binding)**이라고도 불립니다.

 

다형성의 장점

  1. 유연성: 코드의 변경 없이도 새로운 객체 유형을 쉽게 추가할 수 있어, 유지보수와 확장성이 뛰어납니다.
  2. 재사용성: 상위 클래스나 인터페이스에 정의된 메서드를 기반으로 하위 클래스에서 재사용할 수 있습니다.
  3. 코드 간결화: 동일한 메서드나 연산이 다양한 타입의 객체에 적용될 수 있기 때문에 코드가 더 간결하고 직관적입니다.

다형성의 다른 의미들

다형성은 자바에서만 사용되는 개념은 아니며, 프로그래밍 언어 전반에 걸쳐 사용됩니다. 자바에서는 크게 오버로딩오버라이딩을 통한 다형성으로 구분하지만, 일부 프로그래밍 이론에서는 다형성을 다음과 같이 세분화하기도 합니다:

  • Ad-hoc 다형성: 자바의 메서드 오버로딩처럼, 특정 상황에 맞게 여러 함수를 정의하는 것.
  • 매개변수화 다형성(Parametric Polymorphism): 자바의 **제네릭(Generic)**을 통해 다양한 데이터 타입에 대해 동일한 코드 로직을 적용할 수 있는 경우를 말합니다. 예를 들어, List<Integer>와 List<String> 같은 형태로 서로 다른 데이터 타입을 처리할 수 있습니다.

결론

자바의 다형성은 오버로딩과 오버라이딩을 통해 구현되며, 코드의 유연성과 재사용성을 크게 높여줍니다. 컴파일 시간과 런타임에서 각각 다른 방식으로 다형성을 실현할 수 있다는 점에서 자바의 강력한 기능 중 하나입니다.

 

 

 

 

 

 

 

 

 

▶ SOLID 5원칙

1. 단일 책임 원칙 (Single Responsibility Principle, SRP)

  • 정의: 하나의 클래스는 오직 하나의 책임만 가져야 한다. 즉, 클래스는 하나의 기능만을 수행해야 하며, 그 기능을 변경해야 하는 이유도 하나만 존재해야 한다.
  • 설명: 이 원칙은 하나의 클래스가 너무 많은 역할을 수행하지 않도록 하는 것이 중요하다는 것을 의미합니다. 클래스가 여러 책임을 가지면, 코드의 수정이 필요할 때 예상치 못한 부작용이 발생할 수 있습니다.

 

2. 개방-폐쇄 원칙 (Open/Closed Principle, OCP)

  • 정의: 소프트웨어 요소는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.
  • 설명: 이 원칙에 따르면 새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 새로운 클래스를 통해 확장하는 방식으로 개발해야 합니다. 이를 통해 기존 기능을 유지하면서도 새로운 기능을 추가할 수 있어 안정적인 시스템을 유지할 수 있습니다.

 

 

3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

  • 정의: 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다. 즉, 부모 클래스의 객체가 자식 클래스의 객체로 치환되더라도 프로그램이 정상적으로 동작해야 한다.
  • 설명: 이 원칙은 상속의 적절한 사용을 강조합니다. 자식 클래스는 부모 클래스의 모든 기능을 상속받아, 부모 클래스가 기대하는 모든 동작을 동일하게 수행할 수 있어야 합니다. 자식 클래스가 부모 클래스의 동작을 왜곡하거나 변경해서는 안 됩니다.

 

4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

  • 정의: 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다. 즉, 큰 인터페이스를 여러 개의 작은 인터페이스로 분리하여 클라이언트가 자신이 필요로 하는 기능만 사용할 수 있게 해야 한다.
  • 설명: 이 원칙은 인터페이스가 너무 많은 역할을 하지 않도록 하는 것이 핵심입니다. 하나의 큰 인터페이스를 사용하는 것보다 작은 단위의 인터페이스를 여러 개 만들어 각 클래스가 자신에게 필요한 인터페이스만 구현하도록 해야 합니다

 

5. 의존 역전 원칙 (Dependency Inversion Principle, DIP)

  • 정의: 고수준 모듈(정책 결정 모듈)은 저수준 모듈(세부 사항 모듈)에 의존해서는 안 된다. 두 모듈 모두 추상화에 의존해야 하며, 구체적인 구현이 아니라 추상화된 인터페이스나 클래스를 통해 의존성을 관리해야 한다.
  • 설명: 이 원칙은 의존성 주입을 사용하여 구체적인 클래스가 아닌 추상화에 의존하도록 만들어 유연한 설계를 가능하게 합니다. 이를 통해 변화에 쉽게 대처할 수 있고, 테스트와 유지보수가 용이해집니다.

 

 

 

 

 

 

▶ DI (Dependency Injection) - 의존성 주입

 

 

DI (의존성 주입)란 스프링은 객체의 의존성을 의존성 주입을 통해 관리한다.

객체를 직접 생성하는 것이 아니라 외부에서 생성한 후 주입시켜주는 방식이다.

DI (의존성 주입)를 통해 모듈 간의 결합도가 낮아지고 유연성이 높아진다.

 

의존성을 제거하는 클래스 구현 방법으로 각 클래스가 인터페이스를 기반으로 호출하게 하고, 의존성 주입을

통해 객체 생성을 추상화하는 방법이 있는데 이 방법이 DI(의존성 주입)이다.

(DI) 의존성 주입 방법에는

생성자 기반 의존성 주입 (Constructor Injection), Setter 기반 의존성 주입 (Setter Injection)이 있다.

 

 

일단 A라는 객체에서 B, C라는 객체를 이용할 때 2가지 방법이 있다.

DI (의존성 주입)

 

방법 1은  A객체가 B와 C 객체를 new 생성자를 통해 직접 생성하는 방법이다.

방법 2는  외부에서 생성된 객체를 setter()를 통해 사용하는 방법이다.

 

IOC 컨테이너에서 생성된 객체를 주입

두 번째 방법은 A객체에서 B, C 객체를 사용(의존)할 때 A객체에서 직접 생성하는 것이 아니라

외부 (IOC컨테이너)에서 생성된 B, C 객체를 조립(주입)시켜 setter 혹은 생성자를 통해 사용하는 방식이다.

 

 

스프링에서는 객체를 Bean이라 부르고, 프로젝트가 실행될 때 사용자가 Bean으로 관리하는

객체들의 생성과 소멸에 관련된 작업을 자동적으로 수행해주는데 객체가 생성되는 곳을 

스프링에서는 Bean 컨테이너라고 부른다.

 

 

 

 

▶ IoC (Inversion of Control) - 제어의 역전

 

IoC (Inversion of Control) 란 "제어의 역전"이라는 의미로, 말 그대로 메서드나 객체의

호출 작업을 개발자가 결정하는 것이 아니라, 외부에서 결정되는 것을 의미한다.

쉽게 말하면

객체를 필요할 때 생성해서 사용하는 것이 아니라 미리 생성해 놓고 꺼내어 사용하는 방식

 

IoC제어의 역전이라고 말하며, 간단히 말해 "제어의 흐름을 바꾼다"라고 한다.

객체의 의존성을 역전시켜 객체 간의 결합도를 줄이고 유연한 코드를 작성할 수 있게 하여

가독성 및 코드 중복, 유지 보수를 편하게 할 수 있게 한다.

 

기존에는 다음과 같은 순서로 객체가 만들어지고 실행되었다.

객체 생성 -> 의존성 객체 생성(클래스 내부에서 생성) -> 의존성 객체 메서드 호출

 

스프링(spring)에서는 다음과 같은 순서로 객체가 만들어지고 실행된다.

(1) 객체 생성

(2) 의존성 객체 주입

     - 스스로 만드는 것이 아니라 제어권을 스프링에게 위임하여 스프링이 만들어놓은 객체를 주입

(3) 의존성 객체 메서드 호출

 

스프링(Spring)이 모든 의존성 객체를 스프링이 실행될 때 다 만들어주고 필요한 곳에 주입시킴으로써

Bean들은 싱글턴 패턴의 특징을 가지며,

제어의 흐름을 사용자가 컨트롤하는 것이 아니라 스프링(Spring)에게 맡겨 작업을 처리하게 된다.

 

 

[참고 사이트]

https://velog.io/@gillog/Spring-DIDependency-Injection

출처: https://backendcode.tistory.com/102 [무작정 개발:티스토리]

반응형