endsharp / study

1 stars 0 forks source link

[20221127] 객체 지향 설계와 스프링 #2

Open otakijae opened 1 year ago

otakijae commented 1 year ago

스프링 프레임워크 / 객체지향

otakijae commented 1 year ago
TGKim-Peter commented 1 year ago
  1. 스프링은 크게 스프링 프레임워크/스프링 부트 + JPA 라고 볼 수 있음.

    • 스프링 부트

      스프링을 편리하게 사용할 수 있도록 지원. Tomcat같은 웹 서버를 내장했기 때문에 별도의 웹 서버 설치 X. 스프링 부트는 스프링 프레임워크와 별도로 사용할 수 없음

    • 스프링 프레임워크

      스프링 DI 컨테이너, AOP, 이벤트, 기타 등 핵심 기술들을 말함.

      스프링 MVC, 트랜잭션, JDBC, ORM/XML 지원 등

    • JPA

      'Hibernate' 를 가지고 자바 표준인 JPA 를 만듦. 실무에서의 편리함과 표준에서 오는 안정감에 의해 많이 사용되고 있음

      • ORM - 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것
  2. EJB의 지옥에 공감하게 됨

    • 10년 전에 개발된 코드를 아직 운영중에 있는데, 최근 Core 시스템은 Migration을 했으나 여전히 운영 중인 function들이 있음. 아직도 그 개발자가 없으면 이해하기 어려움. 해당 Function에 대해 2년 내에 이 부분을 통합하는 개발을 진행할 예정에 있음 -> 적용을 어떻게할지 개인적으로 고민해볼 것
  3. 객체지향의 단점에 대해 한번더 생각해봄

    • 변경 가능성이 없는 코드에 대해 굳이 추상화를 통해 설계를 하게되면, 추상화 클래스를 한번 더 열어봐야 하기 때문에 불필요한 시간 소비가 됨.

    • 하지만 개발 단계에서 구체화에 대해 결정하는 시점을 미룰 수 있고 다형성을 유지할 수 있음.

      이는 큰 장점이라고 생각함!

  4. 좋은 객체지향이란 무엇인지에 대해 더 깊은 공부를 해야한다고 느낌

    SOLID 에 대해 한번 더 리뷰하고, 아직 명확하게 이해되지 않아서 다시 한번 볼 것.

  5. 스프링 DI나 IoC가 무엇일까?

    • 스프링 DI 란?

      Spring DI 컨테이너가 관리하는 객체를 빈 (Bean) 이라고 하고, 이 빈 (Bean)들을 관리한다는 의미로 컨테이너를 빈 팩토리 (BeanFactory) 라고 부른다.

      이 빈 팩토리 (BeanFactory) 를 상속 받아 여러 가지 컨테이너 기능을 추가하여 'ApplicationContext' 라고 부르고 쓰임

      Detail)

      • BeanFactory

      - Bean을 등록,생성,조회,반환 관리함.

      - 보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용함.

      - getBean() 메서드가 정의되어 있음

      • ApplicationContext

      - Bean을 등록,생성,조회,반환 관리하는 기능은 BeanFactory와 같음

      - Spring의 각종 부가 서비스를 추가로 제공함

      - Spring이 제공하는 ApplicationContext 구현 클래스가 여러 가지 종류가 있음

      Example) 출처 : https://www.nextree.co.kr/p11247/

      • IoC/DI 가 적용되지 않은 예시

        package kr.co.nextree;
        
        public class Foo{
        private Bar bar;
        
        public Foo() {
          bar = new SubBar();
        }
        }
      • IoC/DI 가 적용된 예시

        // 컨테이너
        <beans>
        <bean id="bar" class="kr.co.nextree.SubBar">
        <bean id="foo" class="kr.co.nextree.Foo">
             <property name="bar" ref="bar" />
        </bean>
        </beans>
        // 애플리케이션 코드
        package kr.co.nextree;
        
        public class Foo{
         private Bar bar;
        
          public void setBar(Bar bar){
                 this.bar = bar;
          }
        }

        Application 코드에서 setter 함수의 매개변수로 받아와서 실행 시 동적으로 의존관계를 설정.

    • 스프링 DI 에 의존성을 주입하는 3가지 유형 1) 생성자를 이용한 의존성 삽입 2) Setter() 메소드를 이용한 의존성 삽입 3) 초기화 인터페이스를 사용한 의존성 삽입

otakijae commented 1 year ago

https://cme10575.tistory.com/193

otakijae commented 1 year ago

https://velog.io/@moeun2/SOLID-%EC%A0%95%EB%A6%AC

sixhustle commented 1 year ago

스프링 핵심 원리 - 기본편

Section 1

자바 진영의 추운 겨울과 스프링의 탄생

단점뿐인 EJB의 시대가 있었기 때문에 Spring이 나오게 되었다. EJB는 복잡하고, 느리고, 객체 지향적이지도 않았다.

