[OOP] 객체 지향 프로그래밍의 3요소

dEpayse
15 min readOct 31, 2022

--

이번 포스트에서는 객체 지향 프로그래밍이 무엇인지 알아보고, 객체 지향 프로그래밍의 3요소라고 불리는 것은 어떤 것인지 알아보려고 한다. 그럼 우선 객체 지향 프로그래밍이란 무엇일까?

객체 지향 프로그래밍이란?

객체 지향 프로그래밍은 공통의 상태와 행위를 갖는 데이터를 묶어서 정의하고, 그 정의된 내용을 ‘객체’라는 매개체를 생성하여 사용할 수 있게 하여 그 객체들간 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다. 프로그래밍을 어떻게 바라보고 어떤 관점으로 코드를 작성할 것인지에 대한 일종의 패러다임이라고 보면 된다.

최대한 풀어서 설명했지만, 처음 접했다면 잘 와 닿지는 않을 것이다. 사실 프로그래밍 자체를 처음 접한다면 객체 지향 프로그래밍이라는 패러다임이 왜 필요한지도 와 닿지 않을 것이다.

하나의 예제로 객체 지향의 개념과 필요성에 대해서 살펴보려고 한다.

텔레비전과 스피커가 있는데, 텔레비전과 스피커를 제어하는 소프트웨어를 만들어보려고 한다. 만약 객체 지향의 개념을 모른다면, 알고 있는 기본형 변수들로 프로그래밍을 시도해볼 것이다.

// Ex1. 티비와 스피커 제어하기  var isTvOn = false
var isSpeakerOn = false
fun main() {
// Tv 켜기
turnOnTv()
// 스피커 켜기
turnOnSpeaker()
// Tv 끄기
turnOffTv()
// 스피커 끄기
turnOffSpeaker()
}
fun turnOnTv() {
isTvOn = true
}
fun turnOffTv() {
isTvOn = false
}
fun turnOnSpeaker() {
isSpeakerOn = true
}
fun turnOffSpeaker() {
isSpeakerOn = false
}

티비, 스피커의 상태를 갖고 있는 변수를 만들고, 티비, 스피커의 상태를 제어하기 위해 함수를 생성하여 main 함수에서 티비를 켜거나 끄고 싶을 때 혹은 스피커를 켜거나 끄고 싶을 때 함수를 사용하여 제어할 수 있다.

그런데 만약 아래와 같은 문제가 생긴다면, 계속 코드를 바꿔주고 유지하는데 어려움이 있을 수 있다.

  • 나에게 티비, 스피커 뿐만 아니라 노트북, 추가적인 모니터, 충전기, 조명 등이 추가된다면 이 제품들의 전원 상태를 저장할 변수와 제어할 함수를 전부 추가해줘야 한다.
  • 그리고 만약 켜고 끄는 것이 아닌 절전이라는 상태가 필요하다면, 티비, 스피커, 노트북, 추가적인 모니터, 충전기, 조명 모두 함수를 추가해줘야하고, 상태도 true, false 로는 처리할 수 없으므로 전부 바꿔줘야 한다.

이러한 문제들을 객체 지향적으로 설계하여 변경에 유연하고 유지 및 보수가 용이하도록 작성할 수 있다. 아래는 객체 지향 프로그래밍의 특징을 보여주기 위한 예시로, 오류가 발생할 수 있는 모든 조건은 고려하지 않았다. 또 객체 지향의 좀 더 자세한 특징은 더 아랫부분인 3요소에서 다룰 예정이므로, 이런 식으로 코드를 작성할 수 있다는 것만 이해하자.

먼저 티비와 스피커는 전자기기라는 공통점이 있고, 전원을 켜고 끌 수 있다.

// Ex2. 추상화하기abstract class ElectronicDevice {
private var isOn = false
fun turnOn() {
isOn = true
}
fun turnOff() {
isOn = false
}
}

위와 같이 핵심적인 개념 또는 기능을 간추려 내는 것(‘전자기기’라는 것의 핵심적인 개념과 기능을 간추렸다.)을 추상화라고 한다.

