공부하는 블로그

#8 좋은 디자인 = 유연한 소프트웨어 : 변하지 않는 것은 없다 본문

design patterns

#8 좋은 디자인 = 유연한 소프트웨어 : 변하지 않는 것은 없다

devtimothy 2019. 4. 1. 09:02

#8 좋은 디자인 = 유연한 소프트웨어 : 변하지 않는 것은 없다

본 블로깅은 Head first OOAD: 세상을 설계하는 객체지향 방법론 (한빛미디어) 책을 Typescript 문법으로 전환하며 공부하는 글입니다.
글을 읽기 전에, 광고 배너 한번씩만 클릭 부탁드립니다. 블로그 운영에 큰 보탬이 됩니다 :)

우리가 #1~#4에 걸쳐서 보았던 기타 상점 프로그램으로 다시 돌아가본다. 고갱님의 기타 상점 사업이 번창해서 이제는 "현악기" 상점으로 진화를 하게 된다. 축하한다. 🎉

만돌린이라는 악기를 취급할 예정이라고 하는데, 현재 프로그램에서 고갱님이 만돌린을 취급할 수 있게 만드려면 어떻게 해야할까? 아래는 클래스 다이어그램이 있다.

이제 만돌린을 검색할 수 있게 변경해보자.

여기서 Instrument는 추상 부모 클래스이다. 이에 따라서 Guitar에 있던 대부분의 내용이 Instrument로 옮겨갔다. Guitar는 Instrument를 상속한다.

추상 부모 클래스란?

Instrument 클래스를 보자. 이는 추상 클래스인데, 추상 클래스 자체는 Instrument 자체의 객체는 생성할 수 없다. Mandolin이나 Guitar 같은 Instrument의 서브 클래스가 지정되어야 한다. Instrument는 저장장소 역할만 하기 때문에 추상 클래스로 지정하였다. 구현은 서브 클래스들이 담당한다. Instrument는 실제 구현 클래스들을 위한 Generic 클래스이다.

MandolinSpec 클래스를 만들어보자

만돌린 클래스를 만들어보니 겹치는게 꽤 많다. InstrumentSpec 클래스로 추상 부모 클래스를 만들 수 있을 것 같다.

UML 용어와 표기

UML의 기호와 표기 방식은 많이 있지만, 기본적인 것만 가지고도 많은 사람들이 불만없이 잘 사용한다. 이정도만 익히고 사용하더라도 다른 사람들에게 잘 설명할 수 있을 것이다.

  • 추상 클래스
    • 자바: 추상 클래스
    • UML: 추상 클래스, 이탤릭체의 클래스명
  • 관계
    • 자바: 관계
    • UML: 연관, 일반 직선 화살표
  • 상속
    • 자바: 상속
    • UML: 일반화, 세모 화살표
  • 집합
    • 자바: 집합
    • UML: 집합, 마름모 화살표

코드 작성

코드가 너무 많아서… 여기를 참조하자.

Guitar, Mandolin은 생성자만 가지고 있는 클래스임을 확인할 수 있다. 과연 서브클래스가 필요한 것일까? 라는 생각이 들지만 필요하다. 클래스 타입을 확인하는 방법 밖에는 어떤 타입의 악기인지 알 수가 없다. 그래서 Guitar 클래스를 만들면서 그 생성자에는 MandolinSpec을 전달할 수 없다.

사실 Java로 작성되었던 코드에서는 (나는 Typescript로 하고 있다.) search 메소드가 두개로 갈린다. 하나는 MandolinSpec을 검색하는 것, 하나는 GuitarSpec을 검색하는 것으로 말이다.

추상 클래스를 사용해서 중복을 없애고 악기 속성은 캡슐화해서 사양 클래스로 빼는 것들은 잘한 일이다. 하지만 여전히 Guitar와 Mandolin 클래스가 비어 있고 addInstrument() 메소드가 악기에 대한 지식을 가지고 있는 것도 마음이 걸린다.

물론 검색 기능은 잘 돌아간다. 캡슐화, 중복 코드 피하기, 확장성이 좋게 만들기 등의 객체지향 원리를 녹였다. 물론 새로운 악기 타입을 추가하기에는 많은 작업이 필요하다. 재사용성은 어렵다. 모두 단단히 연결되었고, InstrumentSpec이 Instrument의 일부이다.

처음부터 완벽하게 소프트웨어를 만들긴 어렵다. 그래서 사용하면서 조금씩 바꿔나가는 것도 한 방법이다. 악기 타입이 추가된다고 생각해보자. 각 악기별로 특유의 속성이 있다면? 악기 클래스를 만들고, 사양 클래스를 계속 하나둘씩 만들어야 될 것이다. 그런데 각 악기별로 또 공통점이 보일 수도 있다. 점점 프로그램을 짜기는 복잡해질 것이다.

그렇다면 뭘 해야할까?

인터페이스

인터페이스는 여러 타입에 적용되는 행동을 정의하는 역할과 그 여러 타입을 사용하는 클래스들의 관심의 초점을 나타내는 역할의 두 가지 역할이 있다.

구현보다는 인터페이스에 의존하도록 코딩하는게 소프트웨어 확장에 용이하다.

예: 운동 선수 인터페이스 - 농구선수 클래스, 축구선수 클래스, 야구선수 클래스 등...

인터페이스는 프로그램에 유연성을 추가해준다. 야구선수 클래스와 같은 특정 서브 클래스하고만 상호작용함이 아닌 운동선수 인터페이스와 상호작용이 가능하다. 하키선수, 테니스선수 같은 운동선수 인터페이스의 서브클래스이면 어떤 클래스이든 괜찮다. (아직 안 만들어졌더라도 말이다.)

캡슐화

캡슐화는 객체의 행동 변화에 필요한 코드 변경을 국한하여, 어떤 객체지향 원리보다도 유지보수에서 생기는 문제를 막는 책임을 지고 있다. 쉽게 말하면 클래스를 불필요한 변경으로부터 보호한다.

변화 가능성이 있는 기능이 있다면, 자주 변하지 않을 부분과 분리하려는 노력이 필요하다.

화가 클래스가 있다면, 받침대 준비하고, 붓을 닦고, 그림 그리는 일은 변하지 않을 것이다. 그러나 현대풍 화법일지, 초현실주의 화법일지에 대한 것은 바뀔수 있다.

변경

모든 클래스가 변경에 대해 하나의 이유만을 갖도록 한다. 앞서서 "모든 소프트웨어는 변한다" 라는 불변의 진리를 함께 이야기했다. 클래스의 변경 요인을 줄여서 변경 가능성을 줄이는 것이 필요하다.

자동차 클래스가 출발, 멈춤, 타이어 교체, 운전, 세차, 오일점검까지 다 할 필요는 없다.

  • 자동차 클래스는 출발, 멈춤, 기름넣기
  • 차 세척 클래스는 세차
  • 운전자 클래스는 운전
  • 수리공 클래스는 기름 점검, 타이어 교체

이런식으로 나누는 게 좋다.

다음 포스트에서는 지금까지 만든 코드를 뜯어고쳐보는 시간을 갖도록 하자.

요약

  • 변하는 것을 캡슐화하라
  • 구현에 의존하기보다는 인터페이스에 의존하도록 하라
  • 각 클래스는 변경 요인이 오직 하나여야 한다.
Comments