파이썬과 객체지향 프로그래밍 - . 디자인 패턴 (초울트라심화)

13. 디자인 패턴 (초울트라심화)

  • 객체 지향 설계 패턴
    • 객체 지향 프로그래밍 설계 경험을 통해 추천되는 설계 패턴을 기술한 것
    • https://en.wikipedia.org/wiki/Design_Patterns 디자인 패턴 관련한 최초의 서적
    • 실제 여러 프로그램을 설계해보면서 문제를 발견하고, 디자인 패턴을 적용해보아야 이해할 수 있음
      • 현 단계에서는 주요하고 그나마 간단한 패턴을 통해 디자인 패턴이 무엇인지를 이해해보도록 함

디자인 패턴도 참고만 하세요!

  • 현업에서는 굉장히 디자인 패턴을 좋아하는 개발 조직과, 아예 디자인 패턴을 쓰지 말라고 하는 개발 조직도 있습니다.
    • 디자인 패턴을 쓰면, 코드를 이해하기 힘들어집니다.
    • 개인 의견: 코드는 단순하게 읽기 쉽게 쓰는 것이 더 유지보수도 쉽고, 개발이 쉬울 때가 많습니다.
    • 다만 디자인 패턴을 선호하는 조직에서는 반드시 철저하게 써야 합니다.

13.1. Singleton 패턴

  • 클래스에 대한 객체가 단 하나만 생성되게 하는 방법
    • 예:
      • 계산기 클래스를 만들고, 여러 파일에 있는 코드에서 필요할 때마다 객체로 불러서 계산을 하려 함
        • 매번 계산할 때마다, 객체를 새로 만들 필요는 없음
        • 객체 생성시마다 메모리를 차지하므로, 불필요한 객체 생성 시간, 메모리 사용률을 절약할 수 있음
간단히 개념만!
어떤 객체는 하나만 만들면 되는 객체가 있음 - 예: 데이터베이스를 연결하고, 데이터베이스를 제어하는 인터페이스 객체
보통 프로그램은 여러 파일로 구현합니다.
각 파일에서 해당 객체를 그대로 사용하려면, 부득이 동일 클래스로 객체를 만들어야 합니다.
각각 파일마다 객체를 별도로 생성하지 않고, 동일한 객체를 사용하고 싶을 때, 싱글톤이라는 기법으로 프로그램에서 한번 만들어진 동일 객체를 사용할 수있음

싱글톤을 만드는 여러 방법이 있지만, 이 중에서 가장 많이 사용되는 코드를 기술

In [47]:
# 1. 싱글톤 클래스 만들기
class Singleton(type):    # Type을 상속받음
    __instances = {}      # 클래스의 인스턴스를 저장할 속성
    def __call__(cls, *args, **kwargs):    # 클래스로 인스턴스를 만들 때 호출되는 메서드
        if cls not in cls.__instances:     # 클래스로 인스턴스를 생성하지 않았는지 확인
            cls.__instances[cls] = super().__call__(*args, **kwargs) # 생성하지 않았으면 인스턴스를 생성하여 해당 클래스 사전에 저장
        return cls.__instances[cls]        # 클래스로 인스턴스를 생성했으면 인스턴스 반환
In [48]:
# 2. 싱글톤 클래스 만들기
class PrintObject(metaclass=Singleton):    # 메타클래스로 Singleton을 지정
    def __init__(self):
        print("This is called by super().__call__")


In [50]:
# 3. 싱글톤 객체 만들기 (object1, object2 모두 동일)
object1 = PrintObject()     
object2 = PrintObject()     
print(object1)
print(object2)
<__main__.PrintObject object at 0x111dfeb00>
<__main__.PrintObject object at 0x111dfeb00>

13.2. Observer 패턴

  • 객체의 상태 변경시, 관련된 다른 객체들에게 상태 변경을 통보하는 디자인 패턴
  • 관찰자(관련된 다른 객체)들에게 비관찰자(해당 객체)의 특정 이벤트 발생을 자동으로 모두 전달하는 패턴

