ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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로 이동시켰다.
    • 자신의 크기를 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이 생성하고 구체적인 것을 선택까지 했다.
    • 즉 굉장히 다양한 책임을 가지고 있었다. 따라서 관심사를 분리해야 한다.

     

     

     

    AppConfig.class

     

     

    • 이전에는 클라이언트 코드가 영향을 받았고 이제는 사용 영역의 어떤 코드도 변경할 필요가 없다.
    • 구성 영역은 당연히 변경이 된다. 구체적인 설정은 누군가는 해야 되기 때문이다.
    • 중요한 건 사용 영역에 코드는 아예 손댈 필요가 없다는 것이다. 즉 클라이언트의 코드를 전혀 변경하지 않았다. 사용영역의 코드는 전혀 손대지 않았다.

     

     

     

     

     

    해결책

     

    SRP : 지금 OrderServiceImpl 클래스는 구현 객체를 생성하고 실행하는 다양한 책임을 가진다.

     

    >> 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당해서 실행하는 책임만 담당하도록 함.

    즉 SRP 단일 책임 원칙을 따르면서 관심사를 분리함.

     

     

    OCP : 지금 코드는 기능을 확장해서 변경하면, 클라이언트(OrderServiceImpl) 코드도 변경이 일어난게 된다.

     

    >> AppConfig가 의존관계를 FixDiscountPolicy에서 RateDiscountPolicy로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 됨.

     

     

     

     

    DIP : 인터페이스뿐만 아니라 구체 클래스에도 의존하고 있다.

     

     

    >> 기존에는 클라이언트 코드도 함께 변경해야 했지만 DiscountPolicy라는 추상화 인터페이스만 의존하도록 코드를 변경했고 AppConfig가 대신 생성해서 의존관계를 주입하도록 해주어 DIP 원칙을 따르면서 문제를 해결했다.

     

     

     

     

     

    참고


    오브젝트 - 조영호

     

    스프링 핵심 원리 기본편 - 김영한

     

    개체지향 프로그래밍 및 설계 - 포프

     

     

Designed by Tistory.