디자인패턴, 왜 배워야 할까?

Soonyoung·2024년 5월 12일
1
post-thumbnail

본 글은 헤드퍼스트 디자인패턴 원문 1장을 참조하여 작성되었습니다.
사실, 본 글보다 이 책을 사서 읽는 것을 권장드립니다. 정말 추천합니다.

디자인패턴이란?

디자인패턴이란, 소프트웨어 개발에서 반복적으로 나타나는 문제들을 효율적으로 해결하기 위해 개발된 해결책들의 집합입니다. 즉, 프로그램을 설계할 때 사용할 수 있는 검증된 방법이라고 할 수 있습니다.

더 쉽게 설명하자면, 알고리즘에서 BFS나 이분 탐색 같은 방법들을 떠올리시면 됩니다. 예를 들어, 그래프 탐색을 할 때 DFS나 BFS와 같은 알고리즘을 사용하죠. 이러한 방법을 배우지 않더라도 시간이 지나면 유사한 알고리즘을 직접 구현할 수 있겠지만, DFS나 BFS를 미리 알고 있으면 문제를 더 빠르고 효율적으로 해결할 수 있습니다. 또한, 다른 개발자들과 의사소통할 때도 "DFS를 사용했어"라고 말하면 쉽게 이해시킬 수 있습니다.

디자인패턴도 이와 비슷합니다. 이전 개발자들이 설계해 놓은 패턴을 활용하면, 우리는 더 쉽고 빠르게 주어진 문제를 해결할 수 있으며, 동시에 다른 개발자들에게 "이 문제는 ~패턴으로 해결했어"라고 설명함으로써 의사소통에 드는 시간과 비용을 줄일 수 있습니다.

또한, 이러한 디자인패턴을 통해 객체지향 프로그래밍 원칙을 이해할 수 있습니다.디자인패턴은 단순히 일반적인 객체지향 프로그래밍을 설명하는 것이 아닙니다. 그러나 많은 디자인패턴이 객체지향 원칙을 기반으로 하고 있습니다. 따라서 디자인패턴을 공부하다 보면 객체지향 원칙의 중요성을 자연스럽게 이해하게 됩니다.

디자인패턴의 필요성

"엥? 나는 그냥 프로그래밍 해도 되던데 …. 이걸 디자인까지 해야돼?" 라고 생각할 수 있습니다. 물론, 기능 구현에 충실히 프로그래밍을 해도 동작을 훌륭하게 수행하는 프로그램을 만들 수 있습니다. 하지만, 좋은 프로그램은 변화에 유연하게 대응이 가능해야 합니다. 아래 예시를 보면서 변화에 "유연하게 대응이 가능"하다는 것이 무엇인지 알아보고 디자인의 중요성에 대해 배워봅시다.

철수는 오리가 등장하는 게임을 만든다고 가정해봅시다. 이 게임에서는 다양한 종류의 오리를 구현해서 등장시켜야 합니다! 디자인패턴을 배우지 않은 철수는 객체지향 디자인의 원칙인 “상속”을 이용해서 다양한 오리를 효율적으로 구현해보았습니다.

오리라는 부모 클래스를 사용했고 각각의 오리들은 이 부모 오리클래스를 상속받고 생김새 부분만 각각 구현해주면 됩니다. 나름 훌륭하네요.

‼️ 그런데 갑자기, 훈이가 게임에서 오리가 날아다니는 기능을 추가해야 한다고 합니다. 철수는 당당하게 부모 클래스의 오리에 fly()를 추가해서 해결했습니다.


하지만 충분하지 않습니다. 러버덕은 다른 기능은 동일하지만 날지 못 합니다. 따라서 러버덕은 fly 함수를 아무것도 못 하는 것으로 오버라이드 해주었습니다. 철수는 생각했습니다. “이정도 쯤이야…”

‼️ 그런데 갑자기, 짱구는 게임에서 목각오리도 추가 출시하고 싶다고 합니다. 철수는 이전과 같은 방식으로 오리 클래스를 상속받고, display(), fly()를 오버라이드 해주었습니다.


으… 목각 오리는 quack 소리도 못 내므로 이번엔 quack() 메소드까지 오버라이드 해줘야 하네요…. 아고 귀찮아. 똑똑한 철수는 훈이와 짱구가 원하는 것을 해결해주면서 생각했습니다. “친구들이 원하는 오리를 계속해서 만들어 준다면 끝이 없겠구나….” 그러면서 현재 이 방식에 아래와 같은 문제점들이 있다는 것을 깨달았습니다.

  1. 러버덕, 목각오리 모두 날지 못 하는데 각각 동일한 코드를 다시 작성해줘야 한다.
  2. 새롭게 추가되는 오리는 어떤 예외가 있을지 모르는데 예외가 생기면 다 오버라이딩 해줘야 한다.

이렇게 작성한 코드는 친구들의 요청사항을 들을 때 마다 작성해야 하는 코드가 선형적으로 늘어나게 됩니다.

