Java를 처음 공부한 대학교 2학년 때 처음 들었던 객체지향을 한 번쯤 정리해보고 싶었다. 그러다 문득 오늘 아침에 정리를 하고싶어서 글을 쓰는데 어떻게 써야할지 대략적으로 난감하다. 뻘소리지만 한 번쯤 읽어주고, 아니다 싶으면 과감하고 휘향찬란하게 저를 가르쳐주세요.... kgo0748@gmail.com로 메일주세요 훟힣훟헿
OOP가 뭐지.... 뭘까.... 뭐라고 말하면 좋을까....
분야를 막론하고 공부를 할 때 항상 예시를 통하여 그 개념을 이해하려는 습관이 있다. 그래서 처음 OOP를 배울때 들었던 정의보다 이해하기 위해 생각한 예시를 들어보겠다.
우리가 흔히 사용하는 리모콘과 TV로 예시로 들겠다. 그리고 그 리모콘을 하나의 객체라고 생각해보자. 그 리모콘에는 TV의 전원을 On/Off 할 수 있는 기능, 0~9까지 번호를 누를 수 있는 기능을 하는 버튼, 이전 채널로 갈 수 있는 기능을 하는 버튼 등이 있다.
다른 한쪽에는 TV라는 객체가 있다고 가정하자. 이 TV에는 전원의 On/Off 를 판별하는 기능, 리모콘에서 입력한 번호 데이터를 넘겨받아 채널을 바꿔주는 기능, TV 전원을 Off하기 전 현재 채널을 기억하는 기능 등이 있다.
여기서 주목할 점은 리모콘이라는 객체와 TV라는 객체는 각 객체로서 자신의 역할을 수행하기 위한 데이터와 데이터의 상태, 행위를 갖고있다. 그리고 리모콘으로 TV의 채널을 변경하기 위해 번호를 누르고, 전원을 On/Off하기 위해 전원 버튼을 눌러서 TV와 상호작용을 한다.
이제 이 내용을 전문적으로 정의하자면, 객체 지향 프로그래밍은 컴퓨터 프로그래밍의 패러다임 중 하나로서, 객체에 필요한 데이터를 추상화시켜 상태와 행위를 갖게 만들고, 만들어 낸 각 객체들 간 메시지를 주고받아 데이터를 처리할 수 있게 로직을 구성하는 프로그래밍의 방법 중 하나이다.
그럼 왜 쓰는 것일까
특정 개념이 나타나면 그 개념을 왜 쓰는지에 대한 이유가 가장 중요하다. OOP란 개념이 생긴 이유가 있으면, 그에 따른 결과도 있기 마련이니까.
코드 재사용 용이
위에 예시에서 언급한 리모콘 이라는 객체를 연장선으로 끌고 가보자. 리모콘은 TV에서 쓸 수 있지만, 약간의 변형을 주면 TV 리모콘, 에어컨 리모콘, 선풍기 리모콘 등 많은 객체의 리모콘이 될 수 있다. 이렇듯 리모콘이라는 객체를 재사용해 TV 리모콘, 에어컨 리모콘, 선풍기 리모콘으로 만들 수 있기 때문에 코드 재사용이 용이하다.
간편해지는 유지보수
상대적으로 간편해지는 유지보수는 절차지향 프로그래밍과 비교하면 쉽게 이해가 된다. 절차지향적으로 프로그래밍을 한 경우에는 에러가 발생하면 대략적으로 난감해진다. 일일이 찾아서 수정해야 하는 수고로움이 동반되기 때문이다. 반면에 객체 지향 프로그래밍에서는 에러가 발생한다면 클래스 내부의 멤버 변수 혹은 메소드에서 에러가 발생되기 때문에 에러를 찾기 용이해지고, 유지보수가 간편해지게 된다.
대형 프로젝트에 적합한 프로그래밍
대형 프로젝트에는 만들어야 할 기능도 많고 만든 기능들을 다시 쓰는 경우도 많다. 그렇다면 잘 만들어진 클래스를 모듈화 시켜서 필요할 때 꺼내 쓸 수 있는 객체지향 프로그래밍은 여러 사람들 나아가 여러 회사들이 개발에 필요한 업무 분담이 용이해진다.
그래요. 알겠어요. 그럼 뭘 알아야 하나요
객체지향과 관련해서는 필수적으로 알아두었으면 하는 5가지가 있다. 이것들에 대해서 알아보자
1. 클래스, 인스턴스(객체)
클래스를 하나의 큰 박스라고 생각하자. 그리고 박스에 내용물을 집어 넣는데, 이때 집어넣는 내용물은 상태라고 불리는 멤버변수와 행위라고 불리는 메소드로 구성된다. 좀 더 그럴싸하게 말하자면, 객체를 정의할 수 있는 속성과 행위를 변수와 메소드 화 시킨것이다.
class waterBottle(@NonNull waterAmount: Double) {
private var waterAmount by Delegates.notNull<Double>()
private var waterBottleSize = 10
init {
this.waterAmount = waterAmount
}
fun getLeftOverWaterMount(): Double {
return this.waterAmount
}
fun drinkWater(drinkMount: Double) {
if (drinkMount <= this.waterAmount) {
this.waterAmount = this.waterAmount - drinkMount
}
}
fun addMoreWater(waterMount: Double) {
if (this.waterAmount + waterMount <= waterBottleSize) {
this.waterAmount += waterMount
}
}
}
리모콘 예시는 지겨워서 물병 예시를 들었다. 물병의 사이즈를 알려주는 waterBottleSize와 물병에 들어있는 물의 양인 waterAmount 멤버변수가 있다. 여기에 남은 물의 양 확인하고, 물을 마시고, 물을 더 넣는 행위를 할 수 있는 각각의 메소드들이 있다. 그리고 이 내용물들을 담을 수 있는 waterBottle을 클래스라고 하는 것이다.
예시가 옳바르지 않을 수 있지만, 이해하기 편하게 하기 위하여 들은 예시니까 그러려니 하고 넘어가준다.
이렇게 열심히 만든 waterBottle 클래스들은 정의만 해두면 아무 의미도 없다. 이 클래스들을 메모리에 할당을 해줘야 비로소 이 waterBottle 클래스들은 사용이 될 수 있는것이다. 그리고 이러한 클래스를 실제 메모리상에 할당하여 사용되는 데이터를 우리는 인스턴스(객체)라고 부른다.
2. 캡슐화 (Encapsulation)
캡슐화의 핵심은 서로 연관성이 있는 멤버변수, 메소드를 하나의 클래스로 묶어주는 것이다.
그리고 이러한 캡슐화의 중요 목적은 바로 정보은닉이다. 이 정보은닉은 클래스 선언 시 혹은 멤버변수 선언 시 private를 이용하여 클래스를 숨기거나 멤버변수를 숨기는 것이다. 이러한 정보은닉이 필요한 이유는 데이터를 보호하기 위하여 접근을 제한하기 위함이다.
만약 User Information 변수를 선언했는데 private와 반대되는 public으로 변수를 선언하게되면, 누구든 접근해서 User Information을 변경할 수 있기 때문이다. 그래서 불필요한 변수, 메소드, 클래스를 감추기 위하여 private를 사용하는 것이며, 접근이 필요하다면 getter, setter와 같은 메소드를 통해서 간접적으로 접근을 하는 것이다. 그렇다고 getter, setter를 다 만들면 안되는 것이다. 특히 setter는 간접적이지만 접근을 해서 새롭게 설정하는 것이기 때문에 더더욱 조심해야한다.
3. 추상화
추상화는 핵심적인 개념이나 기능만 추려내서 나타내는 것을 말한다.
쉽게 설명하면 삼성 노트북, 맥북, 기가바이트 노트북, ASUS 노트북 등은 전부 노트북이라는 큰 개념에서 뻗어나가는 것들이다. 이 모든 노트북에 대하여 클래스를 만들고 각각의 기능들을 하나하나 다 정의하면 무의미한 노동이 된다. 왜냐하면 노트북이라는 하나의 큰 틀을 만들고 전원버튼, 키보드, 터치패드, 화면 등 필수 공통적인 요소를 정의하고 전원 On/Off, 키보드 입력, 터치패드 입력, 화면 출력과 같은 필수적이면서도 공통적인 기능만 만들면 되기 때문이다. 그리고 여러가지 제조사의 노트북에서 노트북이라는 하나의 큰 틀에서 필수적인 부분만을 표현하는 것이 바로 추상화이다.
4. 상속성과 다형성
기술면접에서 자주 물어보는 질문이 '오버라이딩과 오버로딩 차이점이 뭐에여?' 이다.
Overriding
클래스에서는 부모 자식이라는 개념이 있다. 한 클래스가 부모 클래스라고 가정했을 때, 그 부모 클래스를 상속받은 자식 클래스가 존재한다. 부모 클래스를 상속받은 자식 클래스는 부모 클래스에서 만들어진 메소드를 자신의 입맛대로 다시 재정의해서 사용할 수 있는데, 이것을 Overriding이라고 한다. Overriding을 통한 상속은 부모 클래스의 기능을 가져와 재사용을 할 수 있고 또한 새로운 기능을 추가할 수 있기 때문에 객체지향에서 중요한 개념이다.
Overloading
메소드의 이름이 같다. 하지만 메소드마다 필요한 매개변수의 갯수 혹은 데이터 타입이 다른 경우이다. 이러한 경우를 Overloading이라고 하는 것이다.
이렇게 하나의 변수명 혹은 메소드 명이 있더라도 상황에 따라 다른 의미로 해석이 될 수 있는데, 이것을 다형성이라고 한다.
다형성이 중요한 이유는 복잡성을 줄일 수 있기 때문이다. 점점 많아지는 API는 복잡성이 증가한다. 하지만 같은 이름의 속성, 속성을 사용하기 위한 인터페이스 유지, 나아가 메소드 이름의 유지는 서로 다른 것들을 정의할 필요없이 Overloading하면 되기 때문에 복잡도가 줄어들게 된다. 그렇기 때문에 다형성은 중요하다.
열심히 써놓았는데, 넘나리 힘든것..... 그래도 하나하나 개념을 다져갈 수 있어서 재밌다. 다음 포스팅은 뭘 할지 기대된다.
참고 사이트
'안드로이드 > 알아두기' 카테고리의 다른 글
[안드로이드] Handler와 Looper 에 대하여 (0) | 2021.12.05 |
---|---|
[안드로이드] MVP와 MVVM 패턴에 대하여 (0) | 2021.12.05 |
[안드로이드] 안드로이드 4대 컴포넌트 (0) | 2021.12.04 |
[안드로이드] context에 대하여 (0) | 2021.12.03 |
[안드로이드] 안드로이드 실행환경에 대한 간단한 설명 (0) | 2021.11.29 |