카테고리 없음

swift 객체지향 프로그램 추상화 캡슐화 상속 다형성(오버라이딩, 오버로딩) 알아보자

kingarthur 2024. 6. 12. 15:46

객체지향이 먼지도 모르고 일단 오버라이드를 사용하게 되었는데 

무엇이길레 중요하고 꼭 알아야 하는가 궁굼증 때문에 찾아보게 되었다. 

객체지향 프로그래밍은 4가지 특성으로 가진다고 한다.

🟠 객체지향 프로그래밍의 4가지 특성 (✨중요!)

1️⃣ 추상화

객체의 공통적인 속성과 기능을 추출하여 정의하는 것
  • 즉, 현실세계의 사물을 객체로 보고, 필요한 공통특성만 다루어 현실의 복잡성을 제거하고 목적에 집중할 수 있도록 한다.
  • 예를들어 토끼, 강아지, 사자라는 객체들을 하나읙 공통된 특징을 바탕으로 포유류 로 추상화 할 수 있다.

2️⃣ 캡슐화

 

- 객체 수행 목적에 따라 데이터 구조 및 처리 방법을 결합시켜 묶어서, 외부에 내부 기능 구현 내용을 감추고 이용방법만 알려줌.
- 외부객체는 내부객체의 구조를 알지 못하고, 객체가 노출해서 제공하는 필드와 메서드만 이용할 수 있음.
 

 

📝 이렇게 왜 외부객체가 알지 못하게 캡슐화를 하는건가요?

캡슐화는 관련있는 변수와 함수를 하나의 클래스로 묶고 외부에서 접근하지 못하도록 은닉하는게 핵심이다. 객체에 직접적인 접근을 막고 외부에서 내부의 정보에 직접 접근하거나 변경할 수 없고, 객체가 접근하는 필드와 메서드를 통해서만 접근이 가능하다.

왜 이렇게 하냐면, 바로 캡슐화를 하면 은닉화특징의 장점을 갖기 때문이다.

은닉화의 장점

  • 외부에서 특정 객체의 데이터 및 함수의 직접접근을 막음으로써 변경을 못하게 하여, 유지보수나 확장시 오류의 범위를 최소화할 수 있다. (객체 내 정보 손상, 오용을 방지하고, 조작법이 바뀌어도 사용방법 자체는 바뀌지 않아서 오류의 범위를 최소화한다는 것이다.)
  • 데이터가 변경되어도 다른 객체에 영향을 주지 않는, 독립성을 갖는다.
  • 처리된 결과를 사용하므로, 이식성이 좋고 객체를 모듈화 할 수 있어서 새로운 시스템의 구성에 하나의 모듈처럼 사용이 가능하다.

 

 

캡슐화를 위해서는 접근제어자를 통해 설계가 잘 이루어져야 한다. 자신 내부의 모듈을 감추고 다른 모듈 내부 작업도 직저적으로 개입하지 못하게 하도록 설계해야한다. (ex. public, private, getter, setter 등과 같은 접근제어자가 존재하는 이유이기도 하다.)\

3️⃣ 상속 (Interitance)

상위개념의 특징을 하위 개념이 물려받아 사용하는 것

📝 상속은 왜 해야하나요?

  • 재사용으로 인해서 코드의 길이를 줄이고, 가독성을 높인다.
  • 상속은 코드의 재사용 관점이 아닌, 기능의 확장개념으로 생각해야한다. (상위 클래스에 있는 기능을 그대로 상속받는다고!)

4️⃣ 다형성 (Polymorphism) (✨중요, 물어보는 면접 좀 많앗음!)

상속과 연관있는 개념으로, 한 객체가 다른 여러형태(객체)로 재구성되는 것
 

즉, 한 부모 밑에서 상속받는 자식들이 모두 똑같지는 않다라는 개념이다.