그러나 티비와 스피커는 분명히 다른점이 있다. 티비는 화면 밝기를 나타낼 수 있지만, 스피커는 그럴 수 없고, 스피커만 저음을 더 강조해주는 효과를 추가적으로 가질 수 있다.

// Ex3. 상속 받기class Television: ElectronicDevice() {
private var lightness = 80
fun increaseLightness10() {
if(lightness <= 90) lightness += 10
}
fun decreaseLightness10() {
if(lightness >= 10) lightness -= 10
}
}
class Speaker: ElectronicDevice() {
private var isBaseEffectModeOn = true
fun turnOnBaseEffectMode() {
isBaseEffectModeOn = true
}
fun turnOffBaseEffectMode() {
isBaseEffectModeOn = false
}
}

또 티비와 스피커를 켜고 끄는 것을 제어하는 것을 리모컨이라는 개념으로 묶어볼 수 있다.

//Ex4. 리모컨 클래스 정의하기class RemoteControl {
private val devices = mutableListOf<ElectronicDevice>()
fun registerDevice(electronicDevice: ElectronicDevice) {
devices.add(electronicDevice)
}
fun switchOn(pos: Int) { devices[pos].turnOn() }
fun switchOff(pos: Int) { devices[pos].turnOff() }
}

이제 main 함수에서 만든 클래스들을 객체화하여 제어할 수 있다.

// Ex5. 객체 지향 프로그래밍을 통해 티비와 스피커 제어하기fun main() {
val tv = Television()
val speaker = Speaker()
val remoteControl = RemoteControl()
remoteControl.apply {
register(tv)
register(speaker)
}
// Tv 켜기
remoteControl.switchOn(0)
// 스피커 켜기
remoteControl.switchOn(1)

// Tv 끄기
remoteControl.switchOff(0)
// 스피커 끄기
remoteControl.switchOff(1)
}

이제 객체 지향적으로 설계하지 않았을 때 발생할 수 있을 만한 어려움에 대해 다시 생각해보자.

  • 전자 기기가 늘어난다 : 새로운 기기를 전자기기를 상속하여 만들고, remoteControl 에 등록하면 쉽게 조정할 수 있다. 전자기기가 늘어날 때마다 함수를 작성하고, 함수 이름을 다르게 해줄 필요가 없다.
  • 켜고 끄는 상태가 바뀐다 : ElectronicDevice 클래스에서만 필드를 수정하고, 그것을 제어하는 RemoteControl 클래스에서 함수를 수정하면 된다. 각각의 모든 클래스에서 일일이 필드를 바꿔줄 필요가 없다.

이렇듯 객체 지향적으로 설계하고 프로그램을 짰을 때 유지 및 보수 관점에서 굉장한 이점이 생긴다.

객체 지향 프로그래밍의 3요소

객체 지향의 3요소를 알아보기 전에, 먼저 클래스와 객체가 무엇인지 잘 모른다면, 아래 링크를 통해 그 개념에 대해 알고 시작하자.

1. 캡슐화(encapsulation)

캡슐화는 객체 지향 프로그래밍에서 2가지 측면이 있다.

  • 객체의 속성(data fields)과 행위(메서드, methods) 를 하나로 묶는다.
  • 보여주어야 할 내용만 보여주고, 그렇지 않아도 되는 것은 감춘다.

위의 예제에서 다뤘던 전자기기 클래스를 다시 한 번 살펴보자.

// Ex2. 추상화하기abstract class ElectronicDevice {
private var isOn = false
fun turnOn() {
isOn = true
}
fun turnOff() {
isOn = false
}
}

우리는 티비와 스피커가 전자기기라는 공통점, 그리고 전자기기가 켜고 끌 수 있는 상태를 갖고 있다는 점에 착안해서 ElectronicDevice 라는 클래스를 생성했다. 그리고 전원 상태를 나타내는 필드를 생성하고 전원을 켜는 함수, 전원을 끄는 함수를 만들었다. 이렇게 공통된 속성과 행위를 묶는 것이 캡슐화의 첫번째 특징이다.

