객체 지향 프로그래밍(OOP) - 기초

객체 지향 프로그래밍(OOP. Object Oriented Programming)

  • Procedual와 Object-Oriented

    • Procedual

      • “함수”를 이용해 추상화
    • Object-Oriented

      • “객체”를 통해 추상화

      • 현실에 존재하는 것들을 어떻게 모델링할 것인가?

  • 객체

    • “관련있는” 데이터(정보, 변수)

    • “관련있는” 변수(상태 정보)와 함수를 한데 모아놓은 ‘곳’

    • the bunding of data with methods

    • attribute = instance member, instance method

  • 객체 == 인스턴스

    • 메모리 상으로는 둘은 완벽히 같다.

    • 다만 객체는 객체 자체에 집중하고, 인스턴스는 특정 해당 클래스에 집중한다.

1. 캡슐화(Encapsulation)

  • “관련있는” 멤버(데이터)와 메소드(행동)를 하나의 단위로 묶는 것

2. 정보은닉(Information Hiding)

  • 어떤 멤버를 공개 또는 비공개할 것인가?

  • 어떤 메소드를 공개 또는 비공개할 것인가?

  • python에서는 완벽한 정보은닉을 제공하지 않는다.(name-mangling, property)

3. 다형성(polymorphism)

  • 부모 클래스에서 물려받은 가상 함수를 자식 클래스 내에서 오버라이딩되어 사용되는 것

SOLID

  1. 단일책임의 원칙(SRP. Single Responsibility Principle)

  2. 개방폐쇄의 원칙(OCP, Open Close Principle)

  3. 리스코브 치환의 원칙(LSP. The Liskov Substitution Principle)

  4. 인터페이스 분리의 원칙(ISP. Interface Segregation Principle)

  5. 의존성역전의 법칙(DIP. Dependency Inversion Principle)

GOF(Gang of Four) Design Pattern

  1. Creational Pattern

    • 객체를 생성하는데 관련된 패턴들

    • 객체가 생성되는 과정의 유연성을 높이고 코드의 유지를 쉽게 함

    • Class - Factory Method

    • Object - Abstract Factory, Builder, Prototype, Singleton

  2. Structural Pattern

    • 프로그램 구조에 관련된 패턴들

    • 프로그램 내에 자료구조나 인터페이스 구조 등 프로그램의 구조를 설계하는 데 활용할 수 있는 패턴들

    • Class - Adapter

    • Object - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

  3. Behavioral Pattern

    • 반복적으로 사용되는 객체들의 상호작용을 패턴화 해놓은 것들

    • Class - Interpreter, Template Method

    • Object - Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor

  • 자주 쓰는 패턴 : Singleton, Observer, Abstruct

