[프로그래밍 패러다임] 객체지향 프로그래밍(OOP)

유재경·2021년 6월 13일
0
post-thumbnail

정의

프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.

절차지향(명령형) 프로그래밍을 보완하기 위해 등장했습니다. 절차지향 프로그래밍은 모듈을 재활용하기 어렵기 때문에 대규모 프로젝트에서는 코드의 반복으로 인해 장황해지거나 비효율적인 설계가 되겠죠. "데이터와 이를 처리하는 함수를 함께 묶을 수 없을까?", "프로그램을 어떻게 구조적으로 나눌 수 있을까?", "코드의 재사용성을 높일 수 없을까?" 를 해결하는 것이 바로 객체지향 프로그래밍의 핵심입니다.

특징

클래스와 인스턴스(객체)

클래스는 특정 기능을 수행하기 위해 추상화를 거쳐 집단에 속하는 속성과 행위를 변수와 메소드로 정의한 것입니다. 특정 기능을 구현하기 위해 필요한 인스턴스와 메소드를 담아둔 컨테이너랄까요
객체는 클래스에서 정의한 것을 실제로 메모리 상에 할당되어 구현된 실체입니다.

클래스 단위로 데이터와 관련 메소드를 정의해놓기 때문에 모듈 재활용에 용이합니다. 객체는 상태 값(state)를 갖고 그 값에 따라 다르게 행동합니다. 그런데 상태 값을 갖는다는 것이 꼭 좋은 것만은 아닙니다. 상태 값에 따라 다르게 행동한다는 것은 개발자 입장에서는 런타임에서 변화에 따른 객체의 동작이 예측이 어렵다는 것을 의미합니다. 그렇게 되면, side effect가 발생하고 복잡도도 증가하게 되겠죠!? (이같은 부작용을 줄이고자 하는 것이 추후 나올 함수형 프로그래밍, 프로토콜 지향 프로그래밍이 핵심입니다.)

추상화

공통의 속성이나 기능을 묶어 이름을 붙이는 것으로, 'class 이름'으로 정의하는 것을 말합니다. 예를 들어 에스프레소, 아메리카노, 바닐라라떼, 카페라떼를 '커피'라는 상위 그룹으로 묶는 것입니다.

캡슐화

절차지향 프로그래밍에서는 변수와 함수가 분산되어 있어 재사용하기 어려웠으나 캡슐화로 관련된 기능과 특성을 한 곳에 모으고 코드를 재사용하도록 하는 것입니다. OOP에서는 "클래스"가 "캡슐"이 되는 것으로 데이터를 외부에서 직접적으로 접근하지 못하고 오로지 클래스를 통해 접근하게 됩니다.

상속

부모클래스의 프로퍼티와 메소드를 자식클래스에서 그대로 사용할 수 있으며 자식클래스는 그 외 다른 메소드나 프로퍼티를 추가할 수 있습니다.
클래스를 상속할 수 있다는 것이 기능 확장에 유리하고 재사용에 뛰어납니다. 여러 클래스를 정의해두지 않고 한 클래스를 정의해두면 이것을 자식클래스에서 다시 정의하지 않아도 그대로 쓸 수 있기 때문이죠.
그러나, 클래스는 아시다시피 참조 타입이므로 한 객체를 두고 여기저기 작업 시 값이 예기치 못하게 변경되는 일이 발생할 수 있습니다. 예를 들면, 객체의 값을 수정 중에 다른 곳에서 그 객체의 값을 불러오게 되는 경우 예상과 다른 결과를 얻을 수 있는 것이죠.

다형성

하나의 변수명이나 함수명이 상황에 따라 다른 의미로 해석될 수 있는 것입니다.
다형성을 지원하는 방법으로는 오버라이딩과 오버로딩이 있습니다.
오버라이딩은 부모 클래스가 가지고 있는 메소드를 자식 클래스가 재정의해서 사용하는 것이고, 오버로딩은 같은 이름의 메소드를 여러 개 가지면서 매개 변수의 유형과 개수를 다르게 하는 것입니다.

// overloading
class Person {
    let name: String
    var age: Int
    
    init(name: String, age: Int) {
    	self.name = name
        self.age = age 
    }
    
    func introduce() {
    	print("My name is \(name).")
    }
    
    func introduce(age: Int) {
    	print("My name is \(name) and my age is \(age).")
    }
}


// overriding 
class student: Person {
    func introduce(age: Int) {
    	print("My name is \(name) and I'm a student.")
    }
}

한계

앞서 말했듯이, 함수의 비일관성 문제가 있다. 객체는 현재 상태 값에 의존하여 동작하기 때문에 함수를 매번 호출할 때 다른 결과값이 반환된다. 그런데 이것이 어떤 문제가 있나??! 이런 구조는 테스트 케이스를 작성하기 어렵게 된다. 같은 input에 매번 다른 output이 나와 예측하기 힘들기 때문에 디버깅 시 출력에 문제가 있는지 여부를 확인하기 어렵습니다.

객체 간 의존성이 증가합니다. 객체 간 상호 작용을 위해 함수가 다른 객체의 함수를 호출합니다. 이 과정에서 overhead를 발생시킬 수 있고, 함수는 외부 세계에 의존적으로 동작하기 때문에(side-effect문제) 재사용성이 떨어지고 프로그램의 복잡도가 증가합니다.

객체 내 상태 변화를 통제하거나 추적하는 것이 어렵습니다 대부분의 함수는 객체 내 변수들을 외부에서 변경 가능하게 하는데, 다수의 외부 경로를 통해 상태가 변경되는 경우 디버깅이 어려울 수도 있습니다.

(출처 https://jeong-pro.tistory.com/95
SPRi-변화하는 프로그래밍언어 '함수형 프로그래밍'이 뜬다. p.29~35)

profile
iOS 개발

0개의 댓글