오버로드와 오버라이드가 다형성의 대표적인 예시이다. 오버로드와 오버라이딩 처럼, 자식클래스가 다양한 형태로 존재할 수 있게 하는 특징이 바로 다형성이다.

 

📝 오버라이드와 오버로드가 뭔가요? 차이점을 알아두자!

오버라이드, 오버로드를 적용한 프로그래밍 방식을 각각 오버라이딩, 오버로딩 이라고 한다.

 

✏️ 오버라이딩 (Overriding) (Swift 기준!)

서브클래스는 슈퍼클래스에서 상속할 메서드, 프로퍼티, 서브스크립트를 서브클래스에서
원하는대로 구현(재정의)할 수 있는데, 이것을 오버라이딩 이라고 함.
 

(⚠️ swift에서의 오버라이딩은 제약조건이 조금 존재함. 프로퍼티의 경우 저장속성만 추가할 수 있고, 이름과 타입은 반드시 명시해야하고, final을 붙이면 더 이상 상속이 불가능하거나 오버라이딩이 불가능하다.. 등 다양한 제한 조건이 있는데 나중에 더 알아보자.)

 

class Human {
    func description {
        print("나는 사람")
    {
}

class Teacher: Human { }

위에서 Teacher이란 클래스가 Human이라는 클래스를 상속받았기 때문에 아래처럼 description이란 메서드에 접근이 가능하다.

let brown: Teacher = .init()
brown.description()  // Human의 func에 접근 가능!

이렇게 그냥 접근하게 되면, 부모클래스의 Human에 그대로 접근가능한건데, override를 사용하면 함수를 '재정의'할 수 있다.

class Teacher: Human {
    override func description() {
        print("나는 보라온!")
    }
}

let brown: Teacher = .init()
brown.description()
// 나는 보라온! 이라고 출력된다.

이렇게 한다면, 재정의된 Teacher에 접근한 프로퍼티들은 모두 override 된 함수가 실행된다! 이것이 바로 오버라이딩에 대한 개념이다.

 

✏️ 오버로딩 (Overloading) (Swift 기준!)

함수의 이름은 같으나, 매개변수, 리턴타입 등으로 다르게 설정하여 함수를 중복으로 선언할 수 있다는 개념이다. Swift는 오버로딩을 허용하고, 함수, 서브스크립트, 생성자에서 사용할 수 있다.

 

이 개념은, 객체지향 프로그래밍에서 쓰이는 개념이라고 잘 알아두자!

func sum() { }
func sum() { }

이렇게 하게 되면, Invalid redclaration of "sum()" 이라는 에러가 뜨게된다. 함수가 재선언됐다는 에러이다. Swift는 오버로딩을 지원하기 때문에, 아래와 같이 파라미터나 리턴값을 다르게 하여 함수를 재정의하면 에러가 나지 않는다.

func sum() { }
func sum(_a: Int, _ b: Int) { }
func sum() -> Int { return 0 }

이렇게 세 함수는 sum()이라는 이름은 같지만 각각 갖고 있는 매개변수, 리턴값이 다르기 때문에 재선언됐다는 에러가 나지 않는다. 이는 오버로딩 개념을 지원하는 특성 때문이다.

즉, 함수를 식별할 때 함수 이름 뿐만 아니라, 함수 이름, 파라미터(타입, 개수, argument label), 리턴타입 모두를 고려해서 함수를 식별한다고 알 수 있다. 오버로딩의 장점은, 타입이나 리턴값이 다르지만, 동일한 기능을 하는 함수를 하나로 만들 수 있다는 것이다.

// ❌ 오버로딩을 지원하지 않는다면
func sumString(_ a: String, _ b: String) -> String {
    return a + b
}

func sumInt(_ a: Int, _ b: Int) -> Int {
    return a + b
}

sumInt(1, 2)
sumString("ABC","DEF")

// ✅ 오버로딩을 지원하는 경우
func sum(_ a: Stirng, _ b: String) -> String {
    return a + b
}

func sum(_ a: Int, _ b: Int) -> Int {
    return a + b
}

sum(1,2)
sum("ABC","DEF")

위 두개의 함수는 모두 sum 기능을 한다. 하지만 오버로딩을 지원하지 않는 경우에는 sumString sumInt로 따로 구분해서 타입별로 함수를 작성해주어야한다. 하지만, 오버로딩이 가능하다면, 아래의 경우처럼 sum 하나의 함수이름으로 접근이 가능하다. (결국 다른함수긴 하지만, 코드를 작성하는 입장에서는 매우 편리하고, 가독성 또한 좋아진다.)


🔸 OOP의 장점

  • 재사용성: 상속을 통해 코드의 재사용성을 높일 수 있다.
  • 생산성 향상: 잘 설계된 클래스를 만들어서 독립적인 객체를 사용함으로써 개발의 생산성을 향상시킬 수 있다.
  • 자연적인 모델링: 일상생활에서 모습의 구조가 객체에 자연스럽게 녹아들었기 때문에 생각하고 있는 것을 그대로 자연스럽게 구현할 수 있다. (기능별로 나눠서 구현한다거나,,)
  • 유지보수의 우수성: 기존 기능을 수정 시 함수를 새롭게 바꾸더라도 캡슐화 되어 그 함수의 세부 정보가 은닉되어 있기 때문에 주변에 미치는 영향을 최소화 하기 때문에 유지보수의 우수성을 갖는다. 새로운 객체의 종류를 추가 시에는 상속을 통해서 기존의 기능을 활용하고, 존재하지 않은 새로운 속성만 추가하면 되므로 매우 경제적이다.

🔸 OOP의 단점

  • 많은 오버헤드가 발생한다. (객체간의 정보교환이 모두 메시지 교환을 통해서 일어나기 때문이다.)
 오버헤드란, 어떤 처리를 하기 위해서 소모되는 간접적인 처리 시간, 메모리를 의미한다.
  • 상태를 가지는 객체로 인한 버그 발생 가능성이 존재함: 내부 변수로 인해 객체가 예측할 수 없는 상태를 갖게 되기 때문이다.

🟠 OOP의 5대 원칙 SOLID와 SWIFT

🔸 스위프트는 객체지향? 함수지향? 무슨 패러다임 언어임??

스위프트의 대표적인 특징은 아래와 같다.

- ARC (Automatic Reference Counting, 자동 참조 카운팅) 지원
- Objective-C의 동적 객체 모델과 매개변수 형식 도입
- 컴파일 언어
 

그리고, Swift는 다중 패러다임 언어이다. 스위프트는 다음과 같은 프로그래밍 패러다임을 차용해서 만들어졌다.

- 명령형 프로그래밍 패러다임
- 객체지향 프로그래밍 패러다임
- 함수형 프로그래밍 패러다임
- 프로토콜 프로그래밍 패러다임
 

정확하게는, 명령형과 객체지향을 기반으로, 함수형, 프로토콜 프로그래밍 패러다임을 지향한다. (진짜 짬뽕이잖아?🍜)

  • 애플의 프레임워크 대부분은 객체지향 프로그래밍 패러다임을 기반으로 설계된 수많은 클래스로 구성되어있음.
  • 애플의 프레임워크에서 사용될 언어라면, 객체지향 프로그래밍 패러다임을 수용해야만 할 것이다.
  • 또, 스위프트는 함수형 프로그래밍 패러다임을 강조한다. 애플 프레임워크를 벗어나 다른 영역에서 스위프트를 사용했을 때, 순수하게 함수형 프로그래밍 패러다임으로 프로그램을 작성할 수 있기 때문이다.)

따라서 스위프트는, 함수형과 객체지향 둘다 차용하고 있으므로, 두개의 패러다임을 잘 섞어서 코딩을 한다면, 필요한 기능에 맞게 최적의 성능을 발휘하고, 생산성도 극대화 할 수 있을 듯?!