행위

파이썬 클래스 설계방법

DB CAFE

Dbcafe (토론 | 기여)님의 2020년 9월 23일 (수) 10:19 판
thumb_up 추천메뉴 바로가기


1 클래스 설계 방법론[편집]

1.1 5가지 클래스 설계의 원칙 (S.O.L.I.D)[편집]

  1. S - SRP(Single responsibility principle) 단일 책임 원칙
  2. O - OCP(Open Closed Principle) 개방 - 폐쇄 원칙
  3. L - LSP(Liskov Substitusion Principle) 리스코프 치환 법칙
  4. I - ISP(Interface Segregation Principle) 인터페이스 분리 원칙
  5. D - DIP(Dependency Inversion Principle) 의존성 역전 법칙

출처) https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

1.2 SRP(Single responsibility principle) 단일 책임 원칙[편집]

  1. 클래스는 단 한개의 책임을 가져야 함 (클래스를 수정할 이유가 오직 하나)
    예: 계산기 기능 구현시, 계산을 하는 책임과 GUI를 나타낸다는 책임을 서로 분리하여, 각각 클래스로 설계
  2. 실제 애매한 부분이 많이 존재함, 가급적 설계시 고려하면 좋음.
    1. 나쁜 예
      1. 학생성적과 수강하는 코스를 한개의 class에서 다루는 예
      2. 한 클래스에서 두개의 책임을 갖기 때문에, 수정이 용이하지 않다.
class StudentScoreAndCourseManager(object):
    def __init__(self):
        scores = {}
        courses = {}
        
    def get_score(self, student_name, course):
        pass
    
    def get_courses(self, student_name):
        pass
    1. 변경 예
      1. 각각의 책임을 한개로 줄여서, 각각 수정이 다른 것에 영향을 미치지 않도록 함
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.3 OCP(Open Closed Principle) 개방-폐쇄 원칙[편집]

  1. 확장에는 열려있어야 하고, 변경에는 닫혀있어야 함
    예: 캐릭터 클래스를 만들 때, 캐릭터마다 행동이 다르다면, 행동 구현은 캐릭터 클래스의 자식 클래스에서 재정의(Method Override)한다.
    이 경우, 캐릭터 클래스는 수정할 필요 없고(변경에 닫혀 있음)
    자식 클래스에서 재정의하면 됨(확장에 대해 개방됨)
    1. 나쁜 예
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())
    1. 좋은 예
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.4 LSP(Liskov Substitusion Principle) 리스코프 치환 법칙[편집]

  1. 자식 클래스는 언제나 자신의 부모클래스와 교체할 수 있다는 원칙
  • 갤럭시폰 is a kind of 스마트폰
    • 스마트폰은 다른 사람과 전화와 메시지가 가능하다.
    • 스마트폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
    • 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
  • 위 설명을 갤럭시 폰으로 대체하면 아래와 같다.
    • 갤럭시 폰은 다른 사람과 전화와 메시지가 가능하다.
    • 갤럭시 폰은 데이터 또는 와이파이를 이용해 인터넷을 사용할 수 있다.
    • 스마트폰은 앱 마켓을 통해 앱을 다운 받을 수 있다.
  1. 연습3
    다음 캐릭터의 메서드를 모두 담은 클래스를 만든다면?
    어떻게 하면 OCP 원칙을 고려할 수 있을까요?
    1. Warrior
      - attack: 상대방 객체를 입력받아서, '칼로 찌르다' 출력하고, 상대방의 receive 메서드를 호출해서, striking_power만큼 상대방의 health_point를 낮춰준다.
      - receive: 상대방의 striking_point를 입력으로 받아서, 자신의 health_point를 그만큼 낮추기, health_point가 0 이하이면 '죽었음' 출력
      - use_shield: 1번 공격을 막는다.
    2. Elf
      - attack: 상대방 객체를 입력받아서, '마법을 쓰다' 출력하고, 상대방의 receive 메서드를 호출해서, striking_power만큼 상대방의 health_point를 낮춰준다.
      - receive: 상대방의 striking_point를 입력으로 받아서, 자신의 health_point를 그만큼 낮추기, health_point가 0 이하이면 '죽었음' 출력
      - wear_manteau: 1번 공격을 막는다.
    3. 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.5 ISP(Interface Segregation Principle) 인터페이스 분리 원칙[편집]

  1. 클래스에서 사용하지 않는(상관없는) 메서드는 분리해야 한다.
  1. 추상 클래스 선언하기
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)
class MyClass:
    pass

<source lang=python>
from abc import *

class Character(metaclass=ABCMeta):
    @abstractmethod
    def attack(self):
        pass
  1. 나쁜 예
    추상 클래스 상속하기
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. 첫 번째 예: 이렇게 작성하는 것이 우선 위 코드보다는 더 좋음1
    추상 클래스 선언하기
from abc import *

class Character(metaclass=ABCMeta):
    @abstractmethod
    def attack(self):
        pass
    
    @abstractmethod
    def move(self):
        pass
  1. 추상 클래스 상속하기
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


  1. 두 번째 예: 이렇게 작성하는 것도 처음 코드보다는 더 좋음
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
  1. 한발짝 더 나가보기!(심화 문제)
    게임 캐릭터 클래스 설계 예제 상기해봅니다. 다음 세 캐릭터의 다양한 메서드를 위 두번째 방법과 유사하게 작성해볼까요?
    게임 캐릭터는 다음과 같이 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()

칼로 찌른다