22/05/09 자바 OOP 연습해보기

김석진·2022년 5월 9일
0

다시 초심으로

목록 보기
9/19

OOP로 만든 정렬 구현체

그냥 자바 애플리케이션을 만든 후 스프링을 똑같이 기능을 하는 것을 만들어보고 스프링 부트를 써서 무엇이 더 편리해 졌는데 알아보려고 한다.
( 물론 이건 강의에서 적어 둔 건데 아직 내가 제대로 이해하지 못한거같아서 다시 해볼예정이다)

OOP 프로그래밍?

소프트웨어를 기능 논리보다는 데이터나 객체로 설계하는 것이다.

GitHub에 issue를 추가했다

내가 해야하는 것들을 세팅을 해뒀다 이것을 하면서 Java oop에 대한 연습을 진행 하겠다.

모든것을 구현을 하고나서 이 프로그램이 괜찮은지?


Main에서 BubbleSort를 구현한 코드를 사용했는데 갑자기 JavaSort를 사용하고 싶을때가 있다.
아니면 다른 요구사항으로 인해 바꿔야 할때가 있다.
이때는 타입을 바꾸고 new 뒤에 객체 생성한 부분도 바꿔야한다.

내부 코드를 건들지않고 구현체를 바꿀 수 있다.

객체지향설계(SOLID)

  • SRP: 한 클래스는 하나의 책임만 가져야 한다.
  • OCP: 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야한다.
  • LSP: 프로그램의 객체는 프로그램의 정확성을 깨트리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야함
  • ISP: 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다.
  • DIP: 프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다.

이때 사용할 수 있는데 DIP(Dependency Inversion Priciple)이다.

코드에서 살펴보기


이때 BubbleSort는 구현체이다. 그렇기 때문에 바꾸기도 어렵고 테스트할때도 구현코드를 바꿔서 실현하기가 어렵다.

테스트 코드에서도 정렬을 하는것을 추측은 하지만 외부에서 코드가 어떤 동작을 하는지 정확히 알수없다.
이때 구현체가 Main코드에 강결합 되어있다라고 한다.
이부분을 인터페이스를 이용한다면 결합을 느슨하게 바꿀수있다.

기능에 대한 설계를 인터페이스로 바꿔보기


이렇게 sort기능을하는 인터페이스를 만들고 BubbleSort가 이 메소드를 구현하게 만들예정이다.

이렇게 인터페이스를 상속받아 @Override에노테이션을 이용해 해당 메소드를 구현했다는 것을 나타내줬다.


이렇게 변수선언부를 바꾸지않고 구현체를 바꿔서 구현을 할 수 있지만 여전히 Main메소드는 정렬 알고리즘을 알고있다. Main 부분에서 제어의 역전을 이용하여 Main이 정렬알고리즘을 모르게 만들 예정이다.

Service로직 추가하기


메인 메소드에서 구현된 기능과 동일한 기능을 Service에 작성을 하였다.
하지만 아직 SortService와 정렬알고리즘간에 강한 결합이 있기때문에 DI(의존성 주입)을 이용하여 결합을 낮춰준다.

이렇게 진행된 코드는 똑같은 기능을 하지만 doSort는 list를 받아서 정렬구현체를 이용해 정렬한 값을 내보내준다. 하지만 이코드는 정렬 인터페이스를 가지고 있지만 어떤 정렬 구현체를 사용할 건지 알 지 못한다

테스트코드를 이용해 확인해보자

class SortServiceTest {

    private SortService sut = new SortService(new JavaSort<String>());
    @Test
    void test(){

        //Given

        //When
        List<String> actual = sut.doSort(List.of("3", "1", "2"));
        //Then
        assertEquals(List.of("1","2","3"),actual);
    }

}

지금 이코드를 살펴보자 DI를 하기전에는 정렬 알고리즘을 바꾸기위해서 Service 코드 doSort메소드 내에서 정렬구현체를 변경을 해야했다.
DI로 변경한 후에는 new SortService()의 괄호안에 다른 구현체를 주입을 하면된다. 즉, 서비스 코드는 그대로있고 서비스를 사용하는 곳에서 다른구현체를 주입을하면된다는 뜻이다.

이게 의존성 주입이다..! 그러니까 기능을 구현한곳에서는 구현체를 변경하지않고 기능을 사용하는곳에서 구현체를 주입하면 쉽게 바뀌는구나 라고 알게되었다.

Spring의 적용 , Spring이 무엇을 도와주는가?

Spring의 핵심 기능

  1. The IoC Container(제일중요)
  2. Resources
  3. Validation, Data Binding, and Type Conversion
  4. Spring Expression Language(SpEL)
  5. Aspect Oriented Programming with Spring
  6. Null-safety
  7. Logging
    이 키워드들을 찾아보고 공부해보기

IoC(Inversion Of Controll) 제어의 역전

전통적인 제어 흐름에 비춰볼때, 제어흐름을 반대로 뒤집은것 -Wikipedia

HOLLYWOOD PRINCIPLE

DON'T CALL US, WE'LL CALL YOU

IOC를 설명하는 예 중 하나이다.

WIKIPIDIA에서의 IOC 예제

public class ServerFacade
{
  public Object respondToRequest(Object pRequest)
  {
    if(businessLayer.validateRequest(pRequest))
    {
      DAO.getData(pRequest);
      return Aspect.convertData(pRequest);
    }

    return null;
  }
}

