Object Oriented Programing (feat. 추상화와 다형성)

Eli·2021년 2월 18일
0

내가 주로 사용하게 되는 언어 Swift는 객체지향의 언어이다.

딱히 그 개념에 대해 공부시간을 가져본 적이 없는 것 같아, 이번 클린아키텍처를 공부하고 별도로 다뤄보기로 했다.

OOP Background

먼저 생겨난 배경부터 생각을 해 보자.

나는 C언어를 다뤄보진 않았지만 C언어가 주류인 시절이 있었다고 한다.
C언어는 기본적으로 절차지향적. 위에서 아래로 내려가며 코드의 분기는 goto문으로 이루어 졌다고 한다.

ex) 10번째 줄에 있었다면 30라인으로 이동 등...

이로인해 규칙성 없이 goto문 만을 난무하게 되면 흐름이 없이 코드가 얽혀 유지보수성이나 재활용성 많은 부분에서 떨어진다고 한다.

이러한 문제들을 해결하기 위해 객체지향언어가 도입되기 시작되었다고 한다.

OOP의 목적

  • 코드를 유연하고 변경이 쉽도록 한다.
  • 직관적인 코드 분석이 가능하도록 한다.

위를 목적으로 Strong Cohesion Weak Coupling을 지향하는 컨셉을 지니게 되었다.

Strong Cohesion

한개의 모듈/클래스 내에 존재하는 요소들의 관계의 정도를 나타내는 의미.
한개의 모듈이 한개의 목적을 지향하면서 모든 요소들이 같은 목적을 바라보는 것이 좋은 코드라는 의미이다.

Weak Coupling

Coupling은 모듈과 모듈 사이의 의존, 결합정도를 의미한다.
Coupling이 모듈이 다른 모듈에 의존할 때 의존의 정도가 낮아져야 코드를 쉽게 변경 가능할 수 있다는 의미.

OOP의 특성

OOP는 위와 같은 배경과 목적을 가지고 아래와 같은 특성들을 만들어 해결하고자 했다.

캡슐화

  • 객체 스스로가 자신의 목적에 따라 필요한 요소들(변수/함수)을 함께 묶는 것
  • 다른 외부 객체에서 자신의 내부 요소들에 대한 접근을 선택적으로 해. 안정성을 높이는 것

상속성

  • 부모 클래스의 속성과 기능을 상속받아 그대로 사용가능한 기능.
  • 부모 클래스의 기능을 활용해 재사용이 가능한 코드를 만들 수 있다.

다형성

  • 한 행동을 정의하고 그것을 사용하는 클래스마다 다르게 구현 할 수 있도록 하는 것.
  • 한 개의 기능을 가지고 상황에 따라 다른 행동들과 목적에 맞는 다른 행위들을 수행할 수 있다.

추상화

  • 한 개의 기능의 특성을 일반화하고 제거해 가장 단순한 것으로 만든다.
  • 가장 단순하고 핵심적인 것과 디테일하고 상세적인 것을 분리하면서 재사용성, 의존성들의 문제들을 해결 할 수 있다.

Class vs Object vs Instance

사실 그 동안 느낌적으로만 알았던 내용인데 리드의 피드백을 듣고 한번 정리를 해보았다.

어떻게 보면 다 비슷한 놈 할 수도 있다고하나(모니터의 그 문서를 본다면 다 같은걸 말하겠지만), 실제 컴퓨터 내부에선 다 다른 것을 의미를 가진다.

막상 글로 정리하려니 정말 적당한 표현이나 딱 이해가 가능한 말들이 없다.

옛날에 내가 문서를 읽어봤을때 왜 이렇게 밖에 설명 못해? 라고 생각했는데 진짜 그렇게 밖에 설명 못하는 내용 같다.

그래도 내가 나름 이해한 설명이나 단어들로 정의를 해본다.

Class

어떠한 요소와 기능들을 구현한 구상도.

그냥 단일 Class 파일이나 Class의 괄호 묶음정도로 생각을 하면 되겠다.

Instance

위의 Class 등이 실제 구현되어 메모리에 올라가 컴퓨터 내부의 실체를 가르킨다.

Object

Class의 Instance로 생각을 하면 되며, 코드를 보고 이야기 할 때 이용하는게 맞을 듯 하다.

ex) 여기서 이 객체는 초기화 되고... (뭐 이런식?)

//아래 놈은 Class
class Person {
	var age: Int
	var name: String
}

let person = Person()

//person은 객체가 된다.

//Instance는? 저 코드가 돌아서 메모리에 올라가서 실제 접근할 수 있게 될 때의 녀석을 메모리라 한다.

Abstract class vs Interface vs Protocol

자바의 추상 클래스와 인터페이스

Swift 프로토콜(protocol)과 Java 인터페이스(interface) 차이

각각 기능들에 대한 특징을 나열해 가며 실제 목적과 쓰임의 차이에 대해서 알아보고자 한다.

Abstract Class (Java)

  1. 미완성의 클래스 > 스스로 인스턴스가 될 수 없다.
  2. 추상메소드와 일반메소드를 가질 수 있다. (추상 메소드는 이름만 정의가 되어있는 메소드)
  3. 자바의 특징으로 다중상속이 불가능하다.

위의 특징들로 보아 추상 클래스의 목적은 공통 또는 반복되는 메소드나 변수들을 기반으로 반복되는 코드들을 줄일 수 있으며, 어느정도의 기능을 보장한다.

