본문 바로가기

IT/Python

[객체지향 파이썬 프로그래밍] __init__() method [2]

1. 각 하위 클래스에서 __init__() 구현


이번에는 최상위 클래스에서 __init__() 메소드를 구현하는 것이 아닌, 각 하위 클래스에서 __init__() 메소드를 구현하는 방법에 대해서 알아보겠습니다.

이때, 각 하위 클래스로의 코드 복제를 막기 위해 중복 금지::DRY, Don't Repeat Yourself 원칙을 따르겠습니다.

다음 예제는 하위 클래스에서 초기화를 수행합니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class Suit:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol
 
class Card:
    pass
 
class NumberCard(Card):
    def __init__(self, rank, suit):
        self.suit = suit
        self.rank = str(rank)
        self.hard = self.soft = rank
 
class AceCard(Card):
    def __init__(self, rank, suit):
        self.suit = suit
        self.rank = "A"
        self.hard, self.soft = 111
 
class FaceCard(Card):
    def __init__(self, rank, suit):
        self.suit = suit
        self.rank = {11'J'12'Q'13'K'}[rank]
        self.hard = self.soft = 10
 
def card(rank, suit):
 
    if rank == 1return AceCard('A', suit)
    elif 2 <= rank < 11return NumberCard(str(rank), suit)
    elif 11<= rank < 14return FaceCard(rank, suit)
    else:
        raise Exception("Rank out of range")
 
