본문 바로가기

프로그래밍/파이썬

파이썬 디자인 패턴 개요

*** 본 글은 파이썬 디자인 패턴을 학습하며 요약한 글입니다.

개요


파이썬 디자인 패턴을 익히고 실무에 응용 할 수 있도록 실습

1장. 디자인 패턴 개요


이 책에서는 디자인 패턴을 크게 생성과 구조 그리고 행위 총 3종류로 분류한다.

  • 객체지향 프로그래밍의 이해
  • 객체지향적 디자인 패턴의 원리
  • 디자인 패턴의 종류와 맥락에 대한 이해
  • 동적 프로그래밍 언어 패턴
  • 생성과 구조, 행위 패턴

1.1 객체지향 프로그래밍

객체지향 맥락에서 객체(object) 속성(Data Members) 과 함수로 구성된다. 함수는 객체의 속성을 조작한다.

ex) Car라는 객체는 연료 잔량과 isSedan, speed, steering wheel(운전대), coordinates(위치) 등의 속성과 accelerate()와 차를 자회전시키는 take

파이썬은 객체지향 언어다. 파이썬의 모든 것은 객체다 라는 말이 있듯이 파이썬의 클래스 인스턴스와 변수는 개별적인 메모리 공간에 저장된다. 클래스 인스턴스의 객체는 애플리케이션의 목적에 따라 다른 객체와 상호작용한다.

객체

객체의 특성

  • 프로그램 내의 개체(Entity)를 나타낸다.
  • 개체는 다른 개체와 상호작용하며 목적을 달성한다.
  • 예를 들어 Person과 Car는 각각 개체다. Person은 Car를 타고 원하는 위치로 이동한다.

클래스

Class를 사용해 특정 개체를 표현한다.

  • 클래스는 속성과 행동을 포함하는 객체를 정의한다. 속성은 데이터의 요소이고 함수는 특정 작업을 수행한다.
  • 클래스에는 객체의 초기 상태를 설정하는 생성자가 있다.
  • 클래스는 일종의 템플릿으로 쉽게 재사용할 수 있다.

예를 들어 Person 클래스에는 name과 age 속성이 있고 직장으로 출근하는 gotoOffice() 함수가 있다.

메소드

객체지향 프로그래밍에서 Method의 역할을 다음과 같다.

  • 객체의 행위를 나타낸다.
  • 속성을 조작하고 작업을 수행한다.
class Person(object):
    def __init__(self, name, age): #생성자
        self.name = name #data members / attributes
        self.age = age
    def get_person(self,): #함수
        return "<Person (%s %s)>" % (self.name, self.age)

p = Person("John", 32) # p는 Person형 객체
print("Type of Object:", type(p), "Memory Address:", id(p))

실행결과

1.2 객체지향 프로그래밍의 주요 기능

객체지향 프로그래밍의 개념을 이해했다면 OOP의 주요 기능을 살펴보자.

캡슐화

Encapsulation의 기능은 다음과 같다.

  • 객체의 기능과 상태 정보를 외부로부터 은닉한다.
  • 클라이언트는 객체의 내부 구조 및 상태를 직접 수정할 수 없고 대신 수정을 요청한다. 요청의 종류에 따라 객체는 get과 set 같은 특수 함수를 사용해 내부 살태를 변경한다.
  • 파이썬에는 public과 private, protected(C++, 자바) 같은 캡슐화에 필요한 접근 제어 키워드가 없기 때문에 캡슐화의 개념이 없다. 함수나 변수 앞에 __(더블 언더바)를 붙여 접근을 제어한다.

다형성

Polymorphism의 기능은 다음과 같다.

  • 다형성에는 두 가지 의미가 있다.
    • 객체는 함수 인자에 따라 다른 기능을 한다.
    • class Person: def eat(self, food): print("eat food") def eat(self, fluid): print("eat food") def eat(self, food, fluid): print("eat food, fluid")
    • 동일한 인터페이스를 여러 형식의 객체들이 공유한다.
    • a = "John" b = (1,2,3) c = [3,4,5,6,7] print(a[1], b[0], c[2])
  • 파이썬은 다형성을 지원하는 언어다. 예를 들어 + 연산자는 두 정수를 더하거나 문자열을 합칠 때 모두 사용할 수 있다.

