UML - 객체지향 개발의 원칙

런던행·2021년 4월 5일
0

UML

목록 보기
4/4

코드 또는 다이어그램을 작성할 떄 어떤 설계 원칙을 적용해야 할까? 이 장에서는 UML 다이어그램이나 코드가 잘 설계되었는지 평가하는데 도움이 될 다섯 가지 설계 원칙을 논의하고자 한다.

설계의 품질

잘 설계한 시스템은 이해하기 쉽고, 바꾸기도 쉽고 재사용하기도 쉽다. 개발하는데 특별히 어렵지 않고, 단순하고 간결하면 경제적이다. 잘 설계한 시스템을 개발하는 일은 즐겁다. 반면, 잘못된 설계에서는 마치 썩는 고치처럼 역한 냄새가 난다.

나쁜 설계의 냄새

  1. 경직성: 무엇이든 하나를 바꿀때 마다 반드시 다른 것도 바꿔야한다.
  2. 부서지기 쉬움 : 시스템에서 한 부분을 변경하면 그것과 전혀 상관없는 다른 부분이 작동을 멈춘다.
  3. 부동성: 시스템을 여러 컴퍼넌트로 분해해서 다른 시스템에 재사용하기 힘들다.
  4. 끈끈함: 개발 환경이 배관용 테이프나 풀로 붙인 것처럼 꽉 다라붙은 상태다. 편집 - 컴파일 - 테스트 순환을 한 번 도는 시간이 엄청나게 길다.
  5. 쓸데없이 복잡함: 괜히 머리를 굴러서 짠 코드 구조가 굉장히 많다. 이것들은 대개 지금 당장 하나도 필요 없지만 언젠가는 굉장히 유용할지도 모른다고 기대하며 만든 것이다.
  6. 필요 없는 반복: 코드를 작성한 프로그래머 이름이 마치 '복사'와 '붙여넣기' 같다.
  7. 불투명함: 코드를 만든 의도에 대한 설명을 볼 때 그 설명에 '표현이 꼬인다'라는 말이 잘 얼울린다.

의존 관계 관리하기

잘못 관리한 의존 관계가 많은 냄새의 원인이다. 잘못 관리한 의존 관계가 서로 단단하게 결합하여 얽히고 설킨 코드로 나타난다. 사실 이렇게 얽히고 설킨 모양에서 바로 스파케티 코드라는 표현이 나왓다.
인터페이스를 만들어 의존 관계를 끊거나 의존의 방향을 바꿀수도 있다. 다형성을 사용하면 어떤 함수를 포함한 모듈에 의존하지 않고도 그 함수를 호출 할수 있다.

단 하나의 책임 원칙(SRP)

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.
클래스는 오직 하나만 알아야한다.
오직 하나의 책임만 져야한다. 더 핵심적인 말로 바꿔보면, 어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

그림 6.1을 보자 이 클래스는 너무 많은 것을 안다. 임금과 세금을 계산하는 방법도 알고, 자신을 디스크에 저장하거나 읽어 오는 방법도 안다. 그리고 자신을 XML로 변환하거나 XML에서 읽어오는 방법도 알고 있다. 여기서 '부서지기 쉬움' 냄새를 맡은 사람 없는가? XML에서 JDOM으로 바꾼다면 Employee도 변경 해야한다. 이 설계는 결합도가 너무 높다.
실제로는 이 모든 개념을 각기 다른 클래스로 분리하여 클래스마다 변경해야 하는 이유가 오직 하나만 있도록 만드는 것이 바람직하다.
Employee ㅇ클래스는 세금과 임금만 다루고, XML관련 클래스는 Employee 인스턴스를 XML로 바꾸거나 XML에서 읽어 들인다. 간단히 말해서 우리는 걱정거리르 나누고 싶다, 그림 6.2가 설계 후보 가운데 하나다.

개방 - 폐쇄 원칙(OCP)

소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서 개방, 변경에 대해서는 폐쇄 되어야한다.
이 원칙의 정의는 거창하지만, 의미는 간단하다. 모듈 자체를 변경하지 않고도 그 모듈을 둘러싼 환경을 바꿀 수 있어야 한다.

