TIL - 0809

기초도 못하면서 응용 잘하는 사람 한명도 본 적 없다 기초부터 잘하자

객체지향

객체지향이 뭘까

  • 만들려고 하는 가상세계에 마스터가 모든 것을 처리하는 것이 아니라 여러 존재가 모여 협업하는 세상
    • 객체와 객체 간에는 협력을 함, 협력은 자동적으로 하는게 아니라 메세지에 의해 요청하는 것, 다른 객체는 요청만 할 뿐 처리에 대해 어떠한 관여도 할 수 없음 : 전적으로 담당 객체의 권한 - 그렇기때문에 객체 고유의 상태값 또한 바깥으로 가져올 수 있는 설계를 해서 안되고 조작도 마음대로 하지못하도록 만들어야함
  • 짧게 말해 분업과 협력의 세상

객체와 도메인 그리고 추상화

  • 객체 : 고유한 특성에 의해 다른 존재와 구별되는 존재 - 특성이라하면 예를 들어 나이, 키, 출신지역, 머리색 등 특정값을 가지는 속성과 행동이 있음
    • 추상화 : 공통된 속성, 행동을 하는 객체들을 하나의 타입으로 만드는 작업
  • 추상화 대상 생각해보기 : 세상에 존재하는 모든 객체가 어플리케이션 개발에 있어서 필요한 객체가 아님 , 필요한 객체의 모든 속성을 추상화할 때 속성으로 메소드로 선언할 필요없음
    • 도메인 분석에 따라(물론 만들면서 추상화해야겠지만) 필요한 객체, 메소드, 속성만 추출해서 추상화해야함 : 우리가 만들려고 하는 작은 세상에서 필요한 객체
  • 객체는 고유한 특성 때문에 구별되는 존재인데, 공통되는 객체 끼리 집합으로 묶어서 정의하는 것을 추상화라고 함
    • 실체가 아닌 상태이기때문에 추상화라고 함 : 실체는 고유한 특성을 가지는 객체

테스트에 따른 객체 분리 연습이 좋다고 생각하는 이유

  • 도메인 분석 후 예상되는 역할에 따라 객체를 미리 분리해둘 수도 있지만 오버된 설계가 나올 수 있다는 것
    • 경험적으로 생긴 개발 싸이클 : 필요한 기능이 있을 떄 그 기능을 어떻게 만들지부터 생각을 함 -> 테스트 작성 -> 해당 기능을 담당할 객체를 생각해봄 -> 해당 되는 객체가 없다면 새로운 객체를 만들 것인지 판단함 -> 추상화 -> 구현 후 테스트 케이스 실행(성공)
  • 처음엔 뭔가 설계를 예상하고 개발하는 것이 마냥 좋은지 알았지만 오버된 설계가 가져오는 비효율이 더 크다는 것을 시간이 지나면서 알게됨
    • 해당 부분은 줄이기위해서 다시 리팩토링하는 과정을 거치는 것이 생산성에 굉장히 큰 타격
    • 도메인 분석, 설계에 대한 경험이 많다면 그럴 수 있다고 생각하지만 경험이 적을수록 예상보단 만들다보니깐 역할에 맞는 객체가 없을 때 객체 분리에 대해 생각해보자

DI와 인터페이스

  • DI : 의존성 주입(Dependency Injection), 내가 필요한 기능이지만 굳이 내가 가지고 있을 필요없이 다른 사람이 들고 있는 기능을 끌어다써도(요청) 되는 경우에 사용하는 디자인 패턴 - 자판기를 구현할 것이라 음료수 저장, 빼기 등의 기능이 필요한데 굳이 콜렉션 프레임워크를 상속받아야할까? 의존하는게 더 낫지않을까?
    • 장점 : 변경에 유연해짐(의존 객체 타입 선언할 때 상위 타입으로 선언해두면 굉장히 유연 - 다형성), 컴파일로 변경을 하지않고 속성 파일만 변경하면 런타임 시에 전혀 다른 프로그램 흐름을 만들어낼 수 있음
    • 단점 : 단번에 파악하기 어려운 설계(주로 인터페이스로 DI될 대상의 타입이 선언되어있는데, 처음엔 파악하기 어렵지만 구조만 파악해두면 이후 파악하기가 쉬워짐), 코드 상에서는 복잡해지겠지만 설계 상에서 의존성을 느슨하게 낮춤으로써 변경점을 한 곳으로 몰아버릴 수 있게 됨)