다음 예제를 보면 정수 인덱스를 사용해 문자열과 튜플, 리스트의 원소에 모두 접근할 수 있다. 파이썬의 다형성을 보여주는 예제다.

a = "John"
b = (1,2,3)
c = [3,4,5,6,7]
print(a[1], b[0], c[2])

상속

Inheritance의 개념은 다음과 같다.

  • 상속이란 클래스의 기능이 부모 클래스로부터 파생되는 것을 일컫는다.
  • 부모 클래스에서 정의된 함수를 재사용할 수 있고 애플리케이션의 기본 기능을 확장한다.
  • 상속은 여러 클래스 객체의 상호작용을 기반으로 계층을 형성한다. 파이썬은 자바와는 다르게 다중 상속을 지원한다.
class A:
    def a1(self):
        print("a1")

class B(A):
    def b(self):
        print("b")

b = B()
b.a1()

결과

 

추상화

Abstraction의 기능은 다음과 같다.

  • 클라이언트가 클래스 객체를 생성하고 인터페이스에 정의된 함수를 호출할 수 있는 인터페이스를 제공한다.
  • 클라이언트는 클래스의 복잡한 내부 구현에 대한 이해없이 간편하게 인터페이스를 사용할 수 있다.
class Adder:
    def __init__(self):
        self.sum = 0
    def add(self, value):
        self.sum += value

acc = Adder()
for i in range(99):
    acc.add(i)

print(acc.sum)

결과

컴포지션

Composition의 정의는 다음과 같다.

  • 객체나 클래스를 더 복잡한 자료 구조나 모듈로 묶는 행위다.
  • 컴포지션을 통해 특정 객체는 다른 모듈의 함수를 호출할 수 있다. 즉 상속없이 외부 기능을 사용할 수 있다.
class A(object):
    def a1(self):
        print("a1")

class B(object):
    def b(self):
        print("b")
        A().a1()

objectB = B()
objectB.b()

1.3 객체지향 디자인의 기본 원칙

SOLID

S_단일 책임 원칙

The Single Responsibility Principle 이란 클래스는 하나의 책임만 가져야 한다.

클래스를 구현할 때 한 가지 기능에만 중점을 둬야 한다. 두 가지 이상의 기능이 필요하다면 클래스를 나눠야 한다. 이 원칙에선 책임을 기능을 변경하려는 이유로 정의한다.

클래스를 수정하는 이유가 특정 기능 때문이라면 괜찮지만 두 가지 이상의 이유 때문이라면 클래스 자체를 나눠야 한다.

단일 책임 원칙의 장점

  • 특정 기능을 수정할 때 관련 클래스 외에는 건드릴 필요가 없다.
  • 한 개의 클래스에 여러 가지 기능이 있다면 관련된 모든 클래스를 수정해야 하는 상황이 발생할 수 있다.

O_개방-폐쇄 원칙

The Open/Close Principle 이란 클래스와 객체, 메소드 모두 확장엔 개방적이고 수정엔 폐쇄적이어야 한다는 원칙이다.

즉, 개발 단계에서 클래스나 모듈의 기능을 확장하더라도 기본 클래스는 수정하지 않도록 설계해야 한다. 클래스 확장만으로 새로운 기능을 구현할 수 있어야 한다.

기본 클래스는 건드리지 않고 클래스를 확장해 새로운 기능을 추가 구현할 수 있는 구조가 개방-폐쇄 원칙을 지키는 구조다.

장점

  • 기본 클래스를 수정하지 않기 때문에 실수가 줄어든다.
  • 기존 코드의 호환성을 보장한다.

L_리스코프 치환 원칙

