1. 객체지향 프로그래밍
2. 클래스와 객체
1. 객체 지향 프로그래밍
객체지향 프로그래밍 (Object Oriented Programming) 을 줄여서 OOP라고 부릅니다. 한마디로 주의 모든 사물을 뜻하는 객체라는 개념을 프로그래밍에 도입해서 실제 세상에서 사물의 속성과 기능, 사물간의 관계를 컴퓨터 프로그램에서도 사용할 수 있게 프로그래밍하는 방법입니다.
예전에는 컴퓨터 프로그램을 명령어나 실행문, 함수의 집합으로 이해하고 구현하였는데, 그러다 보니, 개발하는 시간도 오래 걸리고, 높은 기술적인 지식도 필요했으며, 유지보수도 어려워 높은 비용(Cost)가 필요했습니다. 이를 개선하고자 객체라는 개념을 도입하여 독립적인 부품형태로 개발하여, 이러한 부품들을 모아 서로 유기적인 관계를 갖는 하나의 소프트웨어로 개발하게 되는 기법이 바로 객체지향 프로그래밍입니다.
객체지향 프로그래밍은 객체(부품)간에 독립적이기 때문에, 수정이나 추가사항에 대한 작업이 수월하고, 객체들간의 관계를 파악하면 되기 때문에 소프트웨어 설계 측면에서도 보다 직관적으로 분석이 가능합니다. 무엇보다 프로그래밍을 수년간 전공지식을 쌓거나, 특별한 능력이 있는 사람만이 하는것이 아닌 초보자들도 쉽게 배울 수 있는 언어로 사람들에게 인식되어진 계기가 되었습니다.
좀더 쉽게 이해하기 위해, 자동차로 비유해볼까요.
자동차는 셀수 없이 많은 부품들이 모여 하나의 완성된 자동차가 됩니다. 초창기에는 이 자동차를 작은 부품들부터 하나하나 새로 만들었을 것입니다. 그만큼 시간도 오래 걸리게 되고, 금액도 비쌌으며, 특정 부분만 교체하거나 업그레이드를 하려면, 만든 당사자가 아닌 이상 쉽지 않았겠죠? 하지만 요즘은 부품들만 따로 미리 만들어 놓고, 자동차 업체는 이 부품들을 조립만해서 자동차를 완성합니다. 생산 시간도 줄일 수 있으며, 부품단위로 교체가 가능해진 것입니다. 예를 들면 바퀴만 따로 갈아 끼울수도 있고, 신차가 나와도 엔진이나 중요부품은 그대로고 외장만 바꿔서 만들 수도 있는 것입니다.
객체 지향 프로그래밍 언어의 특징
* 상속
* 다형성
* 추상화
* 캡슐화
상속 (Inheritance)
상속은 우리가 일상 생활에서 사용하고 있는 상속이라는 말과 비슷합니다. 부모의 재산을 자식에게 물려 주듯이 객체지향 프로그래밍에서도 부모가 가지고 있는 여러 속성과 기능들을 자식이 그대로 물려받을 수 있게 됩니다. 그래서 자식들은 똑같은 코드를 중복해서 작성할 필요가 없으므로, 반복된 코드의 중복을 줄여주게 됩니다. 따라서 개발 시간이나 유지보수 시간을 줄일 수 있게 됩니다.
다형성 (Polymorphsim)
다형성은 한자로 多形性, 많을 다(多), 모양 형(形), 성질 성(性) 입니다. 즉 다양한 모양(타입)을 갖는 성질(특성) 이라고 해석할 수 있겠죠? 결국 다양한 데이터 타입을 갖을 수 있다는 말인데, 부모/자식간의 관계가 있는 클래스는 자식이 부모의 타입으로 변환이 가능합니다.
컴퓨터를 예로 들면 컴퓨터 메인보드에는 그래픽카드를 꽂을 수 있는 슬롯이 있습니다. 최근의 메인보드는 그래픽 카드를 꽂을 수 있는 슬롯은 모양이 다 똑같은 슬롯을 가지고 있습니다. 이 슬롯에 꽂을 수 있는 그래픽 카드를 부품화 하여 여러 제조사의 다양한 그래픽카드를 슬롯에 맞게 구현하면 어떤 그래픽 카드도 다 꽂을 수 있게 됩니다. 이 예시를 추상적으로 개념화 한 것이 바로 다형성입니다.
추상화 (Abstraction)
추상은 뽑을 추(抽)에, 형상 상(象)자 입니다. 모양을 뽑아낸 것(추출)이라고 할 수 있는데, 반대말은 구체화입니다. 추상화는 사물에서 속성이나 특징들을 추출하는 것을 의미합니다. 즉 개발자들이 구현하기 전에 객체들의 특징이나 속성들을 파악하고 설계하는 과정을 말합니다.
캡슐화 (Encapsulation)
캡슐화는 객체의 실제 기능들을 숨겨서 감춰놓는 것을 말합니다. 캡슐화의 의미는 크게 두가지로 나눌 수 있는데, 하나는 관련있는 속성이나 기능들을 하나로 묶어 담아둬서 관리하기 편하도록 하기 위함이고, 다른 하나는 속성이나 기능들을 객체 내부에 숨겨 놓고 외부의 잘못된 요청이 들어와도 객체 내부에 문제가 생기지 않도록 보호하며 꼭 필요한 속성이나 기능만 외부로 노출하는 것입니다. 이를 정보 은닉(hiding information) 이라고도 부릅니다. 대표적인 예로 TV 리모콘을 생각해볼까요. 리모콘 내부의 회로나 전기신호가 어떻게 전송되고, 변하는지 볼륨을 올리거나 내릴때, 채널을 바꾸려면 리모콘 내부의 처리가 어떻게 되는지 사용자는 알수 없도록 숨겨 놓습니다. 우리는 리모콘의 볼륨업/다운 버튼, 채널 버튼, 전원 버튼 등 만 누르면 어떻게 동작하는지만 알면 사용할 수 있게 되는 것입니다.
2. 클래스와 객체
클래스와 객체를 한마디로 정리하면 클래스는 설계도, 객체는 이 설계도로 만들어진 피조물을 생각하시면 됩니다. 클래스 자체만으로 구체화 될 수 없고, 객체를 생성하기 위해서는 미리 설계도를 만들어 놓고, 이 설계도를 가지고 실제 객체를 만들게 되는 것입니다. 예를 들면, 이번에 신차를 만들기 위해, 자동차 엔진과, 편의 기능, 옵션, 외장 등등을 미리 설계를 하게 되는데 이것이 클래스고 이 설계도를 가지고 신차를 만들어 내게 되면 그 신차가 바로 객체가 됩니다. 설계도는 하나지만, 설계도로 만들어진 객체는 모두 다른 자동차가 되겠죠?
이번에 출시한 신차 이름가 “ABC” 라고 할때, 이 자동차를 내가 샀고, 내 친구가 샀다고 가정해보겠습니. “ABC” 자동차의 설계도로 만들어진 이 자동차는 모두 “ABC” 자동차겠죠? 하지만 내가 산 자동차와 내 친구가 산 자동차는 같은 자동차가 아닙니다. 같은 “ABC”라는 종류의 자동차일 뿐이지, 같은 자동차는 아니라는 것입니다. 즉 자동차의 종류(타입)는 ABC로 같지만 자동차의 이름은 내자동차, 친구자동차로 다른 자동차가가 됩니다.
주위를 살펴보면 모든 것의 사물들이 객체로 볼 수 있습니다. 똑같이 생긴 의자들이 모델명도 같고 같은 부품으로 만들어졌지만, 우린 같은 의자라고 부르지 않습니다. 같은 종류의 의자일 뿐이죠. 객체지향 프로그램에서도 마찬가지입니다.
그럼 자바에서 클래스(설계도)를 만들고 객체(피조물)을 생성하는 방법을 알아보겠습니다.
객체 변수 선언 방법
// 객체 선언과 초기화 별도로 정의
클래스명 변수명;
변수명 = new 클래스명();
// 객체 선언과 초기화 동시에 정의
클래스명 변수명 = new 클래스명();
처음에 배웠던 변수를 선언하고 초기화했던 방법과 동일합니다. 변수명 앞에 클래스명이 오는데, 이 클래스명이 데이터 타입, 즉 자료형이 되는 것입니다. 이렇게 클래스를 이용해 객체를 생성하는 것을 인스턴스(instance)화 한다고 표현하는데, 내가 직접 만든 클래스도 타입으로 지정할 수 있습니다. 이를 사용자 정의 자료형이라고 부릅니다. 사용자 정의 자료형도 참조 자료형 중 하나이므로, new 연산자로 생성된 모든 객체는 참조자료형이 됩니다. 먼저 Member라는 클래스를 만들어 보겠습니다.
public class Member {
}
자바에서는 클래스가 곧 파일이 되므로 반드시 파일명도 Member.java라고 만들어야 합니다. 지금은 내용이 비어 있는 Member 클래스지만 이 설계도로 객체를 생성해보도록 하겠습니다. 다음 예제는 MemberMain이라는 클래스를 생성해서 위에서 만든 Member 클래스를 객체로 생성하는 코드입니다.
public class MemberMain {
public static void main(String[] args) {
Member m = new Member();
Member m2 = new Member();
if (m == m2) {
System.out.println("m개체와 m2객체는 같다.");
} else {
System.out.println("m개체와 m2객체는 같지 않다.");
}
}
}
실행 결과
m개체와 m2객체는 같지 않다.
Member 타입의 m과 m2 라는 변수를 생성하고 new 연산자를 통해 생성한 객체를 대입했습니다. m, m2 변수 모두 객체변수로 같은 자료형이지만 저장된 주소값은 다릅니다. 그래서 m == m2 비교연산의 결과는 false가 된 것을 알 수 있습니다. 즉, 서로 독립된 다른 객체라는 것입니다.
이 부분이 객체에 대한 개념 중에 기초적이지만 아주 중요한 개념입니다. 이 예제에서 두 개의 클래스 파일의 관계를 살펴보겠습니다. Member라는 클래스가 있고, MemberMain이라는 클래스는 main() 메서드에서 Member 클래스의 객체를 생성한 것입니다. MemberMain 클래스는 프로그램을 실행하기 위해 Member 클래스는 다른 클래스에서 사용하기 위해 만든 것입니다. main() 메서드는 프로그램을 실행하기 위한 시작점이라고 했는데, 결국 전체 프로그램에서 실행의 시작점이 되는 main() 메서드가 있는 클래스는 하나이고, 나머지는 전부 다른 클래스에서 사용되기 위한 클래스라는 것을 알 수 있습니다. 이렇게 대부분이 다른 클래스에서 실행하기 위해 만들어지는 클래스 형태로 만들어 집니다. 즉, 다른 파일로 만들어진다는 것입니다. 다음 예제는 main() 메서드에서 자신의 객체를 생성한 예입니다.
public class MemberMain2 {
public static void main(String[] args) {
MemberMain2 m = new MemberMain2();
MemberMain2 m2 = new MemberMain2();
if (m == m2) {
System.out.println("m개체와 m2객체는 같다.");
} else {
System.out.println("m개체와 m2객체는 같지 않다.");
}
}
}
실행 결과
m개체와 m2객체는 같지 않다.
MemberMain2라는 클래스의 main() 메서드에서 자기 자신 클래스인 MemberMain2 타입의 객체를 생성한 것입니다. 위에서 봤던 예제와 실행 결과는 동일하지만 객체 지향 프로그램에서는 객체를 부품으로 나눠서 조립하는 형태로 실행하는 것이 좋습니다. 이 예제도 그렇지만 아직은 왜 이렇게 분리해서 개발하는 것이 좋은지 잘 이해되지 않을 것입니다. 부품은 따로 따로 만들어 놓고, 이 부품들을 결합해 실행하는 곳을 따로 만들어 놓게 되면, 나중에 원하는 부품만 갈아 끼우는 형태로 관리하기도 편하고 찾기도 편하게 됩니다. 그래서 처음부터 객체에 대한 개념을 이해하고 습관을 잘 들여 놓는것이 중요합니다.