public class VendingMachine {
    
    private BeverageStorage storage;

	public Beverage buy(Beverage beverage) {
	    
	}
}

public class BeverageStorage {
    private Map<Beverage, Integer> storage;
    
    public Beverage buy(Beverage beverage) {
        .
        .
        .
    }
}


/* 값 제한(범위)을 만들기위해 Enum을 사용 */
public enum Beverage {
    ORANGE(1000),
    .
    .
    .
}


/* DI를 할 때 유연성 확보를 위한 상위 개념에 의존하기 */
public class Car {
    
    private MoveStrategy moveStrategy;
    
    public Car() {
        moveStrategy = new BasicMoveStrategy();
    }
    
    public void changeMoveStrategy(MoveStrategy moveStrategy) {
        /* condition statement */
        this. moveStrategy = moveStrategy;
    }    
}

스프링

스프링 Core와 스프링 MVC

  • 스프링은 J2EE 기반의 어플리케이션을 조금 더 쉽게(추상화) 만들기위해 제공되는 프레임워크 : 만들어져있는 틀에 우리의 코드를 넣으면 동작도 알아서 함(IOC)
  • 프레임워크의 핵심인 스프링 Core(BeanFactory - 컨테이너, AOP 등)와 스프링 프레임워크(POJO인 Controller)가 제공하는 모듈인 MVC를 구분해서 생각해야함
    • Spring MVC 모듈을 사용하지않고도, Struts2, Servlet을 사용하고도 MVC 개발이 가능함

Handler와 Controller

  • Spring MVC 실행 흐름을 보면 HandlerMaping과 Handler로 표현되어있음 : 실제로 처리하는 객체는 Controller인데 Handler라고 왜 이름이 정의되어있을까?(오늘 러너의 최고 질문)
    • Handler라고 이름을 지은 것은 Spring MVC의 @Controller 혹은 @RestController가 Handler의 일종이기때문에 다시 말해서 Spring은 클라이언트의 요청을 처리할 역할을 Controller 로 못박지않음 : 유연하게 끼워 쓸 수 있게 하기위해 - Spring MVC가 아니더라도 다른 MVC 프레임워크를 사용할 수 있게 만들어둠(스프링 Core에서 제공하는 webBind 어노테이션만 핸들러의 일종에 선언되어있으면 리플렉션으로 처리하기떄문에 구체적인 타입은 뭐가 되어도 상관없음)
    • HandlerMapping이 요청 처리에 적합한 핸들러를 찾고 나서 리턴하는 타입이 HandlerExecutionChain : HandlerExecutionChain을 들어가보면 처리할 핸들러(Spirng MVC에서는 @Controller가 달린 POJO)와 핸들러 시작 전과 후에 있어서 전처리 후처리를 담당하는 Interceptor 리스트가 있음(인터셉터 또한 AOP의 일종 - 여러 컨트롤러에서 중복될 수 있는 코드를 Interceptor가 걸릴 대상을 거를 포인트컷을 지정해두고, 어떻게 처리할지 어드바이스는 인터셉터 구현체, 타겟은 핸들러)
public interface HandlerMapping {
    
    .
    .
    .
    @Nullable
	 HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	private final Object handler;

	@Nullable
	private HandlerInterceptor[] interceptors;

	@Nullable
	private List<HandlerInterceptor> interceptorList;
	
	.
	.
	.	
}
  • 이름(HandlerMapping, HandlerMethodArgumentResolver), 클래스 구성(HandlerExecutionChain) 하나하나 에도 스프링 철학을 엿볼 수 있음

AOP와 Aspect, Interceptor, Resolver

  • AOP : 굳이 번역하면 관점지향프로그래밍, 여러군데를 돌아다니면서 보면 관점을 바꿔서 본다느니 이런 말들이 있는데… 이름도 중요하지만 왜 사용하는지가 중요하지않을까?
    • AOP : 코드에 대해 전/후처리를 하기위해(인프라 로직과 비즈니스 로직을 분리), 그렇게해서 얻는 장점은 개발할 때 깔끔함(비즈니스 로직에 집중할 수 있음), 같은 인프라 로직인데 매번 코드를 반복하지않고도 적용할 수 있음(중복 제거)
    • 그럼 인프라와 비즈니스 로직의 차이는? 비즈니스 로직은 핵심 로직, 어플리케이션의 핵심 기능을 담당하는 로직이고 인프라 로직은 핵심적인 기능 로직은 아니나 시스템 운영에 있어서 필요한 기본적인 로직을 가리킴(트랜잭션, 로깅, 세션으로부터 로그인 유저 정보 가져오기 등)
