본문 바로가기

개발기술/설계|디자인패턴

Spring 도입배경 - 객체지향 디자인원리

자바에서 스프링 프레임워크로

  • 자바프로그래밍 : 개발자가 직접 모든 객체와 동작을 수행시킴 ; 모든 코드 환경을 개발자가 통제하는 환경
  • 프레임워크 : 외부에서 개발된 코드와 규칙에 따라서 개발자의 코드를 로드하는 방식으로, 개발자가 요소를 모두 통제하지 못함.
    • 웹프레임워크 : 동적인 웹서비스를 개발하기 위해서 데이터베이스 연동, 템플릿 형태의 표준, 세선관리를 위한 프로그램.
    • My code call library but framework call my code. 외부의 코드재사용이라는 관점에서 라이브러리와 공통점이 있으나, 개발자가 따라야하는 더 정형적인 규칙이 정해져있다는 측면에서 라이브러리와 다르다.

 

OOP 객체지향 프로그래밍

  • 객체지향프로그래밍 : 명령어의 목록이 아니라 독립적인 객체의 집합으로 보는 관점.  객체들 간의 상호작용을 중심으로 프로그래밍하는 방식. 각 객체는 데이터를 주고받고 데이터를 처리할 수 있다.
  • 객체지향의 특징 : 추상화, 캡슐화, 상속, 다형성 정리
    • 캡슐화 :  속성과 기능을 하나로 묶는 것. 데이터와 그 데이터에 대한 행동(메서드)이 하나의 '객체안에 함께 포함되어 있다
    • 추상화 :  객체의 공통적인 속성과 기능을 추출하여 정의하는것. 추상클래스(인터페이스) 형성
    • 상속 :  상위 클래스로부터 확장된 여러 개의 하위 클래스들이 모두 상위 클래스의 속성과 기능들을 물려받아 사용
    • 다형성 :  특정한 역할(인터페이스)가 여러가지 형태로 구현될 수 있는 성질. 호출자 코드에 영향을 주지않고 피호출자 변경가능. 이는, 호출자가 역할이라는 인터페이스(규약)에 의존하고 있되 구체적인 구현체에 의존하지 않기때문에 가능한 것.
      • 호출자는 인터페이스에 의존하고 있기때문에 인터페이스가 변경되지 않도록 안정적으로 설계가 중요(즉, API설계)

 

 

OOP의 핵심목적 : 분류와 교체

  • 소프트웨어란 하드웨어랑 달리 계속해서 수정하고 바꿀수 있다는 특징이 있음. 단적으로, 카카오톡은  수천번의 업데이트로 기능을 확장수정 했으며 구글은 수십억만줄의 코드를 관리하고 있음. 이러한 방대한 양의 코드를 효과적으로 조직화하여 유지보수를 쉽게할 수 있는 개발방법론이 객체지향 프로그래밍Programming Object Oriented임.
  • 유사한 데이터(field)와 로직(method)를class로 응집시켜 상호교류하여 동작하도록 하게 만드는 개발법.
    • 유사한 코드를 빠르게 찾을 수 있으며, 코드의 재활용성이 높아지며, 낮은 의존성으로 원하는 부분을 손쉽게 갈아끼울 수 있다.
  • 1. 분류(코드를 관련성에 따라서 잘 분류할 수 있어야한다.)
    • 도서관에서 책을 테마에 따라 분류하듯이, 유사한 코드끼리 classification이 필요함. class의 명명법을 통해 역할과 기능을 구획으로 나누고 새로운 코드를 어디다 넣어야할지 판단하게 끔 함.
  • 2. 교체(경우에 따라서 특정 모듈을 통째로 변경해야 할 수도 있다.)
    • 소프트웨어를 항상 교체와 변화가 용이하도록 유지시켜야함 ; 내 라이브러리가 없어진다면? 데이터베이스가 바뀐다면? 외부서비스를 바꾼다면?

 

 

 