SMS / Email / Push 로 각각 알려야 하는 경우 구현해보기

In [74]:
class Observer: 
    def __init__(self):
        self.observers = list()
        self.msg = str()

    def notify(self, event_data):
        for observer in self.observers:
            observer.notify(event_data)

    def register(self, observer):
        self.observers.append(observer)

    def unregister(self, observer):
        self.observers.remove(observer)
In [75]:
class SMSNotifier:    
    def notify(self, event_data):
        print(event_data, 'received..')
        print('send sms')
        
class EmailNotifier:
    def notify(self, event_data):
        print(event_data, 'received..')
        print('send email')
        
class PushNotifier:
    def notify(self, event_data):
        print(event_data, 'received..')
        print('send push notification')
In [76]:
notifier = Observer()

sms_notifier = SMSNotifier()
email_notifier = EmailNotifier()
push_notifier = PushNotifier()

notifier.register(sms_notifier)
notifier.register(email_notifier)
notifier.register(push_notifier)

notifier.notify('user activation event')
user activation event received..
send sms
user activation event received..
send email
user activation event received..
send push notification

13.3. Builder pattern (이 부분은 다른 언어는 어렵지만, 파이썬은 간단히 언어 자체에서 해결됩니다)

  1. 생성자에 들어갈 매개 변수가 복잡하여 가독성이 떨어지고, 어떤 변수가 어떤 값인지 알기 어려우며,
    • 예: Student('aaron', 20, 180, 180, 'cs', ['data structures', 'artificial intelligence'])
  2. 전체 변수 중 일부 값만 설정하는 경우
    • 예: Student('aaron', --, 180, 180, ???, ???)

파이썬은 디폴트값 설정 가능: 2. 전체 변수 중 일부 값만 설정하는 경우 해결



In [89]:
class Student(object):
    def __init__(self, name, age=20, height=180, weight=60, major='cs'):
        self.name = name
        self.age = age
        self.height = height
        self.weight = weight
        self.major = major
In [95]:
student1 = Student('Dave')
print(student1.name)
print(student1.age)
print(student1.height)
print(student1.weight)
print(student1.major)
Dave
20
180
60
cs

파이썬은 함수/클래스 생성시 인자를 가독성 있게 작성 가능: 1. 생성자에 들어갈 매개 변수가 복잡하여 가독성이 떨어지지 않도록 가능함

In [98]:
student1 = Student(major='ds', name='David')
print(student1.name)
print(student1.age)
print(student1.height)
print(student1.weight)
print(student1.major)
David
20
180
60
ds

13.4. Factory pattern

  • 객체를 생성하는 팩토리 클래스를 정의하고 어떤 객체를 만들지는 팩토리 객체에서 결정하여 객체를 만들도록 하는 패턴

모바일 플랫폼별 스마트폰 객체 만들기 구현해보기

In [11]:
# 상세 클래스 만들기
class AndroidSmartphone:
    def send(self, message):
        print ("send a message via Android platform")

class WindowsSmartphone:
    def send(self, message):
        print ("send a message via Window Mobile platform")
        
class iOSSmartphone:
    def send(self, message):
        print ("send a message via iOS platform")
In [15]:
# 팩토리 클래스 만들기
class SmartphoneFactory(object):
    def __init__(self):
        pass
    
    def create_smartphone(self, devicetype):
        if devicetype == 'android':
            smartphone = AndroidSmartphone()   # <-- 실제 객체를 팩토리 클래스 안에서 만든다.
        elif devicetype == 'window':
            smartphone = WindowsSmartphone()   # <-- 실제 객체를 팩토리 클래스 안에서 만든다.
        elif devicetype == 'ios':
            smartphone = iOSSmartphone()       # <-- 실제 객체를 팩토리 클래스 안에서 만든다.
        return smartphone


In [16]:
# 팩토리 클래스로 실제 상세 객체 만들기
smartphone_factory = SmartphoneFactory()
message_sender1 = smartphone_factory.create_smartphone('android')
message_sender1.send('hi')