closure and function and class

  • closure를 사용한 계좌 만들기 예제

    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
    def account(clnt_name, balance):      
    def change_money(money):
    nonlocal balance
    balance += money
    return (clnt_name, balance)
    return change_money

    # 상태 정보 - free variable
    # gloval variable - 맨 바깥쪽
    # local variable - 함수 내

    my_acnt = account('greg', 5000)
    your_acnt = account('john', 3000)

    type(my_acnt)
    >> function

    my_acnt.__name__ == your_acnt.__name__
    >> True

    my_acnt.__name__
    >> 'change_money'

    my_acnt(1000) # 입력 + 상태 정보 = 출력이 나온다.
    >> ('greg', 6000)

    your_acnt(1000)
    >> ('john', 4000)

    cells = my_acnt.__closure__
    for cell in cells:
    print(cell.cell_contents, end = ' ')
    >> 6000 greg
  • 함수와 딕셔너리를 통한 계좌 만들기 예제

    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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    def account_init(clnt_name, balance):
    """
    account_init(clnt_name, balance) --> dictionary
    return : dictionary : 이름과 잔고로 반환
    """
    account_data = {'name' : clnt_name, 'balance' : balance}

    return account_data


    def account_deposit(clnt_data, money):
    """
    account_deposit(clnt_data, money) --> boolean
    만약에 money > 0이면 입금 성공!
    아니면 에러 메시지 출력 후 실패!
    """
    if money < 0:
    print("입금은 0원 초과부터 가능합니다.")
    return False

    else:
    clnt_data['balance'] += money
    a = clnt_data['balance']
    print('이름 : {} 입금 : {} 총액 : {}'.format(clnt_data['name'], money, clnt_data['balance']))
    return True


    def account_withdraw(clnt_data, money):
    """
    account_withdraw(clnt_data, money) --> integer
    return : 인출된 돈
    만약 잔고가 모자라면 None
    """

    if clnt_data['balance'] < money:
    print(f'잔액이 부족합니다.')
    return None

    else:
    clnt_data['balance'] -= money
    print('이름 : {} 출금 : {} 총액 : {}'.format(clnt_data['name'], money, clnt_data['balance']))
    return money


    def account_transfer(clnt_data, other_data, money):
    """
    account_transfer(clnt_data, other_data, money) --> None
    clnt_data에서 other_data로 금액 이동
    """

    clnt_data['balance'] -= money

    account_deposit(other_data, money)


    if __name__ == '__main__':

    my_acnt = account_init('greg', 5000)
    your_acnt = account_init('john', 2000)

    print(my_acnt)
    print(your_acnt)
    print('')

    account_deposit(my_acnt, 5000)
    account_deposit(your_acnt, 8000)

    print(my_acnt)
    print(your_acnt)
    print('')

    account_transfer(my_acnt, your_acnt, 5000)

    print(my_acnt)
    print(your_acnt)
    print('')

    >> {'name': 'greg', 'balance': 5000}
    {'name': 'john', 'balance': 2000}

    이름 : greg 입금 : 5000 총액 : 10000
    이름 : john 입금 : 8000 총액 : 10000

    {'name': 'greg', 'balance': 10000}
    {'name': 'john', 'balance': 10000}

    이름 : john 입금 : 5000 총액 : 15000
    {'name': 'greg', 'balance': 5000}
    {'name': 'john', 'balance': 15000}
  • OOP를 설계를 통한 계좌 만들기 예제

    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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    # 전역 함수 : 어느 클래스에도 속하지 않는다.
    # OOP 설계에서는 쓰지 않는 방법이기 때문에 staticmethod 개념 존재
    def func(a, b):
    return a + b

    class Account:
    # 클래스 이름의 맨 앞은 대문자로 하도록 하자.

    # 클래스 멤버(class member)

    # 모든 객체가 공유한다.

    # 전역 변수(global variable)를 대체


    # ex) 이자율
    interest_rate = 0.08
    num_of_account = 0

    # 클래스 메소드(class method)

    # 객체가 하나도 없는 상태에서도 호출이 가능!!

    # 전역 함수(global variable)를 대체

    # 전역 함수를 대체할 때는 static method를 쓸 수도 있다.

    # 대체 생성자(alternative constructor)

    # cls는 클래스 자체를 받는다.

    # staticmethod는 메소드처럼 보이지만 함수 : 전역 함수
    @staticmethod
    def func(a, b):
    return a + b

    @classmethod #데코레이터
    def get_num_of_account(cls):
    """
    Account.get_num_of_account() -> integer
    """
    return cls.num_of_account

    # 대체 생성자
    # 받을 데이터에 맞춰서 설계
    @classmethod
    def string_constructor(cls, string):
    data = string.split('_')
    clnt_name = data[0]
    balance = int(data[1])
    return cls(clnt_name, balance)

    # 생성자(constructor) : 파이썬에서는 오직 하나
    # 객체(object)가 생성될 때 반드시!!! 한번 호출된다.

    def __init__(self, clnt_name, balance):
    # 인스턴스 멤버(instance member) --> 상태 정보 = 데이터
    # self라는 말은 자기 스스로의 메모리를 가리킨다는 의미
    # self는 객체 메모리를 자체 참조
    self.clnt_name = clnt_name

    # 변수 앞에 "__"를 붙이면 정보 은닉으로 접근하지 말라는 표시

    # 변환 규칙 중에 "_현재클래스이름__멤버변수"를 하면 접근 가능
    self.__balance = balance

    # 클래스 멤버에 접근하는 방법
    Account.num_of_account += 1



    # 인스턴스 메소드(instance method)
    def deposit(self, money):
    """
    deposit(money) --> boolean
    만약에 money > 0이면 입금 성공!
    아니면 에러 메시지 출력 후 실패!
    """
    if money < 0:
    print("입금은 0원 초과부터 가능합니다.")
    return False
    else:
    self.__balance += money
    print(f'{money}원을 입금하셨고, 현재 남아있는 금액은 {self.__balance}원입니다.')
    return True

    # 인스턴스 메소드
    def withdraw(self, money):
    """
    withdraw(money) --> integer
    return : 인출된 돈
    만약 잔고가 모자라면 None
    """
    if self.__balance < money:
    print(f'현재 금액은 {self.__balance}원으로, 출금하실 금액 {money}원보다 부족하여 출금하실 수 없습니다.')
    return None
    else:
    self.__balance -= money
    print(f'{money}원을 출금하였고, 현재 남아있는 금액은 {self.__balance}원입니다.')
    return money

    def transfer(self, other, money):
    # 내 객체가 가진돈
    self.__balance -= money
    # message passing
    # 다른 객체의 상태 정보(인스턴스 멤버)를 변경할 대는 반드시 상대 객체가
    # 가진 메소드를 이용
    # 절대 상대의 객체에 직접 접근하면 안된다.
    other.deposit(money)

    def __str__(self):
    return f'{self.clnt_name} : {self.__balance}'


    if __name__ == '__main__':

    # 객체가 생성되지 않아도 호출이 가능

    print(Account.interest_rate)
    print(Account.get_num_of_account())

    # 객체를 생성
    my_acnt = Account('greg', 5000)
    your_acnt = Account('john', 2000)

    print(my_acnt.interest_rate)

    print(my_acnt.get_num_of_account())

    print(Account.func(5, 4))

    print(type(Account.func))

    print(type(Account.get_num_of_account))

    print(type(my_acnt.deposit))

    # 대체 생성자를 이용한 객체의 생성

    s = 'james_6000'
    his_acnt = Account.string_constructor(s)

    print(his_acnt)

    # instance method 호출하는 방법
    # 외부에서는 self는 자동으로 들어가기 때문에 매개 변수에 쓸 필요가 없다.

    my_acnt.deposit(7000)

    # 메소드 vs 함수(일반)
    # 메소드 = 입력 + 인스턴스 멤버(상태 정보, 데이터)에 의해 결과값이 결정!
    # = 인스턴스 멤버(상태 정보)를 바꾸는 역할!
    # 함수 = 입력에 의해서 출력이 결정

    res1 = my_acnt.withdraw(3000)
    res2 = your_acnt.withdraw(3000)

    print(my_acnt)
    print(your_acnt)

    # 객체 간에 상호 작용 --> 객체가 가지고 있는 멤버 값(데이터, 상태정보)
    # INTERACTRION by 메소드에 의해!!
    # my_acnt.transfer(your_acnt, 1000)
    my_acnt.transfer(your_acnt, 1000)

    # 절대 짜서는 안되는 코드
    # 이렇게 멤버 변수에 직접 접근하는 방법은 좋지 않다.
    # my_acnt.balance -= 1000
    # your_acnt.balance += 1000

    >> 0.08
    0
    0.08
    2
    9
    <class 'function'>
    <class 'method'>
    <class 'method'>
    james : 6000
    7000원을 입금하셨고, 현재 남아있는 금액은 12000원입니다.
    3000원을 출금하였고, 현재 남아있는 금액은 9000원입니다.
    현재 금액은 2000원으로, 출금하실 금액 3000원보다 부족하여 출금하실 수 없습니다.
    greg : 9000
    john : 2000
    1000원을 입금하셨고, 현재 남아있는 금액은 3000원입니다.
Share