-
1주차 스프링 스터디Spring/Spring 2021. 4. 19. 22:11
스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크
객체 지향 프로그래밍?
- 객체 지향 프로그래밍은 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다.
- 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.
- 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
객체 지향 프로그래밍을 위한 세가지 요소
- 캡슐화
- 다형성
- 상속
캡슐화
- 객체 지향의 중요한 원리는 캡슐화이다.
- 캡슐화는 외부에서 알 필요가 없는 부분을 감춤으로써 대상을 단순화하는 추상화의 한 종류다.
- 객체는 자신이 어떤 데이터를 가지고 있는지를 내부에 캡슐화하고 외부에 공개해서는 안된다. 객체는 스스로의 상태를 책임져야 하며 외부에서는 인터페이스의 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.
- 이 사각형의 너비와 높이를 증가시키는 코드가 필요하다고 가정해보자.
- 이 코드는 Rectangle 외부의 어떤 클래스 안에 다음과 같이 구현돼 있을 것이다.
이 코드에는 많은 문제점이 있다.
1. '코드 중복'이 발생할 확률이 높다.
- 다른 곳에서도 사각형의 너비와 높이를 증가시키는 코드가 필요하면 그곳에도 getRight와 getBottom 메서드를 호출해서 right와 bottom을 가져온 후 수정자 메서드를 이용해 값을 설정하는 유사 코드가 존재할 것이다.
2. 변경에 취약하다.
- Rectangle이 right와 bottom 대신 length와 height을 이용해 사각형을 표현하도록 수정한다고 해보자.
- 현재 Rectangle 클래스는 int 타입의 top, left, right, bottom이라는 4가지 인스턴스 변수의 존재 사실을 인터페이스를 통해 외부에 노출시키게 된다.
- 결과적으로 getRight, setRight, getBottom, setBottom 메서드를 getLength, setLength, getHeight, setHeight로 변경해야 하고, 이 변경은 이 접근자 메서드를 사용하는 모든 코드에 영향을 미친다.
해결 방법은 캡슐화를 강화시키는 것이다.
Reactangle 내부에 너비와 높이를 조절하는 로직을 캡슐화하자.
- 방금 Rectangle을 변경하는 주체를 외부의 객체에서 Rectangle로 이동시켰다.
- 즉 자신의 크기를 Rectangle 스스로 증가시키도록 '책임을 이동' 시킨 것이다.
- 이것이 바로 객체가 스스로를 책임진다는 의미다.
정리
- 유지보수성이란 두려움 없이 주저함 없이 코드를 변경할 수 있는 능력을 말하고. 이떄 가장 중요한 것은 캡슐화다.
- 시스템의 한 부분을 감춤으로써 뜻밖의 피해가 발생할 수 있는 가능성을 사전에 방지할 수 있다.
- 만약 시스템이 완전히 캡슐화된다면 우리는 변경으로부터 자유로워질 것이다.
다형성 ( polymorphism )
- poly - 많은 , 다양한 <-> Mono
- morph - 모습이나 형태가 변하다.
즉, 다형성이란 어떤 객체가 다양한 형태로 변할 수 있는 능력이다.
- 많은 사람들이 OOP의 핵심이라 여기는 특징 중 하나고
- 같은 지시를 내렸는데 다른 종류의 객체가 동작을 달리하는 것.
- 같은 지시 : 동일한 함수 시그내처 shout() 호출
- 달리 동작 : 객체의 종류에 따라 실제로 실행되는 함수 구현의 코드가 달라진다.
- 다형성은 객체지향의 프로그램의 컴파일 시간 의존성과 실행 시간 의존성이 다를 수 있다는 사실을 기반으로 한다.
- 어떤 함수 구현이 실행될지는 실행 중에 결정되며 이를 늦은 바인딩 이라고 한다.
정리
- 정리하자면 객체라는 것은 여러 메시지를 주고받으면서 협력관계를 갖는다.
- 협력이라는 것은 하나의 클라이언트와 서버가 될 수 있고 다형성을 통해 클라이언트는 역할만을 알면 될 뿐 그 구현을 변경하거나 확장해도 전혀 상관이 없다.
- 즉 다른 클라이언트에 영향을 주지 않고 변경하지 않고 서버의 구현 기능을 유연하게 변경할 수 있고 확장 가능하다.
상속
- 상속이 가치 있는 이유는 부모 클래스가 제공하는 모든 인터페이스를 자식 클래스가 물려받을 수 있기 때문이다.
- 상속을 통해 자식 클래스는 자신의 인터페이스에 부모 클래스의 인터페이스를 포함하게 된다.
- 결과적으로 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신할 수 있기 때문에 외부 객체는 자식 클래스를 동일한 타입으로 간주할 수 있다.
- OrderServiceImpl이 DiscountPolicy의 인터페이스에 정의된 distcount 메세지를 전송하고 있다.
- DiscountPolicy를 상속받는 FixDiscountPolicy와 RateDiscountPolicy의 인터페이스에도 이 오퍼레이션이 포함돼 있다는 사실을 주목하자.
- discount 메세지를 수신할 수 있는 FixDiscountPolicy와 RateDiscountPolicy 모두 DiscountPolicy를 대신해서 OrderServiceImpl과 협력할 수 있다.
정리
- 자식 클래스는 상속을 통해 부모 클래스의 인터페이스를 물려받기 때문에 부모 클래스 대신 사용될 수 있다.
- OrderServiceImpl의 생성자에서 인자의 타입이 DiscountPolicy여도 FixDiscountPolicy와 RateDiscountPolicy의 인스턴스를 전달할 수 있는 이유가 바로 이 상속 때문이다.
- 앞 선 다형성의 코드도 상속이 있기 때문에 가능했다.
2. SOLID 5원칙
문제점
SRP : 지금 OrderServiceImpl 클래스는 구현 객체를 생성하고 실행하는 다양한 책임을 가진다.
OCP : 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드도 변경이 일어나게 된다.
DIP : 인터페이스뿐만 아니라 구체 클래스에도 의존하고 있다.
이런 의존성을 가지도록 해결해보자!
- 이 문제를 해결하려면 누군가가 클라이언트인 OrdereServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.
- 생각해보면 OrderServiceImpl은 orderService와 관련된 일만 해야 하는데 FixDiscountPolicy로 굉장히 구체적인 것까지 선택하고 있었다
- OrderServiceImpl이 생성하고 구체적인 것을 선택까지 했다.
- 즉 굉장히 다양한 책임을 가지고 있었다. 따라서 관심사를 분리해야 한다.
- 이전에는 클라이언트 코드가 영향을 받았고 이제는 사용 영역의 어떤 코드도 변경할 필요가 없다.
- 구성 영역은 당연히 변경이 된다. 구체적인 설정은 누군가는 해야 되기 때문이다.
- 중요한 건 사용 영역에 코드는 아예 손댈 필요가 없다는 것이다. 즉 클라이언트의 코드를 전혀 변경하지 않았다. 사용영역의 코드는 전혀 손대지 않았다.
해결책
SRP : 지금 OrderServiceImpl 클래스는 구현 객체를 생성하고 실행하는 다양한 책임을 가진다.>> 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당해서 실행하는 책임만 담당하도록 함.
즉 SRP 단일 책임 원칙을 따르면서 관심사를 분리함.
OCP : 지금 코드는 기능을 확장해서 변경하면, 클라이언트(OrderServiceImpl) 코드도 변경이 일어난게 된다.>> AppConfig가 의존관계를 FixDiscountPolicy에서 RateDiscountPolicy로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨.
DIP : 인터페이스뿐만 아니라 구체 클래스에도 의존하고 있다.>> 기존에는 클라이언트 코드도 함께 변경해야 했지만 DiscountPolicy라는 추상화 인터페이스만 의존하도록 코드를 변경했고 AppConfig가 대신 생성해서 의존관계를 주입하도록 해주어 DIP 원칙을 따르면서 문제를 해결했다.
참고
'Spring > Spring' 카테고리의 다른 글
스프링 @Transactional과 잠금 (0) 2021.08.11 Mono.just()와 Mono.defer()에 대한 이해 (0) 2021.07.30 ThreadLocal 과 SpringSecurity (0) 2020.08.09 AuthenticationManager와 Authentication (1) 2020.08.09 SpringSecurityContextHolder와 Authentication (0) 2020.07.23