스프링이란?

자바 언어 기반의 프레임워크로, 객체 지향 언어의 특징을 살려내는 프레임워크

좋은 객체 지향

클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.

SOLID

원칙 설명
SRP(Single Responsibility principle) 한 클래스는 하나의 책임만 가져야 한다.
OCP(Open/closed principle) 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다.
LSP(Liskov substitution principle) 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
ISP(Interface segregation principle) 범용 인터페이스보다 여러 인터페이스가 낫다.
DIP(Dependency inversion principle) 추상화에 의존하자. 인터페이스에만 의존하라.

객체 지향 설계와 스프링

https://www.quickprogrammingtips.com/spring-boot/history-of-spring-framework-and-spring-boot.html

qazyj commented 1 year ago

Section 1

많은 분들이 Section 1에 대한 내용을 준비할 것 같아서, Section 1을 들은 내용을 정리하기 보다는 궁금했던 부분들에 대해 확인해보며 다양한 방법의 장단점에 대하여 공유하고자 한다.

해당 챕터에서 스프링은 좋은 객체지향 프로그래밍을 위해 만들어진 프레임워크다라는 것을 알았고, SOLID에서 DI 없이 지킬 수 없었던 OCP와 DIP를 Spring에서 IoC와 DI를 통해 해결한다는 것을 이해할 수 있었다. 이를통하여 현재 개발자들이 Spring을 통하여 좋은 객체지향 프로그래밍을 할 수 있는 것이다.

그렇다면, 만약 인터페이스 1개에 구현체가 2개있는 경우는 어떻게 될까?? 라는 궁금증이 생겼다.

실제 코드를 통해 알아 보았다. 아래와 같은 Car라는 Interface가 있다.

public interface Car {
    int go();
}

해당 인터페이스를 구현하는 구현체 k5, k7이 아래와 같이 작성되있다고 가정해보자. 참고로 @Compenent의 경우 해당 클래스를 Spring Bean으로 등록한다는 것을 말한다.

@Component
public class k5 implements Car{

    @Override
    public int go() {
        return 5;
    }
}
@Component
public class k7 implements Car{

    @Override
    public int go() {
        return 7;
    }
}

그리고 테스트를 진행해 보았다.

    @Autowired
    private Car car;

참고로, @Autowired의 경우 Car Interface에 맞는 Bean으로 등록 된 객체를 가져와 Spring에서 자동으로 DI를 해달라고 명시해주는 어노테이션이다. 하지만, car 부분에 빨간줄이 생기며 제대로 DI가 안된다는 것을 알 수 있었다.

원인의 에러는 다음과 같았다.

Could not autowire. There is more than one bean of 'Car' type.
Beans: k5   (k5.java) 
       k7   (k7.java) 

직역해보면 autowire할 수 없다. 'Car' type의 bean이 한개보다 많다. 라고 한다.

그렇다. 구현체가 두개 이상 있는 경우에는 역시나 DI를 제대로 하지 못한다.

그렇다면, 해결 방법으로는 어떤 것이 있을까??

  1. 변수 네이밍을 Bean 이름으로 한다.
  2. @Qualifier("Bean 객체 이름")을 사용한다.
  3. @Primary를 사용한다.
  4. 사용하는 구현체 1개만 @Component를 유지하고 나머지 구현체의 @Component를 지워 Bean에 구현체 한개만 등록되도록 한다.

1번째 방법을 통해 해결해본 코드이다.

    @Autowired
    private Car k5;

결과적으로 제대로 DI를 통해 객체를 주입받지만, SOLID의 원칙인 OCP와 DIP가 위배된 코드를 확인할 수 있다.

2번째 방법을 통해 해결해본 코드이다.

    @Qualifier("k5")
    @Autowired
    private Car car;

결과적으로 제대로 DI를 통해 객체를 주입받고, OCP도 지키며 DIP도 지킨다고 볼 수 있다. Spring에서도 @Qualifier를 추가하여 문제를 해결하라고 안내하긴 한다. 하지만,, @Qualifier("k5")를 사용하다가 후에 k7으로 주입하고 싶으면 @Qualifier("k5") -> @Qualifier("k7") 변경 작업이 불가피하기때문에 결과적으로 보면 OCP와 DIP를 위반한다고 볼 수 있다.

3번째 방법을 통해 해결해본 코드이다.

@Component
@Primary
public class k5 implements Car{

    @Override
    public int go() {
        return 5;
    }
}

해당 @Primary의 경우 구현체 중 우선순위를 정해준 것이기 때문에, 같은 interface의 구현체 중 1개만 정해야 하는 단점이 있지만, 테스트 클래스에서 수정하는 것이 아닌 구현체 클래스에서 수정하는 것을 볼 수 있다. 또한, OCP와 DIP도 지켜주는 모습이다.