또한 ElectronicDevice 클래스 내부의 isOn 필드의 앞에 키워드를 보면 private 이라고 되어있는데, 이 키워드가 ElectronicDevice 내에서만 isOn 필드를 사용할 수 있고, 외부에서는 isOn 필드를 사용할 수 없게 만든다. 그러나 turnOn(), turnOff() 함수는 외부에서도 사용할 수 있는 public (kotlin 언어의 가시성은 public이 default이다.) 이기 때문에 전자기기를 켜고 끌 수 있는 기능을 충분히 수행할 수 있다.

이렇게 만들지 않고 isOn 필드를 public 으로 하고, 외부에서 직접 이 값을 true, false 로 설정해주면 더 간단할 것 같다. 그러나 값이 true 혹은 false 값만 갖는 Boolean 이 아니고 Int 등인 경우 외부에서 필드 값을 직접 지정하게 하면 예상치 못한 흐름이 생길 수 있기 때문에, 캡슐화를 통해 보여주고 싶은 것만 보여주는 것이 더 안전한 방법이라고 볼 수 있다. 이런 정보 은닉이 캡슐화의 두 번째 특징이다.

Television, Speaker 역시 캡슐화가 되어 있는 것을 위의 예제에서 확인할 수 있다.

2. 상속(inheritance)

상속은 객체들 간의 관계를 구축하는 방법이다. 상위 클래스와 하위 클래스의 관계가 기본이고, 하위 클래스는 상위 클래스의 속성과 동작을 포함한다. 이런 상속 관계를 이용하는 장점은 재사용성과 확장성에 있다.

(상속에 관한 더 자세한 내용과 Kotlin 언어로 상속을 사용하는 더 자세한 방법은 아래 링크를 참고하자.)

다시 본 포스트의 예제로 돌아와서 상속의 특징을 살펴보자.

// Ex6. 상속 활용하기
abstract class ElectronicDevice {
private var isOn = false
fun turnOn() {
isOn = true
}
fun turnOff() {
isOn = false
}
}
// ElectronicDevice 를 상속받음
class Television: ElectronicDevice() {
private var lightness = 80
fun increaseLightness10() {
if(lightness <= 90) lightness += 10
}
fun decreaseLightness10() {
if(lightness >= 10) lightness -= 10
}
}
// ElectronicDevice 를 상속받음
class Speaker: ElectronicDevice() {
private var isBaseEffectModeOn = true
fun turnOnBaseEffectMode() {
isBaseEffectModeOn = true
}
fun turnOffBaseEffectMode() {
isBaseEffectModeOn = false
}
}

Television 과 Speaker 클래스는 ElectronicDevice 클래스를 상속받아, ElectronicDevice 의 하위클래스이다. 반대로 ElectronicDevice 는 두 클래스의 상위 클래스이다. 여기서 상속의 장점은, Television 과 Speaker 클래스는 ElectronicDevice 의 속성과 동작을 그대로 물려받는다는 것이고, 둘 다 ElectronicDevice 타입이라는 것이다. 그러나 캡슐화의 특성에 따라 ElectronicDevice 클래스의 private 한 속성은 Television 과 Speaker 클래스에서도 사용할 수 없다.

이런 상속의 특성은 공통점이 있는 특성들을 묶어 상속 관계를 만든다면 같은 코드를 여러번 작성하지 않아도 된다는 것이다. 예를 들면 Television 클래스의 객체는 increaseLightness10(), decreaseLightness10() 뿐만 아니라 ElectronicDevice 의 public 메서드인 turnOn(), turnOff() 도 사용가능하고, Speaker 클래스 역시 ElectronicDevice 의 메서드를 사용가능하다.

3. 다형성(polymorphism)

