5가지 클래스 설계의 원칙 (S.O.L.I.D)
이해하기 쉽고, 장황하지 않은 자료를 기반으로 강의를 진행합니다.
잔재미코딩 소식 공유
좀더 제약없이, IT 컨텐츠를 공유하고자, 자체 온라인 강의 사이트와 유투브 채널을
오픈하였습니다
응원해주시면, 곧 좋은 컨텐츠를 만들어서 공유하겠습니다
응원해주시면, 곧 좋은 컨텐츠를 만들어서 공유하겠습니다
● 잔재미코딩 유투브 오픈
[구독해보기]
12. 5가지 클래스 설계의 원칙 (S.O.L.I.D) - 참고사항으로 이해합니다. (초심화)¶
- S - SRP(Single responsibility principle) 단일 책임 원칙
- O - OCP(Open Closed Principle) 개방 - 폐쇄 원칙
- L - LSP(Liskov Substitusion Principle) 리스코프 치환 법칙
- I - ISP(Interface Segregation Principle) 인터페이스 분리 원칙
- D - DIP(Dependency Inversion Principle) 의존성 역전 법칙
출처) https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
객체지향과 디자인 패턴 관련 책 : http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=28301535 파이썬은 아니나, Object Oriendted design과 Design pattern에 관심있으신분은 해당 서적을 참고하시면 좋습니다.
클래스 설계 및 팁¶
- 직접 많이 작성하며 이해하는 부분이지만, 처음에는 들어봤다가 중요합니다.
- 원칙을 이해하면서, 각각의 클래스 설계 예를 들여다보고, 이해를 한다는 느낌으로 접근합니다.
실제 현업에서의 코드 작성시 주의하는 부분¶
중복코드 제거는 매우 좋음¶
- 단 두번이라도 사용된다면, 코드 수정시 각 코드를 모두 수정해야 하기 때문
- 여러 동일한 코드를 수정하다보면 시간도 많이 소요되고, 복잡도 높아지고, 버그 가능성이 많음
- 중복 코드는 함수로 만들던지, 클래스 메서드/인터페이스로 만들던지 반드시 하나의 코드로 만드는 것이 필요함
최적화는 제일 나중에 할것: 굳이 잘돌아가면, bottleneck 이 존재할때만¶
- 괜히 최적화한답시고, 중간마다 계속 바꾸면, 시간만 많이 들 수 있음
- 완벽하게 상세한 레벨까지 최적화된 설계를 처음부터 해도 시간이 많이 들고, 결국 실제 구현시 바뀌는 경우가 많음
12.1. SRP(Single responsibility principle) 단일 책임 원칙¶
- 클래스는 단 한개의 책임을 가져야 함 (클래스를 수정할 이유가 오직 하나이어야 함)
- 예: 계산기 기능 구현시, 계산을 하는 책임과 GUI를 나타낸다는 책임을 서로 분리하여, 각각 클래스로 설계해야 함
- 실제로는 애매한 부분이 많이 존재함, 가급적 설계시 이런 부분을 염두에 두자가 합리적일 수 있음
In [ ]:
# 나쁜 예
# 학생성적과 수강하는 코스를 한개의 class에서 다루는 예
# 한 클래스에서 두개의 책임을 갖기 때문에, 수정이 용이하지 않다.
class StudentScoreAndCourseManager(object):
def __init__(self):
scores = {}
courses = {}
def get_score(self, student_name, course):
pass
def get_courses(self, student_name):
pass
# 변경 예
# 각각의 책임을 한개로 줄여서, 각각 수정이 다른 것에 영향을 미치지 않도록 함
class ScoreManager(object):
def __init__(self):
scores = {}
def get_score(self, student_name, course):
pass
class CourseManager(object):
def __init__(self):
courses = {}
def get_courses(self, student_name):
pass
초간단 연습1
1. SRP 원칙을 고려하여 다음 코드를 클래스로 만들어봅니다.
- https://www.seeko.co.kr/zboard4/zboard.php?id=mainnews 웹페이지에서 타이틀과, 댓글 수를 가져오기
- 엑셀 파일로 만들기
생각해보기
1. 데이터베이스가 있다고 생각해봅니다. 이 중에서도 MySQL 이라는 데이터베이스에 넣는 로직을 구현한다면?
- pymysql 과 MySQL 프로그램을 자신의 PC에 설치하고, 관련 프로그램 파이썬으로 구현 필요
2. 이렇게 만든 클래스를 다른 PC에서 실행하면서 엑셀 파일만 만들고 싶다
3. 다른 PC에서 실행시키려는데, xlsxwriter가 설치 안되어있다. 나는 해당 PC의 데이터베이스만 쓰고 싶다
4. https://www.seeko.co.kr/zboard4/zboard.php?id=mainnews 에서 특정 키워드가 있는 데이터만 추출하고 싶다.
5. 엇, 나는 csv 파일로 만들고 싶다.
1. SRP 원칙을 고려하여 다음 코드를 클래스로 만들어봅니다.
- https://www.seeko.co.kr/zboard4/zboard.php?id=mainnews 웹페이지에서 타이틀과, 댓글 수를 가져오기
- 엑셀 파일로 만들기
생각해보기
1. 데이터베이스가 있다고 생각해봅니다. 이 중에서도 MySQL 이라는 데이터베이스에 넣는 로직을 구현한다면?
- pymysql 과 MySQL 프로그램을 자신의 PC에 설치하고, 관련 프로그램 파이썬으로 구현 필요
2. 이렇게 만든 클래스를 다른 PC에서 실행하면서 엑셀 파일만 만들고 싶다
3. 다른 PC에서 실행시키려는데, xlsxwriter가 설치 안되어있다. 나는 해당 PC의 데이터베이스만 쓰고 싶다
4. https://www.seeko.co.kr/zboard4/zboard.php?id=mainnews 에서 특정 키워드가 있는 데이터만 추출하고 싶다.
5. 엇, 나는 csv 파일로 만들고 싶다.
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
체계적으로 전문가 레벨까지 익힐 수 있도록 온라인 강의 로드맵을 제공합니다
웹크롤링 코드 예제 - 코드를 이해할 수 없습니다. 어떤 변수에 결과물이 담기는지만 집중하세요¶
In [1]:
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
title_list = list()
hit_count_list = list()
for pageNum in range(1):
html = urlopen("https://www.seeko.co.kr/zboard4/zboard.php?id=mainnews&page=" + str(pageNum + 1) + "&select_arrange=headnum&desc=asc&category=&sn=off&ss=on&sc=off&keyword=&sn1=&divpage=10")
html_object = BeautifulSoup(html, "html.parser")
title_data = html_object.findAll("td", {"class": "article_subject"})
title_hit = html_object.findAll("td", {"class": "article_count"})
for title_wrap in title_data:
print(title_wrap.get_text())
title_list.append(title_wrap.get_text())
for title_hit in title_hit:
hit_count_list.append(title_hit.get_text())
데이터 저장 예제 (엑셀 파일로 만들기) - 코드를 다 이해할 수 없습니다. 어떤 변수에 있는 데이터가 엑셀 파일에 씌여지는지만 집중하세요!¶
In [25]:
import xlsxwriter
workbook = xlsxwriter.Workbook('ITArticleReport.xlsx')
worksheet = workbook.add_worksheet()
worksheet.set_column(0, 0, 5)
worksheet.set_column(1, 1, 80)
cell_format = workbook.add_format({'bold': True, 'align': 'center', 'fg_color': '#01579B', 'color': 'white', 'border': 1})
worksheet.write(1, 1, '타이틀', cell_format)
worksheet.write(1, 2, '클릭수', cell_format)
cell_format_gray = workbook.add_format({'fg_color': '#ECEFF1', 'border': 1})
cell_format_white = workbook.add_format({'fg_color': 'white', 'border': 1})
for num in range(len(title_list)):
worksheet.write(num + 2, 1, title_list[num], cell_format_gray)
worksheet.write(num + 2, 2, hit_count_list[num], cell_format_gray)
workbook.close()
12.2. OCP(Open Closed Principle) 개방 - 폐쇄 원칙¶
- 확장에는 열려있어야 하고, 변경에는 닫혀있어야 함
- 예: 캐릭터 클래스를 만들 때, 캐릭터마다 행동이 다르다면, 행동 구현은 캐릭터 클래스의 자식 클래스에서 재정의(Method Override)한다.
- 이 경우, 캐릭터 클래스는 수정할 필요 없고(변경에 닫혀 있음)
- 자식 클래스에서 재정의하면 됨(확장에 대해 개방됨)
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
가장 빠르게 풀스택 개발자가 될 수 있도록, 최적화된 로드맵을 제공합니다
In [ ]:
# 나쁜 예
class Rectangle(object):
def __init__(self, width, height):
self.width = width
self.height = height
class Circle:
def __init__(self, radius):
self.radius = radius
class AreaCalculator(object):
def __init__(self, shapes):
self.shapes = shapes
def total_area(self):
total = 0
for shape in self.shapes:
total += shape.width * shape.height
return total
shapes = [Rectangle(2, 3), Rectangle(1, 6)]
calculator = AreaCalculator(shapes)
print("The total area is: ", calculator.total_area())
In [8]:
# 좋은 예
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
'''다른 도형에 대해 확장하기 위해서,
AreaCalculator는 수정이 필요 없음 (변경에 닫혀 있음)
단지, Shape을 상속받은 다른 class를 정의하기만 하면 됨 (확장에 대해 개방됨)
'''
class AreaCalculator(object):
def __init__(self, shapes):
self.shapes = shapes
def total_area(self):
total = 0
for shape in self.shapes:
total += shape.area()
return total
shapes = [Rectangle(1, 6), Rectangle(2, 3), Circle(5), Circle(7)]
calculator = AreaCalculator(shapes)
print("The total area is: ", calculator.total_area())
초간단 연습2
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 만드려면 어떻게 해야할지, 클래스 설계를 위 OCP 원칙을 생각하며 잡아봅니다.
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 만드려면 어떻게 해야할지, 클래스 설계를 위 OCP 원칙을 생각하며 잡아봅니다.
게임 캐릭터는 다음과 같이 3명이 존재하고, 각각의 메서드는 다음과 같음 Warrior - 공격하면 칼로 찌른다를 출력 Elf - 공격하면 마법을 쓴다를 출력 Wizard - 공격하면 마법을 쓴다를 출력
12.3. LSP(Liskov Substitusion Principle) 리스코프 치환 법칙¶
자식 클래스는 언제나 자신의 부모클래스와 교체할 수 있다는 원칙¶
갤럭시폰 is a kind of 스마트폰
- 스마트폰은 다른 사람과 전화와 메시지가 가능하다.
- 스마트폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
- 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
위 설명을 갤럭시 폰으로 대체하면 아래와 같다.
- 갤럭시 폰은 다른 사람과 전화와 메시지가 가능하다.
- 갤럭시 폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
- 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
초간단 연습3
다음 캐릭터의 메서드를 모두 담은 클래스를 만든다면?
어떻게 하면 OCP 원칙을 고려할 수 있을까요?
다음 캐릭터의 메서드를 모두 담은 클래스를 만든다면?
어떻게 하면 OCP 원칙을 고려할 수 있을까요?
Warrior - attack: 상대방 객체를 입력받아서, '칼로 찌르다' 출력하고, 상대방의 receive 메서드를 호출해서, striking_power만큼 상대방의 health_point를 낮춰준다. - receive: 상대방의 striking_point를 입력으로 받아서, 자신의 health_point를 그만큼 낮추기, health_point가 0 이하이면 '죽었음' 출력 - use_shield: 1번 공격을 막는다. Elf - attack: 상대방 객체를 입력받아서, '마법을 쓰다' 출력하고, 상대방의 receive 메서드를 호출해서, striking_power만큼 상대방의 health_point를 낮춰준다. - receive: 상대방의 striking_point를 입력으로 받아서, 자신의 health_point를 그만큼 낮추기, health_point가 0 이하이면 '죽었음' 출력 - wear_manteau: 1번 공격을 막는다. Wizard - attack: 상대방 객체를 입력받아서, '마법을 쓰다' 출력하고, 상대방의 receive 메서드를 호출해서, striking_power만큼 상대방의 health_point를 낮춰준다. - receive: 상대방의 striking_point를 입력으로 받아서, 자신의 health_point를 그만큼 낮추기, health_point가 0 이하이면 '죽었음' 출력 - use_wizard: 자신의 health_point를 3씩 올려준다.
In [7]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
def __init__(self, name='yourname', health_point=100, striking_power=3, defensive_power=3):
self.name = name
self.health_point = health_point
self.striking_power = striking_power
self.defensive_power = defensive_power
def get_info(self):
print (self.name, self.health_point, self.striking_power, self.defensive_power)
@abstractmethod
def attack(self, second):
pass
@abstractmethod
def receive(self):
pass
@abstractmethod
def special(self):
pass
12.4. ISP(Interface Segregation Principle) 인터페이스 분리 원칙¶
- 클래스에서 사용하지 않는(상관없는) 메서드는 분리해야 한다.
In [8]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
@abstractmethod
def move(self):
pass
@abstractmethod
def eat(self):
pass
여기서 잠깐: metaclass 란?¶
- 클래스를 만들기 위해 파이썬에서는 기본 metaclass가 사용됨
- 즉, 클래스를 만들기 위해서 메타클래스 라는 것이 필요했던 것임
- class 생성시, () 아무 것도 넣지 않으면, 기본 파이썬에서 클래스를 만들기 위한 메타클래스가 쓰인다고 보면 됨
- 추상 클래스 만들시에는 기본 메타클래스로는 생성이 어려우니, 다음과 같이 작성
- class Character(metaclass=ABCMeta)
- 싱글톤을 위해 기본 메타클래스를 바꾸는 것임 (싱글톤은 다음에 나오는 디자인 패턴에서 설명)
- class PrintObject(metaclass=Singleton)
In [20]:
class MyClass:
pass
In [21]:
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
In [9]:
# 나쁜 예
# 추상 클래스 상속하기
class Elf(Character):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
def eat(self):
print ("no eat") # <--- 요정은 밥을 안먹지 않을까요? 그래도 선언해줘야 함(상관없는 기능)
class Human(Character):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self):
print ("eat foods")
첫 번째 예: 이렇게 작성하는 것이 우선 위 코드보다는 더 좋음1¶
In [13]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
@abstractmethod
def move(self):
pass
In [14]:
# 추상 클래스 상속하기
class Elf(Character):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
class Human(Character):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self): # <--- 메서드 확장
print ("eat foods")
In [15]:
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
두 번째 예: 이렇게 작성하는 것도 처음 코드보다는 더 좋음2¶
In [4]:
from abc import *
class AttackingWay(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
class MovingWay(metaclass=ABCMeta):
@abstractmethod
def move(self):
pass
class EatingWay(metaclass=ABCMeta):
@abstractmethod
def eat(self):
pass
class AbstractHumanCharacter(AttackingWay, MovingWay, EatingWay):
pass
In [5]:
# 추상 클래스 상속하기
class Elf(AttackingWay, MovingWay):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
class Human(AttackingWay, MovingWay, EatingWay):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self):
print ("eat foods")
In [6]:
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
한발짝 더 나가보기!(심화 문제)
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
게임 캐릭터는 다음과 같이 3명이 존재하고, 각각의 메서드는 다음과 같음 Warrior - 공격하면 칼로 찌른다를 출력 Elf - 공격하면 마법을 쓴다를 출력 Wizard - 공격하면 마법을 쓴다를 출력
In [10]:
from abc import *
class UsingKnife(metaclass=ABCMeta):
@abstractmethod
def use_knife(self):
pass
class UsingWizard(metaclass=ABCMeta):
@abstractmethod
def use_wizard(self):
pass
class Warrior(UsingKnife):
def use_knife(self):
print ('칼로 찌른다')
class Elf(UsingWizard):
def use_wizard(self):
print ('마법을 쓰다')
class Wizard(UsingWizard):
def use_wizard(self):
print ('마법을 쓰다')
warrior1 = Warrior()
elf1 = Elf()
wizard1 = Wizard()
warrior1.use_knife()
12.5. DIP(Dependency Inversion Principle) 의존성 역전 법칙¶
- 부모 클래스는 자식 클래스의 구현에 의존해서는 안됨
- 자식 클래스 코드 변경 또는 자식 클래스 변경시, 부모 클래스 코드를 변경해야 하는 상황을 만들면 안됨
- 자식 클래스에서 부모 클래스 수준에서 정의한 추상 타입에 의존할 필요가 있음
In [11]:
# 실습 코드
class BubbleSort:
def bubble_sort(self):
# sorting algorithms
pass
In [14]:
# 나쁜 예
class SortManager:
def __init__(self):
self.sort_method = BubbleSort() # <--- SortManager 는 BubbleSort에 의존적
def begin_sort(self):
self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적
이렇게 되면 어떤 문제가 생길까요? BubbleSort의 메서드 이름을 바꿔봤습니다.¶
In [13]:
# BubbleSort의 bubble_sort 메서드 변경
class BubbleSort:
def sort(self):
print('bubble sort')
pass
In [15]:
sortmanager = SortManager()
sortmanager.begin_sort()
그러면, 위 예에서는 상위 클래스인 SortManager도 코드를 바꿔줘야 한다.¶
- 하부 클래스 코드를 수정하면 상위 클래스 코드도 바꿔줘야 하므로, 어색한 것은 분명함
- 이 부분을 의존성 역전 법칙에서 상위 클래스가 하부 클래스에 의존되는 역전현상을 막아야 한다라고 어렵게 써놓은 것임
In [8]:
sorting1 = SortManager()
sorting1.begin_sort()
의존성을 주입하고, 상위 클래스에서 하위 클래스 활용시 하위 클래스에 따라 변경되지 않도록, 일반화(추상화)된 설계를 하면 됨¶
In [16]:
# 좋은 예
class SortManager:
def __init__(self, sort_method): # <--- 의존성을 주입시킨다고 이야기함
self.set_sort_method(sort_method)
def set_sort_method(self, sort_method):
self.sort_method = sort_method
def begin_sort(self):
self.sort_method.sort() # <--- 하부 클래스가 바뀌더라도, 동일한 코드 활용 가능토록 인터페이스화
In [17]:
# 실습 코드
class BubbleSort:
def sort(self):
print('bubble sort')
pass
class QuickSort:
def sort(self):
print('quick sort')
pass
In [11]:
bubble_sort1 = BubbleSort()
quick_sort1 = QuickSort()
sorting1 = SortManager(bubble_sort1)
sorting1.begin_sort()
sorting2 = SortManager(quick_sort1)
sorting2.begin_sort()
초간단 연습2
selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기
selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기
In [19]:
# 좋은 예
class SortManager:
def __init__(self, sort_method): # <--- 의존성을 주입시킨다고 이야기함
self.set_sort_method(sort_method)
def set_sort_method(self, sort_method):
self.sort_method = sort_method
def begin_sort(self):
self.sort_method.sort() # <--- 하부 클래스가 바뀌더라도, 동일한 코드 활용 가능토록 인터페이스화
class SelectionSort:
def sort(self):
print('selection sort')
pass
selection_sort = SelectionSort()
sorting3 = SortManager(selection_sort)
sorting3.begin_sort()
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
체계적으로 전문가 레벨까지 익힐 수 있도록 온라인 강의 로드맵을 제공합니다
In [7]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
def __init__(self, name='yourname', health_point=100, striking_power=3, defensive_power=3):
self.name = name
self.health_point = health_point
self.striking_power = striking_power
self.defensive_power = defensive_power
def get_info(self):
print (self.name, self.health_point, self.striking_power, self.defensive_power)
@abstractmethod
def attack(self, second):
pass
@abstractmethod
def receive(self):
pass
@abstractmethod
def special(self):
pass
12.4. ISP(Interface Segregation Principle) 인터페이스 분리 원칙¶
- 클래스에서 사용하지 않는(상관없는) 메서드는 분리해야 한다.
In [8]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
@abstractmethod
def move(self):
pass
@abstractmethod
def eat(self):
pass
여기서 잠깐: metaclass 란?¶
- 클래스를 만들기 위해 파이썬에서는 기본 metaclass가 사용됨
- 즉, 클래스를 만들기 위해서 메타클래스 라는 것이 필요했던 것임
- class 생성시, () 아무 것도 넣지 않으면, 기본 파이썬에서 클래스를 만들기 위한 메타클래스가 쓰인다고 보면 됨
- 추상 클래스 만들시에는 기본 메타클래스로는 생성이 어려우니, 다음과 같이 작성
- class Character(metaclass=ABCMeta)
- 싱글톤을 위해 기본 메타클래스를 바꾸는 것임 (싱글톤은 다음에 나오는 디자인 패턴에서 설명)
- class PrintObject(metaclass=Singleton)
In [20]:
class MyClass:
pass
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
가장 빠르게 풀스택 개발자가 될 수 있도록, 최적화된 로드맵을 제공합니다
In [21]:
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
In [9]:
# 나쁜 예
# 추상 클래스 상속하기
class Elf(Character):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
def eat(self):
print ("no eat") # <--- 요정은 밥을 안먹지 않을까요? 그래도 선언해줘야 함(상관없는 기능)
class Human(Character):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self):
print ("eat foods")
첫 번째 예: 이렇게 작성하는 것이 우선 위 코드보다는 더 좋음1¶
In [13]:
# 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
@abstractmethod
def move(self):
pass
In [14]:
# 추상 클래스 상속하기
class Elf(Character):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
class Human(Character):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self): # <--- 메서드 확장
print ("eat foods")
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
체계적으로 전문가 레벨까지 익힐 수 있도록 온라인 강의 로드맵을 제공합니다
In [15]:
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
두 번째 예: 이렇게 작성하는 것도 처음 코드보다는 더 좋음2¶
In [4]:
from abc import *
class AttackingWay(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
class MovingWay(metaclass=ABCMeta):
@abstractmethod
def move(self):
pass
class EatingWay(metaclass=ABCMeta):
@abstractmethod
def eat(self):
pass
class AbstractHumanCharacter(AttackingWay, MovingWay, EatingWay):
pass
In [5]:
# 추상 클래스 상속하기
class Elf(AttackingWay, MovingWay):
def attack(self):
print ("practice the black art")
def move(self):
print ("fly")
class Human(AttackingWay, MovingWay, EatingWay):
def attack(self):
print ("plunge a knife")
def move(self):
print ("run")
def eat(self):
print ("eat foods")
In [6]:
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
가장 빠르게 풀스택 개발자가 될 수 있도록, 최적화된 로드맵을 제공합니다
한발짝 더 나가보기!(심화 문제)
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
게임 캐릭터는 다음과 같이 3명이 존재하고, 각각의 메서드는 다음과 같음 Warrior - 공격하면 칼로 찌른다를 출력 Elf - 공격하면 마법을 쓴다를 출력 Wizard - 공격하면 마법을 쓴다를 출력
In [10]:
from abc import *
class UsingKnife(metaclass=ABCMeta):
@abstractmethod
def use_knife(self):
pass
class UsingWizard(metaclass=ABCMeta):
@abstractmethod
def use_wizard(self):
pass
class Warrior(UsingKnife):
def use_knife(self):
print ('칼로 찌른다')
class Elf(UsingWizard):
def use_wizard(self):
print ('마법을 쓰다')
class Wizard(UsingWizard):
def use_wizard(self):
print ('마법을 쓰다')
warrior1 = Warrior()
elf1 = Elf()
wizard1 = Wizard()
warrior1.use_knife()
12.5. DIP(Dependency Inversion Principle) 의존성 역전 법칙¶
- 부모 클래스는 자식 클래스의 구현에 의존해서는 안됨
- 자식 클래스 코드 변경 또는 자식 클래스 변경시, 부모 클래스 코드를 변경해야 하는 상황을 만들면 안됨
- 자식 클래스에서 부모 클래스 수준에서 정의한 추상 타입에 의존할 필요가 있음
In [11]:
# 실습 코드
class BubbleSort:
def bubble_sort(self):
# sorting algorithms
pass
In [14]:
# 나쁜 예
class SortManager:
def __init__(self):
self.sort_method = BubbleSort() # <--- SortManager 는 BubbleSort에 의존적
def begin_sort(self):
self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
체계적으로 전문가 레벨까지 익힐 수 있도록 온라인 강의 로드맵을 제공합니다
이렇게 되면 어떤 문제가 생길까요? BubbleSort의 메서드 이름을 바꿔봤습니다.¶
In [13]:
# BubbleSort의 bubble_sort 메서드 변경
class BubbleSort:
def sort(self):
print('bubble sort')
pass
In [15]:
sortmanager = SortManager()
sortmanager.begin_sort()
그러면, 위 예에서는 상위 클래스인 SortManager도 코드를 바꿔줘야 한다.¶
- 하부 클래스 코드를 수정하면 상위 클래스 코드도 바꿔줘야 하므로, 어색한 것은 분명함
- 이 부분을 의존성 역전 법칙에서 상위 클래스가 하부 클래스에 의존되는 역전현상을 막아야 한다라고 어렵게 써놓은 것임
In [8]:
sorting1 = SortManager()
sorting1.begin_sort()
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
가장 빠르게 풀스택 개발자가 될 수 있도록, 최적화된 로드맵을 제공합니다
의존성을 주입하고, 상위 클래스에서 하위 클래스 활용시 하위 클래스에 따라 변경되지 않도록, 일반화(추상화)된 설계를 하면 됨¶
In [16]:
# 좋은 예
class SortManager:
def __init__(self, sort_method): # <--- 의존성을 주입시킨다고 이야기함
self.set_sort_method(sort_method)
def set_sort_method(self, sort_method):
self.sort_method = sort_method
def begin_sort(self):
self.sort_method.sort() # <--- 하부 클래스가 바뀌더라도, 동일한 코드 활용 가능토록 인터페이스화
In [17]:
# 실습 코드
class BubbleSort:
def sort(self):
print('bubble sort')
pass
class QuickSort:
def sort(self):
print('quick sort')
pass
In [11]:
bubble_sort1 = BubbleSort()
quick_sort1 = QuickSort()
sorting1 = SortManager(bubble_sort1)
sorting1.begin_sort()
sorting2 = SortManager(quick_sort1)
sorting2.begin_sort()
초간단 연습2
selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기
selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기
본 자료와 같이 IT 기술을 잘 정리하여, 온라인 강의로 제공하고 있습니다
체계적으로 전문가 레벨까지 익힐 수 있도록 온라인 강의 로드맵을 제공합니다
In [19]:
# 좋은 예
class SortManager:
def __init__(self, sort_method): # <--- 의존성을 주입시킨다고 이야기함
self.set_sort_method(sort_method)
def set_sort_method(self, sort_method):
self.sort_method = sort_method
def begin_sort(self):
self.sort_method.sort() # <--- 하부 클래스가 바뀌더라도, 동일한 코드 활용 가능토록 인터페이스화
class SelectionSort:
def sort(self):
print('selection sort')
pass
selection_sort = SelectionSort()
sorting3 = SortManager(selection_sort)
sorting3.begin_sort()