샘성의 iOS 개발 일지

Swift의 프로그래밍 패러다임 본문

iOS/Swift

Swift의 프로그래밍 패러다임

SamusesApple 2023. 6. 3. 15:10
728x90

 

1. 함수형 프로그래밍

   1-1. 함수형 프로그래밍이란?

 대입문을 사용하지 않은 프로그래밍이며, 순수함수를 조합하여 문제를 해결하는 방식이다. 함수형 프로그래밍은 깔끔하고 유지보수가 쉬운 소프트웨어를 만들기 위해 함수를 최상의 효과로 사용하는 것이다.

 

  1-2 함수형 프로그래밍의 특징

  • 함수형 프로그래밍은 과정(Process)보다 결과(Result)에 관심이 많다.
  • 무엇(What)이 실행될 지를 강조한다.
  • 데이터를 함수 밖에서 변형하지 않는다. (사이드이펙트를 줄이고, 함수가 어떤 영향을 주는지 추론하기 쉬워짐)
  • 함수형 프로그래밍은 문제를 함수로 분해(Decompose)한다. (각자 맡은 부분만 수행하도록)
  • 함수형 프로그래밍은 수학적 함수의 개념에 기반한다. 
  • 함수를 일급객체 취급한다. (아규먼트가 될 수 있음, 리턴값이 함수가 될 수 있음, 변수에 할당 가능)

 


 

2. 객체지향 프로그래밍 (OOP)

   2-1. 객체지향 프로그래밍이란?

   OOP(Object Oriented Programming)란, 프로그램을 단순히 '데이터' + '데이터 처리하는 방법'으로 분리하여 취급하지 않고 프로그램에 다수의 '객체'를 만들어놓고 객체들간의 상호작용을 통해 하나의 프로그램을 만드는 것이다.
  e.g) '쇼핑몰 앱' = NetworkManager로부터 쇼핑몰 리스트 가져오기 + UIView가 화면에 띄우기 + UIViewController가 사용자와 상호작용 및 화면 전환하기 등 여러 객체가 상호작용해서 앱 하나가 작동함
 
  

   2-2. 객체지향 프로그래밍 특징 (4가지)

  1. 캡슐화(은닉화)
    - 객체가 수행하고자하는 목적에 부합하는 변수와 함수를 묶는다.
    - 다른 객체가 사용할 수 있도록 이용 방법만 알려주고 접근제어(private, open 등)를 통해 외부에서 내부로의 기능 구현 내용 접근을 막는다. (외부에서 직접 변수에 접근하지 못하게 접근제어로 막고 함수를 통해서만 해당 변수에 접근할 수 있도록 함)
    - 장점: 오류 최소화, 유지보수에 좋음, 데이터가 바뀌어도 다른 객체에 영향을 끼치지 않음

      e.g) NetworkManager에 구현된 JSON데이터 파싱하는 코드들 구현 내용은 ViewController에서 알 수 없음. 그저 NetworkManager에서 요청한 파라미터에 적절한 아규먼트를 넣어서 코드를 사용할 뿐.
    NetworkManager 안에 구현된 JSON데이터 파싱하는 코드가 잘못되어도 ViewController 객체에게 영향을 주지 않고 NetworkManager만 손보면 됨.

  2. 상속
    - 상위 클래스의 데이터를 그대로 상속 받거나 재정의할 수 있다.
    - 상속을 하면 코드가 재활용이 되어서 효율적
    - 클래스가 다른 타입과 차별화되는 결정적인 특징
  3. 추상화
    - 일상생활 속에 있는 사물을 객체로 보고, 원하는 공통 특성들을 추출하여 어떤 추상적인 객체로 정의히는 것
    e.g) 후라이드 치킨 >> 치킨 >> ‘음식’

  4. 다형성
    - 하나의 객체가 다양한 타입과 형태로 저장될 수 있다. (override func, 동일한 함수 이름이라도 아규먼트 레이블이 다르다면 overload 가능)

 


 

3. 프로토콜지향 프로그래밍 (POP)

   3-1. 프로토콜 지향 프로그래밍이란?

  POP(Protocol Oriented Programming)이란, 객체지향 프로그래밍의 한계를 보완하기 위한 프로그래밍 패러다임이다.
 

   3-2. POP 장점

  클래스끼리만 상속이 가능하다는 점을 보완하여, 구조체에서 공통적으로 필요한 요구사항을 캡슐화하여 요청할 수 있다.