The Liskov Subsititution Principle 이란 상속받는 클래스는 기본 클래스의 역할을 완전히 치환할 수 있어야 한다.

말 그대로 파생된 클래스는 기본 클래스를 완전히 확장해야 한다는 이야기다. 코드 수정 또는 추가 없이도 파생된 클래스는 기본 클래스를 대체할 수 있어야 한다.

I_제어 반전 원칙

The Inversion of Control Principle 이란 상위 모듈은 하위 모듈에 의존적이지 않아야 한다는 원칙. 세부 구현이 추상화에 의존해야 한다. 추상화가 세부 사항에 의존하는 상황은 바람직하지 않다.

원칙에 의하면 모듈은 지나치게 상호 의존하면 안 된다. 의존적인 모듈 사이에 추상화 계층을 둬 분리해야 한다.

클래스의 세부 내용은 추상화한다. 반대로 추상화가 세부 사항에 의존하는 구조는 피해야 한다.

제어 반전 원칙의 장점

  • 모듈 간의 낮은 상호 의존도는 시스템 복잡도를 줄인다.
  • 관련 모듈을 연결하는 추상화 계층 덕분에 모듈 간 상호 관계를 쉽게 관리할 수 있다.

D_인터페이스 분리 원칙

The Interface Segregation Principle 이란 클라이언트는 불필요한 인터페이스에 의존하지 않아야 한다는 원칙이다.

이 원칙은 효율적인 인터페이스 작성을 유도한다. 개발자는 반드시 해당 기능과 관련 있는 메소드만을 작성해야 한다. 해당 인터페이스와 상관없는 메소드를 포함하는 인터페이스를 구현하는 모든 클래스는 필요 없는 메소드까지 구현해야 한다.

예를 들어 Pizza 인터페이스는 add_chicken()과 같은 메소드가 있어서는 안 된다. Pizza 인터페이스를 구현하는 Veg Pizza(야채 피자) 클래스에는 불필요하다.

인터페이스 분리 원칙의 장점

  • 인터페이스에 특화된 메소드만 있는 가벼운 인터페이스를 작성하게 된다.
  • 의도하지 않은 메소드로 인터페이스가 채워지지 않도록 한다.

1.4 디자인 패턴의 개념

디자인 패턴은 GoF(Gang of Four)가 문제 해결책으로 처음 제시한 개념이다. GoF란 GOF의 디자인 패턴 을 집필한 네 명의 저자를 지칭한다. 이 책은 소프트웨어 디자인에서 흔히 발생하는 여러 문제의 해결책으로 총23개의 디자인 패턴을 소개하며 자바를 기반으로 한다.

디자인 패턴의 주요 기능

  • 언어에 독립적이며 모든 프로그래밍 언어에 적용할 수 있다.
  • 새로운 패턴이 아직도 연구되고 있다.
  • 목적에 따라 변경될 수 있어 개발에 유용하다.

디자인 패턴에 대한 개발자의 인식(대게)

  • 디자인 패턴은 모든 디자인 문제의 만병통치약이다.
  • 문제를 해결하는 훌륭한 해결책이다.
  • 대부분의 개발자가 인정하는 해결책이다.
  • 반복적인 부분이 있다.

문제를 자체적으로 해결해보려고 노력해도 결과가 불완전한 경우가 많다. 디자인 패턴은 완성도를 보장한다. 여기서 완성도란 디자인과 확장성, 재활용성, 효율성 들을 모두 포함한다. 디자인 패턴은 실패를 통해 배우기보단 이미 입증된 해결책을 통해 배워야 한다.

디자인 패턴을 언제 사용해야 하는지도 흥미로운 논제거리다. 소프트웨어 개발 사이클(Software Development Life Cycle)의 분석과 설계 단계 중 언제 사용하는 것이 맞는지에 대한 논쟁이 있다.

디자인 패턴은 이미 알려진 문제의 해결책이다. 그러므로 분석과 설계 단계는 물론 애플리케이션 코드와의 직접적인 관계 때문에 개발 단계에서도 생각해야 한다.