그림 6.4를 예로 들어보자. 이 그림에서 EmployeeDB라는 데이터베이스 퍼사드(facade)를 통해 Employee 객체를 다루는 간단한 애플리케이션이 있다. 이 파사다는 데이터베이스API를 직접다룬다. 바로 이것디 OCP를 위반하는 경우인데, EmployeeDB 클래스의 구현이 변경되면 Employee 클래스도 다시 빌드해야 할지도 모르기 떄문이다. Employee는 EmployeeDB를 통해 데이터베이스 API에도 묶인 셈이다. Employee클래스를 포함하는 시스템은 반드시 TheDatabase API까지 포함해야 한다.
단위 테스트를 할 때는 환경에 생기는 변화를 제어하고 싶은 경우가 자주 생긴다. 예를 들어 Employee객체는 테스트 환경에서 실제 데이터베이스를 바꾸고 싶지 않다.

그림 6.5처럼 EmployeeDB를 인터페이스로 바꾸면 호출이 올바른지 검증할 수 있다. 이 인터페이스에서 파생한 두 가지 구현을 만들 되, 하나는 진짜 데이터베이스를 호출하도록 하고 다른 하나는 테스트환경을 지원하도록 하면 된다. 이렇게 인터페이스를 만들면 데이터베이스 API와 Employee를 분리 할 수 있고, Employee를 손대지 않고도 Employee를 둘러싼 데이터베이스 환경을 변경할 수 있다.

리스코프 교체 원칙 (LSP)

서브타입은 언제나 자신의 기반 타입(Base Type)으로 교체 할수 있어야 한다. LSP에 따르면, 기반클래스(base class)의 사용자는 그 기반 클래스에서 유도된 클래스를 기반 클래스로써 사용할 때, 특별한 것을 할 필요가 없이 마치 원래 기반 클래스를 사용하는 양 그대로 사용할 수 있어야 한다.


그림 6.8에 나온 임금 지금 애플리케이션을 보자, Employee 클래스는 추상 클래스며 calcPay라는 추상 메서드를 가진다. SalariedEmployee(월급쟁이) 클래스는 분명히 월급을 리턴할 것이다. HourEmployee(시급쟁이) 클래스도 분명히 시간당 임금을 리턴할 것이다.
만약에 VolunterrEmployee(자원봉사자)를 추가하기로 결정하면 어떤일이 발생할까? 어떻게 calcPay를 구현해야할까?? 쉽게 생각 하면 calcPay함수는 0을 반환하면 된다. 하지만 이것이 정말로 옳은 것인가?? calcPay를 호출하는것이 이치에 맞다면, 자원 봉사자들에게 임금을 줄 수 있다는 의미가 내포된다. 0원이라는 월급 총계가 나온 임금명세서를 출력하고 메일로 발송하거나 이와 비슷한 말도 안되는 일이 벌어지는 당황스러운 상황이 발생 할 수 있다. 그렇다면 이 문제는 어떻게 해결 할 수 있을까. 답은 간단하다. 자원봉사자는 Employee 직원이 아닌다. 애초에 Employee 클래스에서 파생해서는 안 된다.

의존 관계 역전 원칙(DIP)

A. 고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.

B. 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.

더 쉽게 말해서, '자주 변경되는 컨크리트 클래스(concrete class)에 의존하지 마라' 만약 어떤 클래스에서 상복 받아야 한다면, 기반 클래스를 추상 클래스로, 어떤 클래스의 참조를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라. 만약 어떤 함수를 호출해야 한다면, 호출되는 함수를 추상 함수로 만들어라.
추상 클래스와 인터페이스는 보통 자신에게서 유도된 구체적인 클래스보다 훨씬 덜 변한다. 그러므로 구체적인 것보다는 이런 추상적인 것에 의존하는 편이 난다. 이 원칙을 지키면 변화가 일어났을때 시스템에 비치는 영향을 줄일 수 있다.

우리가 의존하면 안되는 것을 '자주 변경되는' 컨크리트 클래스다. 화발히 개발중인 컨크리트 클래스나 변할 가능성이 높은 비즈니스 규칙을 담음 클래스가 여기에 속한다. 이런 클래스의 인터페이스를 만든 다음, 이 인터페이스에 의존하는 것이 바람직하다.

인터페이스 격리원칙 (ISP)

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다.

그림 6.9에 나온 강좌 등록 시스템을 보자, 이 다이어그램에는 Student Enrollment라는 클래스를 사용하는 두 클라이언트가 있다. 두 클라이언트를 보면 4개 모두 사용하지 않고 있다.

각 클라이언트에게 딱 필요한 메서드만 있는 인터페이스를 제공해서 필요하지 않는 메서드를 제공하지 않는다. 그림 6.10은 이 규칙을 적용핸다.

레퍼런스 : UML 실전에서는 이것만 쓴다. http://www.yes24.com/Product/Goods/4492519

profile
unit test, tdd, bdd, laravel, django, android native, vuejs, react, embedded linux, typescript

0개의 댓글