그러면서 클래스의 한 종류로 상속을 통해 기능의 확장이 가능하다.

또한 수평적인 확장보단 수직적인 확장에 집중되어 있는 클래스이다.

Interface (Java)

  1. 역시 미완성이며 스스로 인스턴스가 될 수 없다.
  2. 추상메소드만을 가질 수 있다. > 구현에 대한 제한이 더 강력해졌다. 하지만 기본적인 디폴트 값등은 구현할 수 있다.
  3. 한개의 클래스에서 여러개의 Inteface를 Implementation 할 수 있다.

한 클래스가 인터페이스를 따르면서 해당 기능을 수행하는 것에 대해서 보장을 한다.

구현은 어렵게 해두었다. 추상클래스와 비교해서 상속을 해 수직적인 확장을 이루는 것이 아니라 해당 인터페이스를 따라 기능을 보장하며 다중적인 인터페이스 따름이 가능해 수평적인 기능의 확장을 목표로 한다.

Protocol (Swift)

  1. 이 역시 미완성 스스로 인스턴스 불가능.
  2. 다중 Interface implementation이 가능하다.
  3. Property의 기본값을 가질 수 없다.
  4. optional 한 function을 선언 할 수 있다.
  5. 단순히 Class만 따를 수 있는 것이 아니라. struct, enum에 까지 프로토콜을 따를 수 있다.

전체적인 컨셉과 목적은 Interface와 유사하지만 크게 다른점은 두가지가 있는 것 같다.

Interface에선 그래도 어느정도의 구현은 가능하지만 Protocol은 어떠한 구현도 불가능하다는 것.

Class 뿐만 아니라 열거형, 구조체에서도 프로토콜을 따르게 하면서 더 넓은 사용성을 가졌다는 것.

위와 같은 차이가 있으며, Swift는 기존의 다른 언어들을 통해 보여졌던 인터페이스와 같은 기능들을 조금 더 극대화 시켜 강한 추상화와 그 추상화에 대한 기능확장(struct, enum)을 통해 코드 전반적에서 안정성을 확보하려 했던 것 같다. (Swift 에서 추구하는 Safety를 여기서도 볼 수 있는 것 같은 느낌?, 아직 확실치는 않다...)

Delegation Pattern

리드가 피드백을 준대로 처음 OOP를 접했을 때, 코드를 줄이거나 재사용을 위해 사용하는 기능으로 상속을 쉽게 선택하곤 한다.

그러나 이번 Clean Architecture를 공부하면서도 배웠겠지만 상속이나 직접적으로 객체를 참조하는 것은 강한 묶임/의존이 된다.

강하게 묶이는 것은 어느 한 객체의 변화에 다른 객체에 큰 영향을 미침을 의미하며 이는 곧 위험이다.

이를 해결하기 위해 DIP(의존성 역전 법칙)으로 배웠으며, 이를 디자인 패턴으로 구현한게 Delegation Pattern 이라고 보여진다.

Swift에서는 iOS Framework에서도 대부분의 기능확장을 Delegate로 구현이 되어 있으며, 알던 모르던 이미 본인은 많은 상황에서 Delegation pattern으로 앱을 만들고 있었을 것이다.

또 위에서 다룬 Protocol을 지원해 쉽게 설계가 가능할 수 있도록 했다.

내가 자주 Delegate를 사용하는 예시로 사용 예시를 보자.

특정 월을 선택하는 월을 이전 ViewController에게 전달하는 연/월 피커이다.

//ViewController간 데이터 전달 기능을 전달할 때
//해당 기능은 특정 월을 선택하는 월을 이전 ViewController에게 전달하는 연/월 피커이다.
protocol SelectMonthViewControllerDelegate: class {
    func selectMonthController(_ didSelectMonth: Date)
}

//Action을 하는 UIViewcontroller
class SelectMonthViewController: UIViewController {
		//기능을 하는 객체가 약한 참조로 구현을 한다.
		//delegate는 서로를 호출하는 경우가 많아 상황에따라 weak을 사용해주면 된다.
		weak var delegate: SelectMonthViewControllerDelegate

		func tappedItem(_ date: Date) {
				//delegate의 Action을 실행해준다.
				self.delegate?.selectMonthController(date)
				self.dismiss(animated: true, completion: nil)
		}
}

//Action을 받는 ViewController
class MainViewController: UIViewController {
		private func presentCalendar() {
        let calendar = SelectMonthViewController()
				calendar.delegate = self
        self.present(calendar, animated: false, completion: nil)
    }
}

extension MainViewController: SelectMonthViewControllerDelegate {
		func selectMonthController(_ didSelectMonth: Date) {
				self.viewModel.selectedMonthDate.accept(didSelectMonth)
		}
}

위와 같은 상황을 볼 수 있다.

위의 구조를 그림으로 보게되면 아래와 같이 된다.

뭐, 이런 패턴이 없다면 직접 MainViewController 를 SelectMonthViewController()내의 변수로 만들어 실행을 해둬도 되겠지만 더 Safety하고 유지보수성을 좋게 하기 위한 한가지의 디자인 컨셉인 것 같다.

iOS에선 TableView나 많은 Apple의 API에서 위의 패턴으로 구현이 되어있고 왜 그렇게 구현했는지에 대해서도 알고 있으면 좋으니까 오늘 내용을 다루어 봤다.

profile
애플을 좋아한다. 그래서 iOS 개발을 한다. @Kurly

0개의 댓글