OOP 방법론 :  SOLID원칙

  • SRP : Single Responsibility Principle 단일책임원칙(분류) ;한 클래스는 하나의 책임만 가져야한다. 변경시 파급효과가 적은 것
    • 역할과 책임의 구획화라는 것이 다소 모호하고 추상적인 개념이라 정답이 없기때문에 어려움이 있을 수 있음
    • 예시문제점) 초반에 1인개발, 소수 기능만 가지던 서비스가 다인체제, 복잡한 기능을 갖으면 code의 탐색의 어려움 그리고 팀원들의 code간 logic conflict문제가 발생함.
    • 예시해결책) 이를 방지하기 위해서 기능별로 클래스 분류하여 코드의 내용을 명확하게 하여 찾기 쉽게만들고 담당별로 자신의 클래스만 작업 진행.
  •  OCP : Open Closed Principle 개방폐쇄원칙(교체) ; 소프트웨어 요소는 확장에는 열려있고 변경에는 닫혀있어야한다.
    • 서비스의 확장이 필요하면 기존 클래스에 기능을 계속해서 추가하는 것보다는 새로운 클래스를 추가하는 방식을 고려해라.
      • if else에서 반복적인 케이스가 보이면 클래스 분리를 고려하라.
    • 다형성을 잘활용하라는 이야기, 인터페이스라는 역할과 클래스라는 구현이 있을때, 다른 종류의 구현을 추가하라는 것. 
      • 한계점은 java에서 다형성을 잘활용해도 결국 호출하는 client가 클래스 구현체를 assign하는 코드를 변경 해줘야함. 이러한 변경이 발생하지 않도록 하기위해서는 객체를 생성하고 연관관계를 맺어주는 조립자, 설정자가 필요함, 이는 스프링임.
    • 예시문제점)  초기 서비스 시에는 기능도 적고, 대응국가도 한국 뿐이었으나 비즈니스가 발전하면서 서비스 기능이 추가되고 대상국가에 따라 다른 로직을 적용해야했음. 처음에는 국가별로 달라야하는 서비스만 if문으로 로직을 달리 적용하였으나 국가별 차이점이 점점 늘어감에 따라 혼동이 심함.
    • 예시해결책)  국가별로 공통적인 서비스는 하나의 인터페이스로 묶어서 관리하고 국가별로 해당 인터페이스를 상속을 통해서 국가별로 구현함. 이렇게 할시, 서비스 확장이 편하고 새로운 국가가 추가되어도 개체 추가를 통해서 대응가능. 
  • LSP : Liskov Substitution Principle 리스코프 치환법칙(교체) ; 상속받은 클래스는 상속받은 부모/인터페이스와 동일한 성격의 동작을 해야 재활용 가능성이 높아진다.
    •  자동차를 상속하여 엑셀기능에 후진하는 기능을 오버라이딩 해서는 안됨
    • 예시문제점) 사실 실무에서는 상속을 많이 사용하지 않음. 상속시 오버라이드 유무, 오버라이드와의 기존 로직충돌, 자식기능을 과도하게 확장하면 재활용성이 낮아짐. 
    • 예시해결책) 1. 상속을 위해서 설계한 클래스만 상속 할것 - 꼭 오버라이드로 확장해야하는 기능만 오버라이드 할 것. 2. 부모클래스를 상속대신 인터페이스를 활용하라 3. 상속이 필요하다면 부모와 동일한 기능을 사용하도록 상속할것.
  • ISP : Interface Segregation Principle 인터페이스 분리원칙(교체) ; Interface도 SRP를 따라야 구현이 편리하고 재활용성이 올라감. 인터페이스도 단일 책임을 갖도록 분리해야한다. 
    • 클래스도 마찬가지이지만 인터페이스 구현시 역할과 책임을 분리를 잘해야한다. 너무 큰 인터페이스를 만드는 경우 구현된 클래스에서 불필요한 메서드를 만드는 경우가 발생함.
    • 문제점) OCP 사례에서처럼 여러국가에 공통된 하나의 인터페이스를 관리하는 경우, 해당 인터페이스에 너무 다양한 기능을 넣으면 1. 구현체가 너무 커지고 기능이 다양해져서 SRP원칙을 따르기 어려워 여러사람이 한가지 인터페이스를 수정하고 conflict가능성이 있음 2. 국가별 인스턴스를 생성할때 국가별로 사용하지 않는 method가 생김, 만약 팁을 주는 서비스를 IF에 구현시에 한국에서는 해당 로직을 사용하지 않음.
    • 해결책) 하나의 인터페이스에 계속해서 기능을 추가하지말고 특이적인 기능들(팁을 준다던지)은 별도의 인터페이스로 만든다. 그리하여 사용하는 기능만 구현될 수 있도록 클래스뿐만아니라 인터페이스도 나눠준다. 실제로 하나의 메서드를 가지고 있는 인터페이스만 생성하는 경우도 빈번하다.
  • DIP : Dependency Inversion Principle 의존성 역전 원칙(교체); 프로그램은 추상화(인터페이스)에 의존해야지 구체화(클래스)에 의존해서는 안된다.
    • 하위모듈의 변경이 상위모듈의 변경을 요구하는 의존성을 끊어내야한다. 개발을 하다보면 내가 사용하던 라이브러리가 다른 라이브러리로 변경하면 코드를 다 뜯어고쳐야하는 경우가 있는데 라이브러리에 직접적으로 의존하면 교체가 어렵다. 라이브러리에 과도하게 반결합하는 코드를 만들지 말고 라이브러리가 우리의 코드에 의존하도록 만든다.
      • 문제점) jedis adapter라는 특정 라이브러리에 모든 서비스가 의존하는 코드를 유지하고 있었는데 보안상의 이유로 lettuce로 변경이 필요하여 모든 서비스가 코드를 바꾸어야했음.
      • 해결점) jedis adapter라는 것을 직접적으로 각 서비스클래스에 이용하지 않고 jedis adapter을 거치는 interface를 따로 빼고, 각 서비스가 해당 IF를 통해서 로직을 연결하도록 설계한다. 그렇다면 외부라이브러리가 변경해도 연결되는 IF를 수정해주면 각 서비스를 바꿀필요없다. 마찬가지로 외부 db라던지 외부 업체를 사용할때 의존성 역전원칙을 지켜야함.
  • 마무리 : 새로 시작하는 어플리케이션에서는 위의 5가지 원칙을 찾기어렵고 지키기 위해서는 코드만 과도하게 복잡해질 수 있다. 초반에는 단순한 방법으로 코딩을 하다가 서비스의 연혁이 쌓이고나면 코드들의 구조와 확장필요 방향이 보이기시작하면 리팩터링을 할때 위의 원칙을 생각해보는 것을 추천한다.
  • Spring을 사용해야하는 당위성 : 위의 SOLID원칙들을 표준화하고 강제화하여 코드를 레고블럭과 같이 모듈화 시켜준다. 그리하여, 코드 작성을 간결하게 하고 쉽게 갈아끼울 수 있도록 해줌. 

 

 

 그 외 원칙들 

  • KISS (Keep in simple and stupid)
    • 여러가지 원칙들을 생각하고 기술들을 전부 적용시키려 하지말고 우선, 최대한 단순화 시켜라.
    • 기획이 오면 오는대로 구현하는 것이 아니라 기획 내용을 검토해보고 기획을 구현하는 개발이 너무 복잡하다면 기획자와 협의를 통해서 기획이 완벽하게 구현되지 않더라도 코드가 과도하게 복잡해지지 않도록 유지보수를 해라.
  • YAGNI (You ain't Gonna Need It)
    • 한때는 사용했더라도, 당장 사용하지 않는 코드라면 과감하게 지워버려라. 다른 사람들이 착각하여 유지보수하고 해당 코드를 이해하려고 시간이 낭비될수 있다.
  • FailFast
    • if문을 사용하여 분기시에, 여러가지 fail/exception case가 있을때, success case를 맨뒤로 보내어 가독성을 높이고 fail/exception case추가를 용이하게 만듬.

'개발기술 > 설계|디자인패턴' 카테고리의 다른 글

클래스 다이어그램  (1) 2024.09.10
프로젝트 설계 및 문서화  (0) 2024.08.14
디자인패턴  (0) 2024.08.10
성능 최적화 전략  (0) 2024.08.02
Java programming OOP 연습케이스(1)  (0) 2024.05.13