Club, Diamond, Heart, Spade = Suit('Club''♣'), Suit('Diamond''◆'), Suit('Heart''♥'), Suit('Spade''♠')
deck = [card(rank, suit) for rank in range(114for suit in (Club, Diamond, Heart, Spade)]
 
print("------------------------------------------------")
for i in range(len(deck)):
    print("OBJECT : {}".format(deck[i]))
    print("suit : {}".format(deck[i].suit))
    print("suit->name : {}".format(deck[i].suit.name))
    print("suit->symbol : {}".format(deck[i].suit.symbol))
    print("rank : {}".format(deck[i].rank))
    print("hard : {}".format(deck[i].hard))
    print("soft : {}".format(deck[i].soft))
    print("------------------------------------------------")
 
# 공통적인 초기화가 빠져서 불필요한 반복이 발생
# suit의 반복적인 초기화
# 따라서 이를 상위 클래스로 올려야 한다.
 
>>------------------------------------------------
OBJECT : <__main__.AceCard object at 0x104b38400>
suit : <__main__.Suit object at 0x104a9b390>
suit->name : Club
suit->symbol : ♣
rank : A
hard : 1
soft : 11
------------------------------------------------
OBJECT : <__main__.AceCard object at 0x104b38438>
suit : <__main__.Suit object at 0x104b38240>
suit->name : Diamond
suit->symbol : ◆
rank : A
hard : 1
soft : 11
------------------------------------------------
.
.
.
(중략)



다형성이 분명하게 생겼지만, 공통적인 초기화가 빠져서 불필요한 중복이 발생하게 됩니다.


여기서 불필요한 중복이란 suit의 반복적인 초기화를 의미합니다.


따라서 불필요한 중복을 막기위해서 이를 상위 클래스로 올려야합니다.


이를 구현한 코드는 다음과 같습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Suit:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol
 
class Card:
    def __init__(self, rank, suit, hard, soft):
        self.rank = rank
        self.suit = suit
        self.hard = hard
        self.soft = soft
 
class NumberCard(Card):
    def __init__(self, rank, suit):
        super().__init__(str(rank), suit, rank, rank)
 
class AceCard(Card):
    def __init__(self, rank, suit):
        super().__init__("A", suit, 111)
 
class FaceCard(Card):
    def __init__(self, rank, suit):
        super().__init__({11'J'12:'Q'13:'K'}[rank], suit, 1010)
 
def card(rank, suit):
 
    if rank == 1return AceCard(rank, suit)
    elif 2 <= rank < 11return NumberCard(rank, suit)
    elif 11<= rank < 14return FaceCard(rank, suit)
    else:
        raise Exception("Rank out of range")
 
Club, Diamond, Heart, Spade = Suit('Club''♣'), Suit('Diamond''◆'), Suit('Heart''♥'), Suit('Spade''♠')
deck = [card(rank, suit) for rank in range(114for suit in (Club, Diamond, Heart, Spade)]
 
print("------------------------------------------------")
for i in range(len(deck)):
    print("OBJECT : {}".format(deck[i]))
    print("suit : {}".format(deck[i].suit))
    print("suit->name : {}".format(deck[i].suit.name))
    print("suit->symbol : {}".format(deck[i].suit.symbol))
    print("rank : {}".format(deck[i].rank))
    print("hard : {}".format(deck[i].hard))
    print("soft : {}".format(deck[i].soft))
    print("------------------------------------------------")
 
# 공통적인 초기화가 빠져서 불필요한 반복이 발생한 것을 제거
# 이전에 비해서 팩토리 함수가 간소화
# 하지만, __init__() method 구현의 공수에 비교했을 때, factroy 함수의 간소화의 효율이 떨어진다.
 
# __init__() method는 직접적인 방법을 사용하고
# 복잡한 부분은 팩토리 함수에 넣는게 더 효율적이다.
 



해당 코드는 하위 클래스와 상위 클래스 모두에서 __init__()을 정의합니다. 다음 코드 조각에서 알 수 있듯이 


이 방법은 이전에 만든 팩토리 함수를 간소화시키는 이점이 있습니다.


하지만 이런 형태의 변경은 팩토리 함수의 성능 향상이 상대적으로 크지 않은데 비해, __init__() 메소드는 훨씬 복잡해집니다.



2. 단순 복합 개체


복합 객체는 컨테이너::Container라고 부릅니다.

카드 덱을 표현하는 단순 객체를 살펴보겠습니다. 해당 객체는 기본적인 컬렉션입니다. 

너무 기본적이라 간단한 list를 덱으로 사용할 수 있습니다. 


1
2
3
deck = [card.rank(r+1).suit(s) for r in range(13for s in (Club, Diamond, Heart, Spade)]
random.shuffle(deck)
hand = [deck.pop(), deck.pop()]
cs



일반적으로 객체의 컬렉션을 디자인 하는 일반적인 디자인 전략은 다음과 같이 3가지가 있습니다.


  • 래핑(Wrapping)
  • 확장(Extend)
  • 개발(Invent)


2-1. 컬렉션 클래스 랩핑

다음은 내부 컬렉션을 포함하는 래퍼 디자인 입니다.

1
2
3
4
5
6
7
8
9
class Deck:
    def __init__(self):
        Club, Diamond, Heart, Spade = Suit('Club''♣'), Suit('Diamond''◆'), Suit('Heart''♥'), Suit('Spade''♠')
        card = CardFactory()
        self._cards = [card.rank(r+1).suit(s) for r in range(13for s in (Club, Diamond, Heart, Spade)]
        random.shuffle(self._cards)
 
    def pop(self):
        return self._cards.pop()
cs


내부 컬렉션을 list 객체로 만드는 Deck을 정의했습니다.


Deck의 pop() 메소드를 래핑된 list 객체에 위임합니다.


이제 다음과 같은 코드를 이용하여 Hand 인스턴스를 생성할 수 있습니다.


1
2
deck = Deck()
hand = [deck.pop(), deck.pop()]
cs



전체 코드는 다음과 같습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import random
 
class Suit:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol
 
 
class Card:
    def __init__(self, rank, suit):
        self.suit = suit
        self.rank = rank
        self.hard, self.soft = self._points()
 
 
class NumberCard(Card):
    def _points(self):
        return int(self.rank), int(self.rank)
 
 
class AceCard(Card):
    def _points(self):
        return 111
 
 
class FaceCard(Card):
    def _points(self):
        return 1010
 
class CardFactory:
 
    def rank(self, rank):
        self.class_, self.rank_str={
            1:(AceCard, 'A'),
            11:(FaceCard, 'J'),
            12:(FaceCard, 'Q'),
            13:(FaceCard, 'K'),
        }.get(rank, (NumberCard, str(rank)))
 
        return self
 
    def suit(self, suit):
        return self.class_(self.rank_str, suit)
 
 
 
class Deck:
    def __init__(self):
        Club, Diamond, Heart, Spade = Suit('Club''♣'), Suit('Diamond''◆'), Suit('Heart''♥'), Suit('Spade''♠')
        card = CardFactory()
        self._cards = [card.rank(r+1).suit(s) for r in range(13for s in (Club, Diamond, Heart, Spade)]
        random.shuffle(self._cards)
 
    def pop(self):
        return self._cards.pop()
 
 
deck = Deck()
hand = [deck.pop(), deck.pop()]
 
print("HAND : {}\n".format(hand))
 
print("------------------------------------------------")
for i in range(len(hand)):
    print("OBJECT : {}".format(hand[i]))
    print("suit : {}".format(hand[i].suit))
    print("suit->name : {}".format(hand[i].suit.name))
    print("suit->symbol : {}".format(hand[i].suit.symbol))
    print("rank : {}".format(hand[i].rank))
    print("hard : {}".format(hand[i].hard))
    print("soft : {}".format(hand[i].soft))
    print("------------------------------------------------")
 
 
cs


2-1. 컬렉션 클래스 확장


래핑 외에 내장 클래스를 확장하는 방법도 있습니다. 

이렇게 하면, pop() 메소드를 다시 구현하지 않고 단순히 상속받을 수 있습니다.

1
2
3
4
5
6
class Deck(list):
    def __init__(self):
        Club, Diamond, Heart, Spade = Suit('Club''♣'), Suit('Diamond''◆'), Suit('Heart''♥'), Suit('Spade''♠')
        card = CardFactory()
        super().__init__(card.rank(r+1).suit(s) for r in range(13for s in (Club, Diamond, Heart, Spade))
        random.shuffle(self)
cs



다음 코드를 살펴보면, list를 확장한 Deck 클래스를 정의한 것을 확인할 수 있습니다.