"파이썬 클래스 설계방법"의 두 판 사이의 차이
DB CAFE
(→상세 페이지 참고) |
(→5가지 클래스 설계의 원칙 (S.O.L.I.D)) |
||
(같은 사용자의 중간 판 10개는 보이지 않습니다) | |||
1번째 줄: | 1번째 줄: | ||
== 클래스 설계 방법론 == | == 클래스 설계 방법론 == | ||
=== 5가지 클래스 설계의 원칙 (S.O.L.I.D) === | === 5가지 클래스 설계의 원칙 (S.O.L.I.D) === | ||
− | # S - SRP(Single responsibility principle) 단일 책임 원칙 | + | {{틀:고지상자 |
+ | |아이콘색=red | ||
+ | |아이콘이름=laptop_mac | ||
+ | |제목 = " 5가지 클래스 설계의 원칙 " | ||
+ | |내용 =# S - SRP(Single responsibility principle) 단일 책임 원칙 | ||
# O - OCP(Open Closed Principle) 개방 - 폐쇄 원칙 | # O - OCP(Open Closed Principle) 개방 - 폐쇄 원칙 | ||
# L - LSP(Liskov Substitusion Principle) 리스코프 치환 법칙 | # L - LSP(Liskov Substitusion Principle) 리스코프 치환 법칙 | ||
# I - ISP(Interface Segregation Principle) 인터페이스 분리 원칙 | # I - ISP(Interface Segregation Principle) 인터페이스 분리 원칙 | ||
# D - DIP(Dependency Inversion Principle) 의존성 역전 법칙 | # D - DIP(Dependency Inversion Principle) 의존성 역전 법칙 | ||
+ | }} | ||
출처) https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design | 출처) https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design | ||
− | = | + | ==== SRP(Single responsibility principle) 단일 책임 원칙 ==== |
− | |||
− | |||
− | === SRP(Single responsibility principle) 단일 책임 원칙 === | ||
# 클래스는 단 한개의 책임을 가져야 함 (클래스를 수정할 이유가 오직 하나) | # 클래스는 단 한개의 책임을 가져야 함 (클래스를 수정할 이유가 오직 하나) | ||
#: 예: 계산기 기능 구현시, 계산을 하는 책임과 GUI를 나타낸다는 책임을 서로 분리하여, 각각 클래스로 설계 | #: 예: 계산기 기능 구현시, 계산을 하는 책임과 GUI를 나타낸다는 책임을 서로 분리하여, 각각 클래스로 설계 | ||
# 실제 애매한 부분이 많이 존재함, 가급적 설계시 고려하면 좋음. | # 실제 애매한 부분이 많이 존재함, 가급적 설계시 고려하면 좋음. | ||
− | + | #: | |
## 나쁜 예 | ## 나쁜 예 | ||
### 학생성적과 수강하는 코스를 한개의 class에서 다루는 예 | ### 학생성적과 수강하는 코스를 한개의 class에서 다루는 예 | ||
51번째 줄: | 53번째 줄: | ||
pass | pass | ||
</source> | </source> | ||
+ | |||
+ | ==== OCP(Open Closed Principle) 개방-폐쇄 원칙 ==== | ||
+ | |||
+ | # 확장에는 열려있어야 하고, 변경에는 닫혀있어야 함 | ||
+ | #: 예: 캐릭터 클래스를 만들 때, 캐릭터마다 행동이 다르다면, 행동 구현은 캐릭터 클래스의 자식 클래스에서 재정의(Method Override)한다. | ||
+ | #:이 경우, 캐릭터 클래스는 수정할 필요 없고(변경에 닫혀 있음) | ||
+ | #:자식 클래스에서 재정의하면 됨(확장에 대해 개방됨) | ||
+ | #: | ||
+ | ## 나쁜 예 | ||
+ | <source lang=python> | ||
+ | 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()) | ||
+ | </source> | ||
+ | |||
+ | ## 좋은 예 | ||
+ | <source lang=python> | ||
+ | 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()) | ||
+ | </source> | ||
+ | |||
+ | ==== LSP(Liskov Substitusion Principle) 리스코프 치환 법칙 ==== | ||
+ | # 자식 클래스는 언제나 자신의 부모클래스와 교체할 수 있다는 원칙 | ||
+ | * 갤럭시폰 is a kind of 스마트폰 | ||
+ | ** 스마트폰은 다른 사람과 전화와 메시지가 가능하다. | ||
+ | ** 스마트폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다. | ||
+ | ** 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다. | ||
+ | * 위 설명을 갤럭시 폰으로 대체하면 아래와 같다. | ||
+ | ** 갤럭시 폰은 다른 사람과 전화와 메시지가 가능하다. | ||
+ | ** 갤럭시 폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다. | ||
+ | ** 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다. | ||
+ | |||
+ | # 연습3 | ||
+ | #: 다음 캐릭터의 메서드를 모두 담은 클래스를 만든다면? | ||
+ | #: 어떻게 하면 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씩 올려준다. | ||
+ | |||
+ | <source lang=python> | ||
+ | # 추상 클래스 선언하기 | ||
+ | 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 | ||
+ | |||
+ | </source> | ||
+ | |||
+ | ==== ISP(Interface Segregation Principle) 인터페이스 분리 원칙 ==== | ||
+ | # 클래스에서 사용하지 않는(상관없는) 메서드는 분리해야 한다. | ||
+ | |||
+ | # 추상 클래스 선언하기 | ||
+ | <source lang=python> | ||
+ | from abc import * | ||
+ | |||
+ | class Character(metaclass=ABCMeta): | ||
+ | @abstractmethod | ||
+ | def attack(self): | ||
+ | pass | ||
+ | |||
+ | @abstractmethod | ||
+ | def move(self): | ||
+ | pass | ||
+ | |||
+ | @abstractmethod | ||
+ | def eat(self): | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | * metaclass 란? | ||
+ | ** 클래스를 만들기 위해 파이썬에서는 기본 metaclass가 사용됨 | ||
+ | *** 즉, 클래스를 만들기 위해서 메타클래스 라는 것이 필요했던 것임 | ||
+ | *** class 생성시, () 아무 것도 넣지 않으면, 기본 파이썬에서 클래스를 만들기 위한 메타클래스가 쓰인다고 보면 됨 | ||
+ | *** 추상 클래스 만들시에는 기본 메타클래스로는 생성이 어려우니, 다음과 같이 작성 | ||
+ | **** class Character(metaclass=ABCMeta) | ||
+ | *** 싱글톤을 위해 기본 메타클래스를 바꾸는 것임 (싱글톤은 다음에 나오는 디자인 패턴에서 설명) | ||
+ | **** class PrintObject(metaclass=Singleton) | ||
+ | |||
+ | <source lang=python> | ||
+ | class MyClass: | ||
+ | pass | ||
+ | |||
+ | <source lang=python> | ||
+ | from abc import * | ||
+ | |||
+ | class Character(metaclass=ABCMeta): | ||
+ | @abstractmethod | ||
+ | def attack(self): | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | # 나쁜 예 | ||
+ | #: 추상 클래스 상속하기 | ||
+ | <source lang=python> | ||
+ | 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") | ||
+ | </source> | ||
+ | |||
+ | |||
+ | # 첫 번째 예: 이렇게 작성하는 것이 우선 위 코드보다는 더 좋음1 | ||
+ | #: 추상 클래스 선언하기 | ||
+ | <source lang=python> | ||
+ | from abc import * | ||
+ | |||
+ | class Character(metaclass=ABCMeta): | ||
+ | @abstractmethod | ||
+ | def attack(self): | ||
+ | pass | ||
+ | |||
+ | @abstractmethod | ||
+ | def move(self): | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | # 추상 클래스 상속하기 | ||
+ | <source lang=python> | ||
+ | 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") | ||
+ | </source> | ||
+ | |||
+ | <source lang=python> | ||
+ | elf1 = Elf() | ||
+ | human1 = Human() | ||
+ | |||
+ | elf1.attack() | ||
+ | elf1.move() | ||
+ | human1.attack() | ||
+ | human1.move() | ||
+ | human1.eat() | ||
+ | practice the black art | ||
+ | fly | ||
+ | plunge a knife | ||
+ | run | ||
+ | eat foods | ||
+ | </source> | ||
+ | |||
+ | |||
+ | # 두 번째 예: 이렇게 작성하는 것도 처음 코드보다는 더 좋음 | ||
+ | <source lang=python> | ||
+ | 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 | ||
+ | </source> | ||
+ | <source lang=python> | ||
+ | # 추상 클래스 상속하기 | ||
+ | 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") | ||
+ | </source> | ||
+ | <source lang=python> | ||
+ | elf1 = Elf() | ||
+ | human1 = Human() | ||
+ | |||
+ | elf1.attack() | ||
+ | elf1.move() | ||
+ | human1.attack() | ||
+ | human1.move() | ||
+ | human1.eat() | ||
+ | practice the black art | ||
+ | fly | ||
+ | plunge a knife | ||
+ | run | ||
+ | eat foods | ||
+ | </source> | ||
+ | |||
+ | # 한발짝 더 나가보기!(심화 문제) | ||
+ | #: 게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요? | ||
+ | #: 게임 캐릭터는 다음과 같이 3명이 존재하고, 각각의 메서드는 다음과 같음 | ||
+ | * Warrior | ||
+ | ** - 공격하면 칼로 찌른다를 출력 | ||
+ | * Elf | ||
+ | ** - 공격하면 마법을 쓴다를 출력 | ||
+ | * Wizard | ||
+ | ** - 공격하면 마법을 쓴다를 출력 | ||
+ | <source lang=python> | ||
+ | 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() | ||
+ | </source> | ||
+ | |||
+ | 칼로 찌른다 | ||
+ | |||
+ | ==== DIP(Dependency Inversion Principle) 의존성 역전 법칙 ==== | ||
+ | # 부모 클래스는 자식 클래스의 구현에 의존해서는 안됨 | ||
+ | # 자식 클래스 코드 변경 또는 자식 클래스 변경시, 부모 클래스 코드를 변경해야 하는 상황을 만들면 안됨 | ||
+ | # 자식 클래스에서 부모 클래스 수준에서 정의한 추상 타입에 의존할 필요가 있음 | ||
+ | ## 실습 코드 | ||
+ | <source lang=python> | ||
+ | class BubbleSort: | ||
+ | def bubble_sort(self): | ||
+ | # sorting algorithms | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | ## 나쁜예 | ||
+ | <source lang=python> | ||
+ | class SortManager: | ||
+ | def __init__(self): | ||
+ | self.sort_method = BubbleSort() # <--- SortManager 는 BubbleSort에 의존적 | ||
+ | |||
+ | def begin_sort(self): | ||
+ | self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적 | ||
+ | </source> | ||
+ | |||
+ | 이렇게 되면 어떤 문제가 생길까요? BubbleSort의 메서드 이름을 바꿔봤습니다. | ||
+ | |||
+ | <source lang=python> | ||
+ | # BubbleSort의 bubble_sort 메서드 변경 | ||
+ | class BubbleSort: | ||
+ | def sort(self): | ||
+ | print('bubble sort') | ||
+ | pass | ||
+ | </source> | ||
+ | <source lang=python> | ||
+ | sortmanager = SortManager() | ||
+ | sortmanager.begin_sort() | ||
+ | --------------------------------------------------------------------------- | ||
+ | AttributeError Traceback (most recent call last) | ||
+ | <ipython-input-15-7b6c218e4fe5> in <module>() | ||
+ | 1 sortmanager = SortManager() | ||
+ | ----> 2 sortmanager.begin_sort() | ||
+ | |||
+ | <ipython-input-14-eb257b4cc514> in begin_sort(self) | ||
+ | 5 | ||
+ | 6 def begin_sort(self): | ||
+ | ----> 7 self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적 | ||
+ | |||
+ | AttributeError: 'BubbleSort' object has no attribute 'bubble_sort' | ||
+ | </source> | ||
+ | |||
+ | 그러면, 위 예에서는 상위 클래스인 SortManager도 코드를 바꿔줘야 한다. | ||
+ | 하부 클래스 코드를 수정하면 상위 클래스 코드도 바꿔줘야 하므로, 어색한 것은 분명함 | ||
+ | 이 부분을 의존성 역전 법칙에서 상위 클래스가 하부 클래스에 의존되는 역전현상을 막아야 한다라고 어렵게 써놓은 것임 | ||
+ | |||
+ | <source lang=python> | ||
+ | sorting1 = SortManager() | ||
+ | sorting1.begin_sort() | ||
+ | </source> | ||
+ | <source lang=python> | ||
+ | --------------------------------------------------------------------------- | ||
+ | AttributeError Traceback (most recent call last) | ||
+ | <ipython-input-8-19868312ab3c> in <module>() | ||
+ | 1 sorting1 = SortManager() | ||
+ | ----> 2 sorting1.begin_sort() | ||
+ | |||
+ | <ipython-input-6-eb257b4cc514> in begin_sort(self) | ||
+ | 5 | ||
+ | 6 def begin_sort(self): | ||
+ | ----> 7 self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적 | ||
+ | |||
+ | AttributeError: 'BubbleSort' object has no attribute 'bubble_sort' | ||
+ | </source> | ||
+ | |||
+ | 의존성을 주입하고, 상위 클래스에서 하위 클래스 활용시 하위 클래스에 따라 변경되지 않도록, 일반화(추상화)된 설계를 하면 됨 | ||
+ | |||
+ | # 좋은 예 | ||
+ | <source lang=python> | ||
+ | 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() # <--- 하부 클래스가 바뀌더라도, 동일한 코드 활용 가능토록 인터페이스화 | ||
+ | </source> | ||
+ | |||
+ | # 실습 코드 | ||
+ | <source lang=python> | ||
+ | class BubbleSort: | ||
+ | def sort(self): | ||
+ | print('bubble sort') | ||
+ | pass | ||
+ | |||
+ | class QuickSort: | ||
+ | def sort(self): | ||
+ | print('quick sort') | ||
+ | pass | ||
+ | </source> | ||
+ | |||
+ | <source lang=python> | ||
+ | bubble_sort1 = BubbleSort() | ||
+ | quick_sort1 = QuickSort() | ||
+ | |||
+ | sorting1 = SortManager(bubble_sort1) | ||
+ | sorting1.begin_sort() | ||
+ | |||
+ | sorting2 = SortManager(quick_sort1) | ||
+ | sorting2.begin_sort() | ||
+ | bubble sort | ||
+ | quick sort | ||
+ | </source> | ||
+ | |||
+ | # 초간단 연습2 | ||
+ | * selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기 | ||
+ | |||
+ | # 좋은 예 | ||
+ | <source lang=python> | ||
+ | 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() | ||
+ | </source> | ||
+ | selection sort | ||
+ | |||
+ | === 상세 페이지 참고 === | ||
+ | https://www.fun-coding.org/PL&OOP2-1.html | ||
+ | |||
+ | [[category:python]] |
2023년 5월 25일 (목) 01:06 기준 최신판
thumb_up 추천메뉴 바로가기
- DBA { Oracle DBA 명령어 > DBA 초급 과정 > DBA 고급 과정 }
- 튜닝 { 오라클 튜닝 목록 }
- 모델링 { 데이터 모델링 가이드 }
목차
1 클래스 설계 방법론[편집]
1.1 5가지 클래스 설계의 원칙 (S.O.L.I.D)[편집]
출처) https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
1.1.1 SRP(Single responsibility principle) 단일 책임 원칙[편집]
- 클래스는 단 한개의 책임을 가져야 함 (클래스를 수정할 이유가 오직 하나)
- 예: 계산기 기능 구현시, 계산을 하는 책임과 GUI를 나타낸다는 책임을 서로 분리하여, 각각 클래스로 설계
- 실제 애매한 부분이 많이 존재함, 가급적 설계시 고려하면 좋음.
- 나쁜 예
- 학생성적과 수강하는 코스를 한개의 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.2 OCP(Open Closed Principle) 개방-폐쇄 원칙[편집]
- 확장에는 열려있어야 하고, 변경에는 닫혀있어야 함
- 예: 캐릭터 클래스를 만들 때, 캐릭터마다 행동이 다르다면, 행동 구현은 캐릭터 클래스의 자식 클래스에서 재정의(Method Override)한다.
- 이 경우, 캐릭터 클래스는 수정할 필요 없고(변경에 닫혀 있음)
- 자식 클래스에서 재정의하면 됨(확장에 대해 개방됨)
- 나쁜 예
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())
- 좋은 예
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())
1.1.3 LSP(Liskov Substitusion Principle) 리스코프 치환 법칙[편집]
- 자식 클래스는 언제나 자신의 부모클래스와 교체할 수 있다는 원칙
- 갤럭시폰 is a kind of 스마트폰
- 스마트폰은 다른 사람과 전화와 메시지가 가능하다.
- 스마트폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
- 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
- 위 설명을 갤럭시 폰으로 대체하면 아래와 같다.
- 갤럭시 폰은 다른 사람과 전화와 메시지가 가능하다.
- 갤럭시 폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
- 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
- 연습3
- 다음 캐릭터의 메서드를 모두 담은 클래스를 만든다면?
- 어떻게 하면 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씩 올려준다.
# 추상 클래스 선언하기
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
1.1.4 ISP(Interface Segregation Principle) 인터페이스 분리 원칙[편집]
- 클래스에서 사용하지 않는(상관없는) 메서드는 분리해야 한다.
- 추상 클래스 선언하기
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)
- 클래스를 만들기 위해 파이썬에서는 기본 metaclass가 사용됨
class MyClass:
pass
<source lang=python>
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
- 나쁜 예
- 추상 클래스 상속하기
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
- 추상 클래스 선언하기
from abc import *
class Character(metaclass=ABCMeta):
@abstractmethod
def attack(self):
pass
@abstractmethod
def move(self):
pass
- 추상 클래스 상속하기
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")
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
practice the black art
fly
plunge a knife
run
eat foods
- 두 번째 예: 이렇게 작성하는 것도 처음 코드보다는 더 좋음
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
# 추상 클래스 상속하기
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")
elf1 = Elf()
human1 = Human()
elf1.attack()
elf1.move()
human1.attack()
human1.move()
human1.eat()
practice the black art
fly
plunge a knife
run
eat foods
- 한발짝 더 나가보기!(심화 문제)
- 게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
- 게임 캐릭터는 다음과 같이 3명이 존재하고, 각각의 메서드는 다음과 같음
- Warrior
- - 공격하면 칼로 찌른다를 출력
- Elf
- - 공격하면 마법을 쓴다를 출력
- Wizard
- - 공격하면 마법을 쓴다를 출력
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()
칼로 찌른다
1.1.5 DIP(Dependency Inversion Principle) 의존성 역전 법칙[편집]
- 부모 클래스는 자식 클래스의 구현에 의존해서는 안됨
- 자식 클래스 코드 변경 또는 자식 클래스 변경시, 부모 클래스 코드를 변경해야 하는 상황을 만들면 안됨
- 자식 클래스에서 부모 클래스 수준에서 정의한 추상 타입에 의존할 필요가 있음
- 실습 코드
class BubbleSort:
def bubble_sort(self):
# sorting algorithms
pass
- 나쁜예
class SortManager:
def __init__(self):
self.sort_method = BubbleSort() # <--- SortManager 는 BubbleSort에 의존적
def begin_sort(self):
self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적
이렇게 되면 어떤 문제가 생길까요? BubbleSort의 메서드 이름을 바꿔봤습니다.
# BubbleSort의 bubble_sort 메서드 변경
class BubbleSort:
def sort(self):
print('bubble sort')
pass
sortmanager = SortManager()
sortmanager.begin_sort()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-15-7b6c218e4fe5> in <module>()
1 sortmanager = SortManager()
----> 2 sortmanager.begin_sort()
<ipython-input-14-eb257b4cc514> in begin_sort(self)
5
6 def begin_sort(self):
----> 7 self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적
AttributeError: 'BubbleSort' object has no attribute 'bubble_sort'
그러면, 위 예에서는 상위 클래스인 SortManager도 코드를 바꿔줘야 한다. 하부 클래스 코드를 수정하면 상위 클래스 코드도 바꿔줘야 하므로, 어색한 것은 분명함 이 부분을 의존성 역전 법칙에서 상위 클래스가 하부 클래스에 의존되는 역전현상을 막아야 한다라고 어렵게 써놓은 것임
sorting1 = SortManager()
sorting1.begin_sort()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-19868312ab3c> in <module>()
1 sorting1 = SortManager()
----> 2 sorting1.begin_sort()
<ipython-input-6-eb257b4cc514> in begin_sort(self)
5
6 def begin_sort(self):
----> 7 self.sort_method.bubble_sort() # <--- BubbleSort의 bubble_sort 메서드에 의존적
AttributeError: 'BubbleSort' object has no attribute 'bubble_sort'
의존성을 주입하고, 상위 클래스에서 하위 클래스 활용시 하위 클래스에 따라 변경되지 않도록, 일반화(추상화)된 설계를 하면 됨
- 좋은 예
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 BubbleSort:
def sort(self):
print('bubble sort')
pass
class QuickSort:
def sort(self):
print('quick sort')
pass
bubble_sort1 = BubbleSort()
quick_sort1 = QuickSort()
sorting1 = SortManager(bubble_sort1)
sorting1.begin_sort()
sorting2 = SortManager(quick_sort1)
sorting2.begin_sort()
bubble sort
quick sort
- 초간단 연습2
- selection sort 를 출력하는 SelectionSort 클래스 만들고, SortManager로 begin_sort() 호출해서 출력해보기
- 좋은 예
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()
selection sort