프로그래밍 패러다임

조정훈·2024년 3월 25일

선언형과 함수형 프로그래밍

  • 선언형 프로그래밍 : 무엇을 풀어내는가에 집중하는 패러다임

  • 함수형 프로그래밍 : 선언형 패러다임의 일종

순수함수 : 출력이 입력에만 의존하는 함수
고차함수 : 함수를 값처럼 매개변수로 받아 로직을 생성

def sum_of_list(numbers):
    if not numbers:  # 빈 리스트인 경우
        return 0
    else:
        return numbers[0] + sum_of_list(numbers[1:])

numbers = [1, 2, 3, 4, 5]
print(sum_of_list(numbers))  # 출력: 15

항상 같은 입력에 대해 같은 결과를 반환한다.
함수형 프로그래밍은 함수를 값으로 다루고, 불변성과 순수 함수를 지향하여 복잡성을 줄이고 코드의 이해를 쉽게 한다.
병렬처리, 오류 줄이기 등 가능하고 테스트하기 쉬운 코드를 작성하기 좋다.


객체지향 프로그래밍

특징

  • 추상화(abstraction)
    복잡한 시스템으로부터 핵심적인 개념 또는 기능을 간추린것
    ex) 자동차 의 속도,연료량,모델명,색상,가격 등등 있는데 모델명,가격 등으로 따로 특성별로 클래스 속성으로 만든다

  • 캡슐화(Encapsulation)
    객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는것
    ex) 은행 계좌 잔액을 private으로 만들어서 외부에서 접근 못하도록함

  • 상속성(Inheritance)
    상위클래스의 특성을 하위클래스가 이어받아서 재사용하거나 추가, 확장하는것
    ex) 동물클래스의 이름,나이가 속성으로 있고 이걸 상속받아 고양이,강아지 같은 새로운 클래스 만들수 있다.

    class Animal:
       def __init__(self, name, age):
           self.name = name
           self.age = age
    
       def make_sound(self):
           pass  # 이 메서드는 하위 클래스에서 오버라이드될 것입니다.    
           
    class Cat(Animal):
       def make_sound(self):
           return "Meow"
           
    class Dog(Animal):
       def make_sound(self):
           return "Woof"
    
       # 상속을 통해 Cat 클래스와 Dog 클래스는 Animal 클래스의 속성과 메서드를 상속받습니다.
       # 하지만 각 클래스는 고유한 make_sound 메서드를 가지고 있습니다.
       
    cat = Cat("Whiskers", 5)
    print(cat.name)  # 출력: Whiskers
    print(cat.make_sound())  # 출력: Meow
    
    dog = Dog("Buddy", 3)
    print(dog.name)  # 출력: Buddy
    print(dog.make_sound())  # 출력: Woof
    
  • 다형성(Polymorphsim)
    하나의 메서드나 클래스가 다양한 방법으로 동작하는 것. 오버로딩, 오버라이딩이 있다.

    class Animal:
       def sound(self):
           return "Some generic sound"
    
    class Cat(Animal):
        def sound(self):
            return "Meow"
    
    class Dog(Animal):
        def sound(self):
            return "Woof"
    
    def make_animal_sound(animal):
        return animal.sound()
    
    # Animal, Cat, Dog 클래스는 각각 sound 메서드를 정의하고 있습니다.
    # make_animal_sound 함수는 어떤 종류의 동물 객체를 받아도 그 객체의 sound 메서드를 호출합니다.
    # 이때 다형성의 특성에 따라 각 동물 객체에 따라 다른 소리가 출력됩니다.
    
    generic_animal = Animal()
    print(make_animal_sound(generic_animal))  # 출력: Some generic sound
    
    cat = Cat()
    print(make_animal_sound(cat))  # 출력: Meow
    
    dog = Dog()
    print(make_animal_sound(dog))  # 출력: Woof     

    위 코드에서 make_sound_sound 함수는 인자로 전달된 객체의 'sound'메서드를 호출하므로 어떤 종류의 동물 객체를 전달하든 해당 객체의 소리를 출력한다. 이렇게 동일한 메서드 이름을 쓰면서도 각 클래스에 따라 다르게 동작하는 것이 다형성의 핵심

  • 오버로딩
    같은 이름을 가진 메서드를 여러 개 두는 것. 파이썬에서는 기본적으로 오버로딩 지원 안하지만 가변 인자를 활용하여 비슷하게 구현 가능. 정적다형성

    class Calculator:
       def add(self, a, b):
           return a + b
    
       def add(self, a, b, c):
           return a + b + c
    
    # Calculator 클래스에서 add 메서드를 두 번 정의했습니다.
    # 첫 번째는 두 개의 매개변수를 받아서 더하는 기능을 하고,
    # 두 번째는 세 개의 매개변수를 받아서 더하는 기능을 합니다.
    
    calculator = Calculator()
    print(calculator.add(1, 2))  # 오류: 두 개의 매개변수를 받는 add 메서드가 없습니다.
    print(calculator.add(1, 2, 3))  # 출력: 6
  • 오버라이딩
    상위 클래스로부터 상속받은 메서드를 하위클래스가 재정의하는 것. 동적다형성

    class Animal:
       def make_sound(self):
           return "Some generic sound"
    
    class Cat(Animal):
        def make_sound(self):
            return "Meow"
    
    class Dog(Animal):
        def make_sound(self):
            return "Woof"
    
    cat = Cat()
    print(cat.make_sound())  # 출력: Meow
    
    dog = Dog()
    print(dog.make_sound())  # 출력: Woof

    설계원칙

  • 단일 책임 원칙
    클래스는 하나의 책임만 가져야 한다.
    ex) A라는 로직이 있다. 어떤 클래스는 A에 관한 클래스여야하고 이를 수정할때도 A와 관련된 수정이어야한다

  • 개방-폐쇄 원칙(OCP)
    유지보수사항이 생기면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀있어야 한다. 기존 코드를 잘 변경하지 않고도 확장은 쉽게 가능해야한다.

  • 리스코프 치환 원칙(LSP)
    정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 하는 것
    부모 객체에 자식객체를 넣어도 시스템이 문제없이 돌아가야 한다.

  • 인터페이스 분리 원칙(ISP)
    하나의 일반적인 인터페이스보다 구체적인 여러개의 인터페이스를 만들어야 한다.

  • 의존 역전 원칙(DIP)
    자신보다 쉬운것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 거의 변화에 영향받지 않아야한다.
    ex) 타이어를 갈아 끼울 수 있는 틀을 만든 후 다양한 타이어를 교체할 수 있어야한다. 즉 상위계층은 하위계층의 변화에 대한 구현으로부터 독립해야 한다.