4번째 방법을 통해 해결하는 방법은 사용하는 구현체 딱 한 개 말고는 @Component를 삭제하여 Bean에 등록을 안시켜주면 된다. Spring에서 Interface에 의존성 주입을 할 때 1개의 구현체만 있는경우는 문제가 생기지 않기 떄문이다.

결과적으로, 3번째와 4번째 방법이 좋은 해결 방법이라고 볼 수 있다.

가장 좋은 방법은 4번째 방법으로, 사용할 하나의 구현체만 Bean으로 등록하는 것이 좋은 방법인 것 같다.

하지만, 여러개의 구현체를 두고 상황에 맞춰서 각각 다른 구현체를 사용해야 한다면 2번째 방법을 통해 해결하는 것이 최선인 것 같다.

혹시나, 나중에 구현체를 두개이상 두는 경우 해당 방법으로 해결하면 좋을 것 같다.

LeeeeDain commented 1 year ago

스프링 과거

XML

-> XML 편의 기능 지원

-> 자바 코드

-> 자바 8

-> 스프링 부트 1.0

-> 스프링 프레임워크 5.0, 스프링 부트 2.0 (비동기 논블로킹 가능)

-> 스프링 프레임워크 5.2.x, 스프링 부트 2.3.x

스프링 프레임워크

스프링 부트

스프링 단어의 쓰임

스프링 왜 만들었나요?

객체 지향 프로그래밍

다형성의 본질

다형성의 한계

SOLID

스프링

개인적으로 생각해 볼 점

Q. 다형성을 설명할 때 인터페이스 + 오버라이딩으로 설명하는데 다형성이라는 장점을 강조하려면 인터페이스를 많이 사용하면 좋을 것 같은데 왜 실무에서는 인터페이스를 막상 잘 활용하지 않을까? 왜 불편할까? -> 이건 강의에도 나왔듯이 실제 구현체가 어딘지 찾는 단계가 하나 더 추가돼서 나중에 유지보수할 때 귀찮고 시간이 많이 들어가기 때문인 것 같다. 그래서 확장 가능성이 없으면 직접 구현한 클래스를 쓰라는 것...

Q. 대세는 객체 지향이 아닌 함수형이라는 소리도 있는데 둘의 장단점? 어떤 분야의 서비스를 개발할 때는 함수형이 더 편리할까?

Q. SOLID 원칙을 잘 지켜서 개발한 경험 / 원칙을 잘 지키지 못한 경험

otakijae commented 1 year ago
jin-ryu commented 1 year ago

자바 진영의 추운 겨울과 스프링의 탄생

스프링이란?

좋은 객체 지향 프로그래밍이란?

좋은 객체지향 설계의 5가지 원칙(SOLID)

클린 코드의 저자 로버트 C. 마틴이 정의한 좋은 객체 지향 설계의 5가지 원칙

  • SRP(Single Responsibility Principle): 한 객체는 하나의 책임만을 가진다.
  • 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C.마틴
  • 하나의 책임을 가진다? == 한 객체를 변경했을 때, 다른 객체들을 변경할 필요가 없다.
  • ex) 객체의 생성/사용을 분리한다.
  • OCP(Open/Closed Principle): 소프트웨어 요소는 확장에는 열려있지만, 변경에는 닫혀있어야 한다.
  • 다형성을 활용해 역할과 구현을 분리하면 지킬 수 있다!
  • 인터페이스(역할)의 새로운 구현 클래스(구현)을 만들면 기능을 확장할 수 있다. 또한 이를 확장한다고 했어 인터페이스에 의존하고 있는 다른 코드들을 변경할 필요는 없다.
  • LSV(Liscokv Substitution Principle): 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다
  • 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다. - 로버트 C.마틴
  • 인터페이스에 정의한 규약(문법 뿐만 아니라 기능에 대한 암묵적 정의)를 구현체가 모두 따라야지 믿고 구현체를 변경할 수 있다.
  • ISP(Interface Segregation Principle): 하나의 범용적인 인터페이스보다 특정 클라이언트를 위한 여러개의 인터페이스가 낫다.
  • 클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안 된다. - 로버트 C.마틴
  • 인터페이스를 분리하면, 인터페이스가 명확해지고 대체 가능성도 높아진다.
  • DIP(Dependency Injection Principle): 고차원 모듈은 저차원 모듈에 의존하면 안 된다.
  • 고차원 모듈 == 추상화 == 역할 == 인터페이스
  • 저차원 모듈 == 구체화 == 구현 == 구현 클래스
  • 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다.

다형성만으로는 OCP, DIP를 만족할 수 없다.

// DIP : 구체화와 추상화에 모두 의존하고 있다.
// OCP : 구체화를 변경할 때 클라이언트의 변경이 필요하다.
public class MemberService(){
    MemberRepository memberRepository =  new MemoryMemberRepository();
//  MemberRepository memberRepository =  new JdbcMemberRepository();
}

객체 지향 설계와 스프링