message_sender2 = smartphone_factory.create_smartphone('window')
message_sender2.send('hello')

message_sender3 = smartphone_factory.create_smartphone('ios')
message_sender3.send('are you there?')
send a message via Android platform
send a message via Window Mobile platform
send a message via iOS platform
협업 과제(남에게 설명하면, 자신이 배운답니다.!)
클래스 설계해보기
다음 블로그를 참고해서 각 도형을 그려주는 클래스 만들고, x, y 좌표에 따라 그림을 그려주세요
http://andamiro25.tistory.com/17
* 개발자들은 프로그래밍/IT기술 정보를 웹페이지를 통해 확인하고, 적용합니다. 이 방식에도 익숙해지기 위해 관련 사이트 자료를 드리고, 적용해봅니다.

* 쥬피터 노트북에서는 코드 실행시, 에러가 날 수 있으므로, 다음 온라인 환경에서 실행합니다.
- https://trinket.io/
* 클래스 이름/인자 및 메서드
Figure - 클래스 이름 (별도 인자는 받지 않음)

draw_shape(shape, color, x, y)
shape 파라미터 - 도형 모양 의미 (string)
삼각형 그리기 - "triangle"
사각형 그리기 - "quadrangle"
오각형 그리기 - "pentagon"
육각형 그리기 - "hexagon"
원 그리기 - "circle"

Color 파라미터 - 도형 색상 의미 (string)
빨강 - "red"
파랑 - "blue"
검정 - "black"

x, y 파라미터 - 좌표를 의미 (숫자)

clear_shape()
도형 삭제 (Turtle 라이브러리의 reset() 메서드 활용)

move_shape()
기존 도형을 삭제된 듯 보이게하고, 새로운 위치에 동일한 도형을 그려주면 됨
* 생각할 점: shape 파라미터 인자를 삼각형은 1, 사각형은 2 와 같이 인자값을 주면 동일하게 구동되도록 바꿔보세요

협업 과제(남에게 설명하면, 자신이 배운답니다.!)
클래스 설계해보기
네이버 실시간 검색어를 엑셀 파일로 저장하는 프로그램을 객체지향으로 만들기

네이버 실시간 검색어 추출 코드 - 코드를 다 이해할 수 없습니다. 출력에만 집중하세요

In [9]:
import requests
from bs4 import BeautifulSoup

res = requests.get('https://www.naver.com/')

soup = BeautifulSoup(res.content, 'html.parser')

# a 태그이면서 href 속성 값이 특정한 값을 갖는 경우 탐색
link_title = soup.select("#PM_ID_ct > div.header > div.section_navbar > div.area_hotkeyword.PM_CL_realtimeKeyword_base > div.ah_roll.PM_CL_realtimeKeyword_rolling_base > div > ul > li")
for num in range(len(link_title)):
    # link_title 은 리스트 타입으로 개별 태그셋을 저장합니다.
    # print(type(link_title))
    # 각 태그셋은 string이 아니라 BeautifulSoup의 element.Tag 라는 객체입니다.
    # print(type(link_title[0]))
    # 그래서 각 태그셋에 다시 find(), find_all() 과 같은 BeautifulSoup 메서드를 사용할 수 있음을 확인할 수 있습니다.
    link_title_each = link_title[num].find_all('span')
    print(link_title_each[1].get_text())
이동욱
최사랑
수지
정재성
허경영
류시원
신웅
메시아
김민희
홍상수
도시어부
김영미 공주시의원
노선영
단역배우 자매
김정은
홍수철
트럼프
임종석
갤럭시s9
산들

네이버 실시간 검색어 추출 코드 - 코드를 다 이해할 수 없습니다. 어떤 변수에 데이터를 넣어주면 되는지에만 집중하세요

In [ ]:
import xlsxwriter

workbook = xlsxwriter.Workbook('TEST.xlsx')
worksheet = workbook.add_worksheet()
worksheet.set_column(0, 0, 80)
worksheet.write(1, 1, '타이틀')

for num in range(len(title_list)):
    worksheet.write(num + 2, 1, title_list[num])

workbook.close()