다형성은 프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메서드 등)이 다양한 자료형에 속하는 것이 허가되는 성질을 말한다. Kotlin 에서 지원하는 다형성은 Method Overloading, Method Overriding, Operator Overloading, Subtype Polymorphism등이 있는데, 객체 지향에서 의미가 큰 다형성은 Method Overriding 과 Polymorphic Subtypes 가 있다. 본 포스트에서는 두 가지만 다뤄보려고 한다.

1. Method Overriding

상속에서 상위 클래스가 갖는 함수를 하위 클래스에서 재정의할 수 있는 개념이다. 같은 이름을 갖는 메서드가 여러 형태에 속하므로 다형성을 띈다고 볼 수 있다.

위의 전자기기 예시를 확장해보면, 상위 클래스인 Electronic Device 의 메서드인 turnOn() 과 turnOff() 메서드를 하위 클래스에서 다른 방식으로 동작할 수 있게 재정의할 수 있다는 것이다. 하위 클래스의 객체가 이 메서드를 사용할 때는 재정의한 코드로 동작한다.

Kotlin 으로 예시를 살펴보자.

// Ex7. Method Overridingabstract class ElectronicDevice {
private var isOn = false
fun turnOn() {
isOn = true
}
fun turnOff() {
isOn = false
}
}
// ElectronicDevice 를 상속받음
class Television: ElectronicDevice() {
private var lightness = 80
fun increaseLightness10() {
if(lightness <= 90) lightness += 10
}
fun decreaseLightness10() {
if(lightness >= 10) lightness -= 10
}

// ElectronicDevice 의 함수를 재정의함
override fun turnOff() {
super.turnOff()
lightness = 0
}
}

2. Polymorphic Subtypes

보통 상속 관계를 is-a 관계라고 하는데, 하위 클래스는 상위 클래스 타입임을 의미한다. 즉 상위 클래스 타입의 변수에 하위 클래스의 객체의 레퍼런스를 저장할 수 있다. 그러나 이 경우에는 타입 캐스팅을 하지 않는 이상 상위 클래스의 프로퍼티 혹은 메서드만 사용할 수 있다.

// Ex8. Polymorphic Subtypes
fun main() {
// ElectronicDevice 를 저장하는 리스트인데
// Television, Speaker 객체를 넣을 수 있다.
val edList : List<ElectronicDevice>
= listOf(Television(), Speaker())
val remoteControl = RemoteControl() remoteControl.apply {
edList.forEach {
register(it)
}
}
}

위의 예시를 보면, ElectronicDevice 객체들을 저장하는 리스트를 만듦으로써 remoteControl 에 객체들을 등록할 때 훨씬 유지 보수가 편해진 것을 볼 수 있다. 예를 들어 전자기기가 추가되는 상황이 생긴다면, remoteControl 에 전자기기를 등록할 때 일일이 추가해줘야하는 것이 아니고 edList 에만 추가해주면 되는 것을 볼 수 있다.

Reference

  1. [위키백과] “객체 지향 프로그래밍” — https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D
  2. [jacob0122] “객체 지향 프로그래밍이란?” — https://velog.io/@jacob0122/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80
  3. [Stranger’s lab] “객체지향(OOP)과 절차적 프로그래밍(PP)” — https://st-lab.tistory.com/151
  4. [위키백과] “캡슐화” — https://ko.wikipedia.org/wiki/%EC%BA%A1%EC%8A%90%ED%99%94
  5. [위키백과] “상속 (객체 지향 프로그래밍)” — https://ko.wikipedia.org/wiki/%EC%83%81%EC%86%8D_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)
  6. [위키백과] “다형성 (컴퓨터 과학)" — https://ko.wikipedia.org/wiki/%EB%8B%A4%ED%98%95%EC%84%B1_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)
  7. [Baeldung] “Polymorphism In Java” — https://www.baeldung.com/java-polymorphism

--

--

dEpayse
dEpayse

Written by dEpayse

나뿐만 아니라 다른 사람들도 이해할 수 있도록 작성하는, 친절한 블로그를 목표로.

No responses yet