디자인 패턴의 장점

  • 여러 프로젝트에서 재사용될 수 있다.
  • 설계 문제를 해결할 수 있다.
  • 오랜 시간에 걸쳐 유효성이 입증됐다.
  • 신뢰할 수 있는 솔루션이다.

디자인 패턴 용어

아무 코드의 디자인을 디자인 패턴으로 분류할 수 없다. 예를 들어 문제를 해결할 때 사용하는 구조체나 자료 구조를 패턴이라고 부르지 않는다. 디자인 패턴 용어를 정리해보자.

  • 스니펫 Snippet : 데이터베이스에 연결하는 파이썬 코드 등의 특수한 목적을 위한 코드
  • 디자인 Design : 문제에 대한 해결책
  • 스탠다드 Standard : 문제를 해결하는 대표적인 방식. 포괄적이며 현재 상황에 적합한 방식
  • 패턴 Pattern : 유효성이 검증된 효율적이고 확장 가능한 해결책

디자인 패턴 맥락

디자인 패턴을 효율적으로 사용하려면 개발자는 애플리케이션의 전체 맥락을 정확히 이해해야 한다. 맥락은 다음과 같다.

  • 참가자: 디자인 패턴에서 사용되는 클래슬를 일컫는다. 각 클래스는 다른 역할을 한다.
  • 비기능적 조건: 메모리 최적화와 사용성, 성능 등이 여기에 속한다. 솔루션 전체에 영향을 미치는 핵심적인 요소다.
  • 타협선: 디자인 패턴이 모든 상황에 항상 딱 들어맞지 않으므로 적당한 타협선이 필요하다. 디자인 패턴을 사용하기 위해 결정해야 하는 부분이다.
  • 결과: 디자인 패턴이 적합하지 않은 상황에서 사용한다면 결과가 좋지 않은 경우가 있다. 개발자는 디자인 패턴의 영향과 사용법을 정확히 인지해야 한다.

1.5 동적 언어 패턴

파이썬은 Lisp와 같은 동적 언어다. 파이썬의 동적인 특성은 다음과 같다.

  • 자료형과 클래스는 런타임 객체이다.
  • 변수형은 런타임에 변경될 수 있다. (ex. a = 5, a = "John"처럼 변수 a의 값은 런타임에 지정되고 변수형도 변경된다.)
  • 동적 언어는 클래스 제한 측면에서 더 유동적이다.
  • 예를 들어 파이썬은 다형성이 언어에 구현돼 있다. private나 protected 같은 키워드가 없고 모든 변수는 기본적으로 public이다.
  • 동적 언어를 사용해 쉽게 디자인 패턴을 구현할 수 있다.

1.6 디자인 패턴의 분류

  • 생성 패턴
  • 구조 패턴
  • 행위 패턴

생성 패턴

특징

  • 객체가 생성되는 방식을 중시한다.
  • 객체 생성 관련 상세 로직을 숨긴다.
  • 코드는 생성하려는 객체형과는 독립적이다.

싱글톤 패턴 Singleton Pattern 생성 패턴의 한 종류다.

구조 패턴

특징

  • 클래스와 객체를 더 큰 결과물로 합칠 수 있는 구조로 설계한다.
  • 구조를 간결화하고 클래스와 객체 간의 상호관계를 파악할 수 있다.
  • 클래스 상속과 컴포지션을 중시한다.

The Adapter Pattern 구조 패턴의 한 종류다

행위 패턴

특징

  • 객체 간의 상호작용과 책임을 중시한다.
  • 객체는 상호작용하지만 느슨하게 결합돼야 한다.

The Observer Pattern 은 행위 패턴의 한 종류다.

1.7 정리

객체와 클래스, 변수 그리고 다형성과 상속, 추상화 등의 객체지향 프로그래밍의 기본적인 개념을 코드 예제와 함께 설명했다.