@Transactional
public String delete(....) {

}
  • @Transactional : 스프링에서 제공하는 선언적 트랜잭션, 자바에서 트랜잭션을 만든다고 하면 begin과 try ~ catch, commit, rollback을 구현했어야했는데, 해당 어노테이션 하나면 트랜잭션 시작 - 끝 묶는 처리가 다 끝남
    • AOP 개념 : 트랜잭션이 필요한 곳에서 모두 try ~ catch begin 등 트랜잭션 처리에 필요한 코드를 거둬낼 수 있음(중복 제거)
    • 해당 어노테이션이 포인트컷 : 어노테이션이 선언된 대상만 찾으면 되니깐(대상까지도)
    • 트랜잭션이 어드바이스
    • 심화 공부 대상 : 어떻게 트랜잭션 코드가 없는데도 동작을 할까? 프록시, CGLib 등 스프링이 어떻게 트랜잭션 코드를 적용하는지 살펴보기
  • HandlerArgumentResolver와 같은 Resolver도 Interceptor도 중복 제거(코드를 한 곳으로 모음), 전/후처리 : AOP 일종

빈 생성과정

  1. 스프링빈으로 생성될 대상을 찾음 : streotype 어노테이션(@Component, @Service, @Controller, @Bean 등)
  2. 곧바로 생성하지않고 BeanDefinition을 작성함 : 클래스명, 상위 클래스명, 빈 스코프 등 - 인스턴스를 곧바로 생성하지않고 호출될 때 없으면 생성한다고 하는데, 이 부분에 대해서는 테스트 해봐야겠음
  3. 생성자로 인스턴스 생성 : 디폴트 생성자가 있으면 곧바로 생성됨, 디폴트 생성자가 없다면 다른 생성자를 찾음(생성자의 인자로 선언되어있는 객체를 컨테이너에서 찾음 -> DI)
  4. DI 진행 : 필요한 인스턴스를 컨테이너에서 DI 해줌 - 디폴트 생성자가 아니라 다른 생성자일 경우 인스턴스 생성 - DI 진행이 동시에 되는 것
  5. 초기화 : 필드에 대해 초기화를 진행함 - DI보다 뒤에 있는 이유는 DI한 객체에 의해 값이 초기화될 수 있기 때문에 시점이 뒤로 미뤄져있음

빈 스코프

  • 기본적으로 스프링 컨테이너에서 생성 - DI 및 관리 - 제거되는 빈은 싱글톤 스코프를 가짐 : 상태값을 가지지 않는 빈, 공통적으로 멀티 쓰레드 환경에서 같이 사용해도 상관없는 인스턴스를 빈으로 등록함
  • 이외 prototype 등 여러 스코프가 있음 : @Scope 어노테이션을 통해서 스프링빈 스코프를 지정할 수 있음
    • prototype으로 @Scope 선언된 스프링빈은 getBean으로 컨테이너에서 찾아올 때마다 새로운 빈이 생성되어서 관리됨 : 싱글턴으로 관리되는 빈에서 사용하면 안됨(상태값을 가지는 아이를 참조하는 것 - 싱글턴 빈을 초기화할 때 한번만 getBean해서 해당 빈을 가져오므로 상태값을 가지는 빈을 고정적으로 가지게되는 것임 고로 싱글턴빈이 무상태에서 상태 관리하는 것으로 됨)

빈 생성 방법

  1. @Component 등 streotype
  2. @Bean
  3. 한가지 방법이 더 있으나 아직 사용해본 적이 없어 사용하게 되었을 때 포스팅
  • @Bean과 streotype 어노테이션으로 생성하는 방법 2가지 나눠놓은 이유는?
    • @Bean으로 생성하는 경우는 스테레오 타입으로 선언되어있지않은, 내가 만든 코드가 아닌 다른 사람이 만든 클래스의 인스턴스를 생성하는데 빈에서 관리를 하고 싶을 때 수동으로 생성하고 스프링빈으로 지정해줄 때 사용함