객체지향과 디자인패턴

5 minute read

Published:

파이썬 디자인 패턴

객체지향과 디자인 패턴 개념을 매 번 공부해야한다고 마음만 먹고 시작을 안한다. 개인적인 공부를 위해 글 작성을 시작해보자. 사실 쓰면 기억에 잘 남는편이라..에 내용을 그대로 옮겨 오는식으로 포스팅할 예정

아래 내용은 총 3개의 챕터로 나누어진다.

  1. 객체지향 프로그래밍의 주요 기능
  2. 객체지향 디자인의 기본 원칙
  3. 디자인 패턴의 분류

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

캡슐화(Encapsulation)

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

다형성(Polymorphism)

  • 다형성에는 두 가지 의미가 있다.
    • 객체는 함수 인자에 따라 다른 기능을 한다.
    • 동일한 인터페이스를 여러 형식의 객체들이 공유한다.
  • 파이썬은 다형성을 지원하는 언어다. 예를 들어 + 연산자는 두 정수를 더하거나 문자열을 합칠 때 모두 사용할 수 있다.
"""
정수 인덱스를 사용해 문자열과 튜플, 리스트의 원소에 모두 접근 할 수 있다. 
파이썬의 다형성을 보여주는 예제다.
"""
a = "John"
b = (1,2,3)
c = [3,4,6,8,9]
print(a[1], b[0], c[2])

상속(Inheritance)

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

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

b = B()
b.a1()

추상화(Abstraction)

  • 클라이언트가 클래스 객체를 생성하고 인터페이스에 정의된 함수를 호출할 수 있는 인터페이스를 제공한다.
  • 클라이언트는 클래스의 복잡한 내부 구현에 대한 이해없이 간편하게 인터페이스를 사용할 수 있다.
# 다음 예제는 Adder 클래스의 내부 구현을 add() 함수로 추상화한다.
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)

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

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

objectB = B()
objectB.b()

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

다음은 디자인 패턴을 공부할 때 반드시 기억해야 하는 객체지향 디자인의 기본 원칙이다.
SRP(단일 책임 원칙), OCP(개방-폐쇄 원칙), LSP(리스코프 치환 원칙), ISP(인터페이스 분리 원칙), DIP(의존 역전 원칙),

단일 책임 원칙(The Single Responsibility Principle)

단일 책임 원칙이란 클래스는 하나의 책임만을 가져야 한다는 원칙이다.

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

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

단일 책임 원칙의 장점은 다음과 같다.

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

개방-폐쇄 원칙(The Open/Close Principle)

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

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

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

이 원칙의 장점은 다음과 같다.

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

리스코프 치환 원칙(The Liskov Substitution Principle)

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

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

인터페이스 분리 원칙(The Interface Segregation Principle)

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

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

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

인터페이스 분리 원칙의 장점은 다음과 같다.

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

의존 역전 원칙(The Dependency Inversion Principle)

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

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

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

제어 반전 원칙의 장점은 다음과 같다.

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

3. 디자인 패턴의 분류

GoF 책에서 23개의 디자인 패턴을 다음 3개의 범주로 분류한다.

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

디자인 패턴은 객체가 생성되는 과정과 클래스와 객체의 구조 그리고 각 객체 간의 상호작용에 따라 분류된다. 각 분류에 대해 더 자세하게 알아보자.

생성 패턴

생성 패턴의 특징은 다음과 같다.

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

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

구조 패턴

구조 패턴의 특징은 다음과 같다.

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

어댑터 패턴은 구조 패턴의 한 종류다.

행위 패턴

행위 패턴의 특징은 다음과 같다.

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

옵저버 패턴은 행위 패턴의 한 종류다.

마무리

어렵다 글로만보면…..
특히 solid원칙 머리에 잘 안들어간다.
다음편에 코드로 직접 이해하는 편을 만들어야겠다.

참고

Comments