상속을 한 경우, 원하지 않는 상위 클래스의 데이터까지 받게되는 단점을 보완할 수 있다. (상속이 수직 확장이라면, 프로토콜은 수평 확장)
 

   3-3 프로토콜 지향 프로그래밍 예시 코드

  하단의 코드를 보면
1. 이름을 말하는 sayName 함수가 Human, Cat, Dog 클래스 모두 중복적으로 구현되어있다.
2. 그르렁거리는 함수(grrrrr)가 Cat과 Dog 클래스에 각각 중복적으로 구현되어있다.

class Animal {
    var name: String
    var numberOfLegs: Int
    
    init(name: String, numberOfLegs: Int) {
        self.name = name
        self.numberOfLegs = numberOfLegs
    }
}

class Human: Animal {
    func sayName() {
        print(self.name)
    }
    
    func talk() {
        print("\(self.name)이가 말을 한다")
    }
}

class Cat: Animal {
    func sayName() {
        print(self.name)
    }
    
    func grrrrrr() {
        print("그르렁")
    }
}

class Dog: Animal {
    func sayName() {
        print(self.name)
    }
    
    func grrrrrr() {
        print("그르렁")
    }
}

 
 
1. Animal 객체를 상속받은 클래스들에 sayName() 이 중복적으로 모두 구현되는 문제 개선.
 
  우선 모든 Animal 클래스를 상속받은 하위 객체들에게 sayName 함수는 공통적으로 필요하기에 Animal 클래스에 해당 함수를 넣어준다.

class Animal {
    var name: String
    var numberOfLegs: Int
    
    init(name: String, numberOfLegs: Int) {
        self.name = name
        self.numberOfLegs = numberOfLegs
    }
    
    func sayName() {
        print(self.name)
    }
}

class Human: Animal {
    func talk() {
        print("\(self.name)이가 말을 한다")
    }
}

class Cat: Animal {
    func grrrrrr() {
        print("그르렁")
    }
}

class Dog: Animal {
    func grrrrrr() {
        print("그르렁")
    }
}

 
 
 
2. 그르렁거리는 함수(grrrrr)가 Cat과 Dog 클래스에 각각 중복적으로 구현되어있는 점 개선 (Human에는 들어가면 안됨)
 
  canGrowl이라는 프로토콜을 생성하고, 해당 프로토콜을 확장하여 print("그르렁")이라는 함수의 기본 실행값을 준다.
클래스에서 한꺼번에 다 구현하면, Person까지 그르렁대는 상황이 생기는 것을 프로토콜을 통해 방지할 수 있다.

class Animal {
    var name: String
    var numberOfLegs: Int
    
    init(name: String, numberOfLegs: Int) {
        self.name = name
        self.numberOfLegs = numberOfLegs
    }
    
    func sayName() {
        print(self.name)
    }
}

protocol canGrowl: AnyObject {
    func grrrrrr()
}
// 프로토콜 확장을 통해 해당 함수의 기본값 제시
extension canGrowl {
    func grrrrrr() {
        print("그르렁")
    }
}

class Human: Animal {
    func talk() {
        print("\(self.name)이가 말을 한다")
    }
}

class Cat: Animal, canGrowl {
// name, sayName, grrrrr 다 갖게 됨
}

class Dog: Animal, canGrowl {
// name, sayName, grrrrr 다 갖게 됨
}

 
 
또한, 클래스는 상속이 가능하지만 구조체(struct)는 상속이 불가능하다. 이러한 구조체의 한계를 프로토콜을 사용하면 극복 가능하다.
 
  예시로, 상단에 사용했던 Cat과 Dog를 구조체 타입으로 변경하고 각각의 name이 필수적으로 필요하기에 canGrowl 프로토콜에 name 계산속성을 추가했다.
 
이렇게 하면, 프로토콜에서 반드시 필요한 내용을 캡슐화하여 요청할 수 있다. (canGrowl 프로토콜을 채택하면서 name 속성을 초기화 하지 않는 상황을 방지할 수 있음)

protocol canGrowl {
    var name: String { get set }
    func grrrrrr()
}

extension canGrowl {
    func grrrrrr() {
        print("그르렁")
    }
}

struct Cat: canGrowl {
    var name: String
}

struct Dog: canGrowl {
    var name: String
}

 

728x90