이코드가 전통적인 제어의 흐름을 보는 예제이다
ServerFacade가 검증도하고 DAO를 알고있고 데이터를 받아오고 바꾸는 과정을 다 결합되어있다.
-> Validate, getData, convertData가 바뀐다면 ServerFacade도 변경이 되어야하는 구조이다..

public class ServerFacade
{
  public Object respondToRequest(Object pRequest, DAO dao)
  {
    return dao.getData(pRequest);
  }
}
이것은 IOC를 적용한 예제이다. 메소드 시그니처에 입력인자에 드러나게 되어있다.
입력인자에 다른동작을 하는 구현체를 입력한다면 ServerFacade는 자기할일을 할 수 있다.
검증로직이 있든 없든 상관이 없다는 뜻이다.
이렇게 함으로써 IoC를 구현할수 있다는 것이다.

Resource

low-level resource에 접근할 수 있는 보다 폭넓은 기능을 제공

  • UrlResource
  • ClassPathResource
  • FileSystemResource
  • PathResource
  • ServletContextResource
  • InputStreamResource
  • ByteArrayResource
    이러한 구현체들을 가지고 있어서 Spring이 Resource를 접근할때 지원을 해준다.

Validation,Data Binding, Type Conversion

  • 데이터 검증
    - 검증 코드를 지원하지 않는다면 맨날 반복적인 검증 코드를 구현하고 사용하는 곳마다 반복되는 코드가 있을것이다. Spring은 Validation을 지원하기 때문에 공통적인 검증로직을 일관적이게 제공한다.
  • 데이터를 인식하고 자료형에 할당
  • 데이터 자료형의 변환

SpEL

스프링 애플리케이션의 런타임에 다양한 데이터에 접근하기 위한 언어
스프링에 더 특화되어 다양한 기능을 제공

@Value 에노테이션은 SPring에서 제공해주는 에노테이션이고 ("${}")은 SpEL이다.

Aspect Oriented Programming with Spring

  • AOP: 관점지향 프로그래밍- 공통 기능을 개발자의 코드 밖에서 필요한 시점에 적용 가능
  • AOP를 적극적으로 사용하고 지원하는 프레임워크가 Spring이다.
  • Proxy,Aspect,Join Point, Advice, Pointcut, Weaving
  • CGLib, Aspectj
  • AOP를 사용하지 않아도, 심지어 몰라도 여전히 프레임워크를 사용할 수 있따.

종합을 하자면 스프링은 엔터프라이즈 애플리케이션을 만드는데 필요한 거의 모든 요소를 지원해주는 프레임워크다 라는 결론을 도출 할 수 있었다.

스프링을 적용해보기

build.gradle에 스프링 프레임워크 의존성을 추가해보기

    implementation 'org.springframework:spring-context:5.3.8'
    testImplementation 'org.springframework:spring-test:5.3.8'

ApplicationContext를 만들고 bean을 주입해보기

ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

Main에 이렇게 일단 코드를 추가를 해두었다.
그리고 별도의 Config 클래스를 만들어 스프링 전용 Configuration 클래스를 따로 만들었다.

@ComponentScan("com.exm.springpractice ")
@Configuration
public class Config {
}

//- @ComponentScan안의 패키지루트아래의 모든 Spring bean들을 스캔하겠다라는 뜻이다.

Bean 등록해보기

@Component 애노테이션을 이용해 Bean으로 등록을 해두었다 나머지 JavaSort 등등을 등록을 해보겠다.

@Component
public class BubbleSort<T extends Comparable<T>> implements Sort<T> {

    @Override
    public List<T> sort(List<T> list) {
        List<T> output = new ArrayList<>(list);

        for (int i = output.size() - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (output.get(j).compareTo(output.get(j + 1)) > 0) {
                    T temp = output.get(j);
                    output.set(j, output.get(j + 1));
                    output.set(j + 1, temp);
                }
            }
        }
        return output;
    }



}

SortService는 서비스의 역할을 하는 빈으로 따로만든 @Component 애노테이션과 동일한 @Service 애노테이션을 붙여 서비스로 빈으로 등록했다.

@Service
public class SortService {


    //DI 방법 사용 1. 필드에 주입하고 싶은 객체를 넣는다.
    private final Sort<String> sort;

    // 생성자 주입방법으로 주입방법을 사용하기

    public SortService(Sort<String> sort) {
        this.sort = sort;
    }

    public List<String> doSort(List<String> list) {
        return sort.sort(list);
    }
}

Main을 bean을 이용하여 service를 가져오고 정렬 테스트를 진행해보기

ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        Sort<String> sort = context.getBean(Sort.class);
        SortService sortService = context.getBean(SortService.class);

        System.out.println("[result] " + sortService.doSort(Arrays.asList(args)));

이렇게 bean에등록된 SortService를 이용하여 정렬과정을 실행해 보았다.

테스트코드

하지만 테스트 코드에서 오류가 발생하였다.

WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sortService' defined in file ~~~~~

work.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.exm.springpractice.logic.Sort<java.lang.String>' available: expected single matching bean but found 2: bubbleSort,javaSort

즉 SortService에서 Sort라는 인터페이스를 구현한 구현체가 두개가 있어서 빈으로 매칭이 안된다는 뜻이었다.
그래서 강의에서는 SortService에서 @Qualifier 애노테이션을 이용하여 빈매칭을 시켜줬더니 결과가 알맞은 값이 나왔다.
하지만 나는 @Primary 애노테이션을 BubbleSort에 붙였다.
@Primary 는 빈들의 우선순위를 정하는 방법이라는 검색결과를 믿고 실행했더니 결과값이 제대로 나왔다!.

profile
주니어 개발자 되고싶어요

0개의 댓글