객체 지향 프로그래밍적 사고에 대해서 공부를 했으니 이제 객체지향 프로그래밍의 4가지 특징에 대해서 알아보겠습니다!
객체 지향 프로그래밍을 한마디로 표현한다면 실제 세계를 코드로 모델링하여 문제를 해결하는 것 이라고 했습니다. 이 아이디어를 토대로 앞으로 개발할 때 아래의 특징을 설계에 녹여내야합니다.
결국 이런 것들에 대해서 공부하고 생각하는 이유는, Swift로 (저는 현제 iOS 개발자 이니까) 개발에 객체 지향적 사고를 더 잘 녹여내고 클린 아키텍쳐의 베이스를 잘 다져 좋은 코드를 작성하기 위합니다!
이것들을 염두에 두고 객체 지향 프로그래밍의 4가지 특징에 대해 알아보겠습니다!
📌 객체 지향 프로그래밍의 특징은 다음과 같습니다
- 추상화
- 상속
- 다형성
- 캡슐화
추상화 ( Abstraction )
어렵게 들리겠지만 추상화는 불필요한 ㅂ정보는 제거하고 본질적이고 공통적인 부분만 추출하여 표현하는 것입니다.Swift의 protocol이나 Java의 interface 는 구체적 구현없이 다양한 클래스에서 공통된 기능을 정의할 수 있습니다.
protocol Animal {
func sound() -> String
}
class Dog: Animal {
func sound() -> String {
return "Bark"
}
}
class Cat: Animal {
func sound() -> String {
return "Meow"
}
}
func animalSound(animal: Animal) {
print(animal.sound())
}
let myDog = Dog()
let myCat = Cat()
animalSound(animal: myDog) // "Bark"
animalSound(animal: myCat) // "Meow"
Animal 이라는 프로토콜을 사용해 추상화 된 동물의 소리기능을 정의하고 Dog, Cat 클래스가 그 기능을 구현합니다.
추상화는 제어 추상화와 데이터 추상화로 나눌 수 있습니다.
- 제어 추상화 ( Control Abstraction )
어렵게 들리겠지만 추상화는 불필요한 ㅂ정보는 제거하고 본질적이고 공통적인 부분만 추출하여 표현하는 것입니다. Swift의 protocol이나 Java의 interface 는 구체적 구현없이 다양한 클래스에서 공통된 기능을 정의할 수 있습니다.
예를들어, 우리가 게임을 할 때 게임의 조작법에 대해서만 신경쓰면되고, 실제로 게임의 프로그래밍이 어떻게 되어 있는지, 맵을 그리는 내부로직이 어떻게 되어 있는지는 알 필요가 없습니다.
게임을 하는데에는 이런것들을 몰라도 지장이 없기 때문입니다. 만약 이런것들을 다 알아야 게임을 할 수 있다면, 난이도가 올라가고 게임을 하는 사람들이 적어져서 게임 산업이 지금처럼 발전하기 어려웠을 것 입니다.
비유를 들어 설명했지만 이는 프로그래밍 시 빠르게 설계하고 쉽게 구현하기 위해 추상화를 하는 이유입니다. Swift에서 delegate 패턴은 제어 추상화의 좋은 예 입니다. 특정기능을 protocol에서 선언하고 이를 다른 객체에 위임하여 복잡한 흐름을 단순화합니다.
제어 추상화는 어떤 클래스의 메소드를 사용하는 당사자에게 메소드의 내부로직을 숨기는 것을 말합니다. - 데이터 추상화 ( Data Abstractino )
데이터 추상화는 데이터의 복잡성을 숨기고 대상을 간단한 개념으로 일반화하는 것을 말합니다.
예를 들어 아이폰, 안드로이드폰, 블랙베리 가 있을 때 이들을 공통 특징인 휴대폰으로 묶어 이름을 붙이는 것을 데이터 추상화라고 생각하면 됩니다. 이런 데이터 추상화를 통해 데이터의 내부 구조를 숨기고 공통된 특징만 남게 됩니다.
데이터 추상화는 데이터의 복잡성을 숨기과 대상을 간단한 개념으로 일반화 하는 것을 말합니다.
protocol 전자제품 {
func powerOnOff()
}
protocol 통신기기: 전자제품 {
func phoneCall()
}
protocol 휴대폰: 통신기기 {
func camera()
func game()
}
class 아이폰: 휴대폰 {
func powerOnOff() { ... }
func phoneCall() { ... }
func camera() { ... }
func game() { ... }
func appleLinker() { ... }
}
이는 데이터의 추상화를 쉽게 이해하기 위해 작성한 간단한 예시입니다.
아이폰 자체는 종류가 다양하기 떄문에 아이폰에 대한 공통기능을 이렇게 추상화해두면 다음에 나올 상속을 통해서 구조를 쉽게 확장할 수 있다.
상속 ( Inheritance )
상속은 말 그대로 자식 클래스가 부모 클래스의 속성 및 메서드를 물려받아 사용하는 것을 의미합니다.
Swift에서는 클래스 간 상속을 통해 코드 재사용성을 높일 수 있습니다.
위에서 설명한 아이폰 예시에서 추상화 객체들을 서로 연결시켰는데 이것이 상속입니다.
- 상위 클래스의 속성 ( 변수 ), 기능( 메소드 )를 상속하여 하위클래스가 전부 물려받게 됩니다.
class Vehicle {
var speed: Int = 0
func description() -> String {
return "Traveling at \\(speed) km/h"
}
}
class Car: Vehicle {
var hasAirConditioning: Bool = true
}
let myCar = Car()
myCar.speed = 120
print(myCar.description()) // "Traveling at 120 km/h"
이 처럼 Car 클래스에서 decription함수를 정의하지 않아도 상위 클래스인 Vehicle 클래스의 메소드와 변수를 사용할 수 있습니다.
- 상속해주는 상위클래스 : super / parent
- 상속받는 하위 클래스 : sub / child
다형성 ( Polymorphism )
다형성의 사전적 정의는 ‘같은 종의 생물이면서도 어떤 형태나 형질이 다양하게 나타나는 형상’ 입니다.
프로그래밍에서의 다형성 또한 같은 메서드가 다양한 형태로 동작할 수 있는 능력을 말합니다.
즉, 같은 자료형에 여러가지 타입의 데이터를 대입하여 다양한 결과를 만들어 낼 수 있는 것이죠.
클래스를 상속 시 오버라이딩과 오버로딩을 통해서 다형성을 발현할 수 있습니다.
- 오버라이딩
- 부모 클래스의 매서드를 자식에서 재정의하여 동작을 변경하는 것입니다.
class Parent { func greet() { print("Hello from Parent") } } class Child: Parent { override func greet() { print("Hello from Child") } } let child = Child() child.greet() // "Hello from Child"
- 오버로딩
- 같은 이름의 매서드를 동일하게 유지하면서 파라미터의 갯수, 타입에 따라 여러개 생성해서 사용할 수 있습니다.
class Calculator { func add(a: Int, b: Int) -> Int { return a + b } func add(a: Double, b: Double) -> Double { return a + b } func add(a: Int, b: Int, c: Int) -> Int { return a + b + c } } let calculator = Calculator() print(calculator.add(a: 2, b: 3)) // 5 print(calculator.add(a: 2.5, b: 3.5)) // 6.0 print(calculator.add(a: 1, b: 2, c: 3)) // 6
[정적 타이빙과 동적 타이핑]
- 타이핑이란 값의 형식을 지정하거나 확인하는 것을 뜻합니다.
- 컴파일 타입에 값의 형식을 파악하는 것이 정적타이핑, 런타임에 값의 형식을 파악하는 것이 동적 타이핑입니다.
- 정적타이핑은 컴파일 타임에 오류를 발견할 수 있다는 큰 장점이 있습니다.
- 동적타이핑은 코드를 유연하게 짤 수 있지만 런타임에러가 발생할 수 있다는 단점이 있습니다.
[Any, AnyObject]
실제로 코딩을 하다보면 protocol을 사용할 떄 Anyobject를 상속받아서 사용하는 경우가 많습니다. 잠깐 Any와 AnyObject에 대해서 설명하고 넘어가자면
- Any 값(구조체) / 참조(클래스) 타입을 대표하는 형식이고
- AnyObject 는 모든 참조(클래스)타입을 나타내는 프로토콜입니다.
- 따라서, protocol에 AnyObject를 상속받으면 구조체가 아닌 클래스의 인스턴스로만 제한할 수 있습니다.
캡슐화 ( Encapsulation )
캡슐화란 실제 우리가 먹는 캡슐알약을 떠올리면 이해하기 쉽습니다.
약 자체는 가루지만 이것을 캡슐로 감싸면서 복용하기 편하게 만들고, 복잡한 내용물을 은닉할 수 있습니다.
이처럼 캡슐화는 객체의 속성(데이터)과 메소드를 외부에서 직접 접근하지 못하도록 보호하고, 필요한 경우에만 제한된 방법으로 접근할 수 있게 만드는 개념입니다.
Swift의 경우 private( 특정 객체에서만 사용할 수 있도록 제어 ) , fileprivate ( source file 내에서만 접근할 수 있도록 제한 )을 사용하여 구현할 수 있습니다.
class Wallet {
private var cash: Double = 0.0
// 돈을 지갑에 넣음
func addMoney(amount: Double) {
if amount > 0 {
cash += amount
print("Add \(amount)")
} else {
print("Cannot Add")
}
}
// 지갑에서 돈을 꺼냄
func spendMoney(amount: Double) -> Bool {
if amount > 0 && amount <= cash {
cash -= amount
print("Spent \(amount)")
return true
} else if amount > cash {
print("Not enough money")
return false
} else {
print("Cannot spend")
return false
}
}
// 현재 지갑에 있는 금액 가져오기
func checkBalance() -> Double {
return cash
}
}
let myWallet = Wallet()
myWallet.addMoney(amount: 100.0)
print(myWallet.checkBalance()) // 100.0
myWallet.spendMoney(amount: 50.0)
print(myWallet.checkBalance()) // 50.0
여기까지 객체지향 프로그래밍의 특징 4가지를 모두 살펴보았습니다! 설계가 아니라 사용법을 배우면서 눈에는 익숙했던 것들에 대한 개념이 조금씩 잡혀가는 것 같습니다. 더 많은 코드를 보고 더 좋은 코드를 짤 수 있도록 계속해서 공부해 봅시다!
참조
inpa Dev
'iOS' 카테고리의 다른 글
[iOS] Fastlane을 사용한 In House(ipa) CI/CD 구축 (1) | 2024.11.06 |
---|---|
동기(sync)/비동기(Async) & 블로킹(blocking)/논블로킹(Non-blocking) (0) | 2024.08.22 |
객체지향 프로그래밍적 사고 - 2 (0) | 2024.08.16 |
객체지향 프로그래밍적 사고 (0) | 2024.08.13 |
.DS_Store 파일이란? (0) | 2024.04.16 |