절차형 프로그래밍

순차적으로 실행하여 원하는 결과를 얻는 방식
ex) 머신러닝 배치작업, 함수

# 사용자로부터 두 개의 숫자를 입력 받는다.
num1 = float(input("첫 번째 숫자를 입력하세요: "))
num2 = float(input("두 번째 숫자를 입력하세요: "))

# 두 숫자를 더한다.
sum_result = num1 + num2

# 두 숫자를 뺀다.
sub_result = num1 - num2

# 두 숫자를 곱한다.
mul_result = num1 * num2

# 두 숫자를 나눈다.
div_result = num1 / num2

# 결과를 출력한다.
print("덧셈 결과:", sum_result)
print("뺄셈 결과:", sub_result)
print("곱셈 결과:", mul_result)
print("나눗셈 결과:", div_result)

절차대로 위에서 아래로 직선적으로 이어짐. 모듈화하기 어렵고 유지보수성이 떨어진다.


-------------------

패러다임의 혼합

어떠한 패러다임이 가장 좋냐?
-> 그런것은 없다. 비즈니스 로직이나 서비스의 특징을 고려해서 패러다임을 정하는 것이 좋다. 상황과 맥락에 따라 패러다임 간의 장점만 취해 개발하는 것이 좋다.

ex) 백엔드에 머신러닝 파이프라인과 거래 관련 로직이 있다면 머신러닝 파이프라인은 절차지향형 패러다임, 거래관련로직은 함수형 프로그래밍으로 적용

0개의 댓글