철수는 디자인패턴을 알고있는 맹구에게 조언을 부탁하여 디자인패턴의 원칙 두 개를 듣습니다. 힌트 두 개를 사용해서 철수는 다시 한 번 디자인 해봅니다.

디자인원칙 1. 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분과 분리한다. 바뀌는 부분, 바뀌지 않는 부분을 뽑아냅니다.

모든 오리는 “나는행동”, “꽥꽥거리는 행동”, “형태 보이기” 등은 모두 공통적으로 갖고 있습니다. 대신에, 나는 행동 안에 “날기, 못 날기” 등의 달라지는 부분들이 있습니다. “형태보이기” 또한 “노랑색 형태, 빨간색 머리, 형형색색” 등 바뀌는 부분이 존재합니다.

디자인원칙 2. 구현보다는 인터페이스에 맞춰서 프로그래밍한다.

이런식으로 디자인하면 다른 형식의 객체에서도 나는 행동과 꽥꽥거리는 행동을 재사용할 수 있습니다.

철수는 이 두 가지 원칙을 활용해 이제 오리 행동을 아래와 같이 통합해 볼 수 있습니다.

  • Duck Class
public abstract class Duck {
	QuackBehavior quackBehavior;
	//기타 코드
	
	public void performQuack() {
		quackBeahavior.quack(); 
		// 꽥꽥 행동을 직접 처리하는 대신, 
		// quackBeahavior로 참조되는 객체에 그 행동을 위임합니다.
	}
}
  • RubberDuck Class
public class RubberDuck extends Duck {
	public RubberDuck() {
		quackBehavior = new Squeak(); //러버덕은 Duck에서 quackBehavior와 
		flyBeahavior = new FlyNoWay(); // flyBeahavior를 상속받는걸 잊지 마세요.
	}
	
	public void display() {
		System.out.println("저는 노란 오리입니다.");
	}
}

또한, 철수는 변덕이 심한 짱구를 위해, 오리의 기능을 동적으로 변경할 수 있도록 세터메소드(Setter Method)를 만들어 주었습니다. 이로서 게임 실행중에도 오리가 “꽥꽥”, “끽끽”, “꼬꼬” 등 울음소리를 동적으로 수정이 가능합니다.

public abstract class Duck {
	....
	
	public void setQuackBehavior(QuackBehavior qb) {
		quackBeahavior = qb;
	}
}

전체 코드를 도식화하면 아래와 같이 볼 수 있습니다.

각각의 행동들을 캡슐화한다는 객체지향의 원리도 볼 수 있네요. 또한, 첫 번째 방법과 비교하면 상속보다는 구성을 활용하면 다양한 행동들을 자연스럽게 재사용 가능하네요!

철수는 디자인원칙3 을 깨닫습니다. 디자인원칙 3. 상속보다는 구성을 활용한다.

정리

이렇듯, 변화하는 요구사항에 대응하려면 코드에도 “디자인”이 필요합니다. 이전 개발자들이 경험을 통해 패턴화 해놓은게 디자인 패턴이구요. 축하합니다. 여기까지 글을 읽었다면 당신과 철수는 디자인패턴에서 가장 많이 쓰이는 패턴 중 하나인, 전략패턴 을 배웠습니다. 전략패턴은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해줍니다. 전략패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.

만약, 철수가 전략패턴을 미리 알았다면 이렇게까지 고생하지 않고 프로그램을 작성할 수 있었겠죠? 또한, 맹구는 철수에게 전략패턴을 사용해보는 건 어때? 라고 제안할 수도 있었을 것 입니다. 따라서, 디자인 패턴을 배우고 공부한다면 아래의 장점이 있습니다.

  1. 복잡한 문제를 해결

    복잡한 문제를 객체지향적(코드의 재사용성, 동작의 유연성)으로 문제를 해결할 수 있습니다.

  2. 빠르고 효율적인 방법

    프로그래밍시 디자인을 빠르게 할 수 있습니다.

  3. 개발자 간 의사소통의 용이

    협업 시 의사소통의 시간 및 비용을 절약할 수 있습니다.

하지만, 디자인패턴 역시 사소한 단점이 존재합니다. 단점을 고려하여 적절한 상황에서 올바르게 사용해야 합니다.

  1. 복잡성 증가

    작은 프로젝트나 단순한 문제에서는 오히려 과도한 설계가 될 수 있습니다.

  2. 유연성 제한

    특정 디자인패턴을 적용하면 유연성이 제한될 수 있습니다.

  3. 성능저하 가능성

    디자인패턴을 사용함으로써 간접적인 호출 또는 추상화가 많아져 성능이 저하될 수 있습니다.

  4. 학습곡선

    디자인패턴을 제대로 이해하고 적용하기 위해서는 학습이 필요합니다. 객체지향의 개념이 잡히지 않은 개발자 입장에서는 이해하고 적용하기 어려울 수 있습니다.

0개의 댓글