
요즘 Java 프로그래밍 언어를 학습하고 있다.
지금도 내가 서버 프로그래밍 언어로 사용하는 JavaScript, TypeScript는 동적 프로그래밍과 유연한 구조가 강점이라면 자바는 멀티 스레딩, 객체 지향 등에서 강점이 있는 것으로 널리 알려져 있다.
그 중에서도 오늘은 "객체 지향" 이 무엇인지, 어떤 기준으로 객체 지향이라고 하는 것인지 학습한 내용을 정리하려 한다.
"객체를 지향하는 것"이 무엇인지 이해하기 전에 객체가 어떤 의미인지 이해할 필요가 있다.
객체는 한마디로 "속성" 과 "행동"을 가진 무엇이라고 할 수 있다.
흔히 객체를 구현하기 위해서 클래스를 이용하므로 클래스를 예로 설명을 들어보자.
클래스에는 멤버 변수와 메소드가 존재한다.
Class Vehicle{
int speed;
int weight;
void speedUp(){
speed++;
}
}
간단한 클래스 코드이다. Vehicle라는 추상적 개념(객체)을 클래스라는 구조로 구체화하여 만들었다.
speed, weight 변수는 Vehicle라는 개념에 포함되는 속성.
speedUp()이라는 함수는 Vehicle가 행동할 수 있는 방식이라고 할 수 있다.
이처럼 "객체지향"에서의 객체는 자신만의 속성을 가지고 행동하는 것이라고 볼 수 있다.
"객체"가 무엇인지 이해했으니 이제는 "객체를 지향하는 방법" 에 대해서 정리해보자.
객체 지향 프로그래밍 (Object Oriented Programming)은 위에서 설명한 객체 개념을 중심으로 소프트웨어를 개발하는 방법론이다.
쉽게 얘기하자면, 프로그래밍에서 여러 객체들이 서로의 속성을 가지고 독립적으로 상호작용하게 하는 방식으로 소프트웨어를 개발한다는 것으로 이해할 수 있다.
이러한 객체 지향 프로그래밍을 위해서 중요한 핵심 개념이 있다.
크게 4가지로 " 캡슐화, 상속, 다형성, 추상화 "가 있다.
1. 캡슐화
캡슐화는 말 그대로 속성과 기능들을 클래스라는 캡슐에 넣어 보관하는 것이다.
알약 가루가 든 캡슐 안을 우리가 직접 만지지 못하는 것처럼, 클래스 에서도 각 멤버 변수와 메소드들을 외부에서 직접 접근하지 못하도록 하는 것이다.
이러한 정보 은닉을 통해서 객체의 독립성을 유지하고 외부로부터 영향을 받지 않도록 한다.
public class Vehicle {
private int speed;
public Vehicle(int speed) {
this.speed = speed;
this.weight = weight;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public void accelerate() {
speed++;
}
public void decelerate() {
if (speed > 0) {
speed--;
}
}
}
해당 코드를 보면 Vehicle이라는 클래스 내부 속성인 speed와 weight에 대해서 접근 제한자가 private로 되어 있기 때문에 클래스 내부에서만 접근이 가능하며, 외부에서 직접적으로 접근할 수 없다.
아니 public 접근제한자인 getSpeed나 setSpeed 메소드로 speed라는 변수의 값을 읽어오고 변경할 수 있는데 이게 캡슐화의 의도가 맞나? 싶을 수 있다.
결론은 맞다.
결국 speed라는 변수의 값을 읽어오고 변경하기 위해서는 필연적으로 Vehicle클래스 인스턴스의 getSpeed()나 setSpeed() 메소드를 사용해야 한다.
인스턴스.speed = 100과 같은 직접 접근을 막음으로써 개발자는 의도치 못한 값의 변경을 방지할 수 있다는 것이다.
2. 상속
상속은 말 그대로 한 클래스(부모)의 속성과 메소드를 다른 클래스(자식)가 이어받는 과정을 말한다.
이를 통해서 자식은 필요한 기능의 전체 또는 일부를 부모로부터 물려받을 수 있으므로 코드의 재사용성을 높이고 관리를 용이하게 할 수 있다.
코드로 보면 아래와 같다.
public class Car extends Vehicle {
private int fuelLevel;
public Car(int speed, int fuelLevel) {
super(speed);
this.fuelLevel = fuelLevel;
}
public void refuel() {
fuelLevel = 100;
}
}
Car라는 클래스의 어디에도 speed라는 속성을 볼 수 없다.
또한 차량의 움직임을 담당하는 가속,감속 메소드도 존재하지 않는다.
그러나 Car 클래스의 인스턴스는 이러한 메소드 사용이 가능하다!
Vehicle 클래스의 accelerate 및 decelerate 메소드를 사용해 차량의 움직임을 구현할 수 있는 것이다.
또한 Car 클래스만의 fuelLevel 속성과 refuel메소드를 활용해 자동차만의 특성을 나타내기도 한다.
이러한 상속을 통해 Car 클래스는 Vehicle 클래스의 코드를 재사용하고 코드의 중복을 줄이며 자동차만의 기능을 추가적으로 정의할 수 있는 것이다.
- 다형성
다형성은 많은 형태를 가질 수 있는 속성이라고 할 수 있다.
말이 조금 어려운데, 하나의 객체가 여러개의 타입을 가질 수 있음을 의미하는 것이다.
부모 클래스의 타입으로 자식 클래스의 객체를 참조할 수 있다는 것인데, 이와 관련하여 "오버라이딩"이라는 중요한 개념을 알아야 한다.
"오버라이딩" 은 자식 클래스에서 부모 클래스의 메소드를 재정의하여 같은 메소드를 호출할 때 다른 행동을 할 수 있도록 하는 것이다.
예를 들어, 부모 클래스를 Vehicle이라고 보았을 때 자식 클래스에 Truck, Bicycle, Car가 있고 부모 Vehicle의 타입으로 각기 다른 자식 클래스 객체를 참조하는 경우를 생각해보자.
Vehicle vehicle = new Car();
vehicle.accelerate(); ..... 1
vehicle = new Truck();
vehicle.accelerate(); ..... 2
vehicle = new Bycicle();
vehicle.accelerate(); ..... 3
Car, Truck, Bycicle 클래스 각각에 Accelerate 메소드가 있고 각각의 함수에서 증가하는 speed 크기를 다르게 구현해 놓았다면 위 코드에서 같은 Vehicle 타입의 변수라도 1,2,3번의 Accelerate 메소드의 의미는 다르다.
이처럼 하나의 코드( 메소드 ) 가 여러 타입의 객체에 대해서 다른 동작을 수행할 수 있어 코드의 유연성과 재사용성을 높일 수 있다.
같은 Vehicle 타입의 변수에 참조만 변경했을 뿐인데 사용하는 메소드의 의미가 달라지는 것이다!
- 추상화
마지막으로 추상화는 복잡한 개념을 단순화하여 프로그래밍에 적용한다는 것이다.
위에서 설명한 Vehicle 클래스라는 추상화된 개념을 이어받는 Car, Truck, Bycicle이 그 예이다.
Vehicle에는 탈것이라는 개념에 공통되는 Speed와 Accelerate, Decelerate라는 개념이 존재한다.
그러나 이를 상속받아 사용하는 Car, Truck, Bycicle 클래스에서는 이를 그대로 사용하지 않고 각 속성에 부합하는 용도로 바꿔 사용한다.
Accelerate라는 Vehicle 클래스의 추상적 개념을 하위 자식 클래스에서 구체화한 것이라고 할 수 있다.
상위 클래스에서 메소드의 명시만 해둔 것을 자식 클래스에서 구체화 하는 것이 추상화의 개념이라고 할 수 있다.
이렇듯 객체지향 프로그래밍은 "캡슐화, 상속, 다형성, 추상화" 를 통해 각 객체 간 작용을 독립적으로 관리하며 진행한다. 이러한 과정을 통해서 유연하고, 확장이 가능하며 유지보수가 쉬운 소프트웨워를 개발하는 것이다.
특히 복잡한 프로그래밍에서는 이러한 방법이 굉장히 효율적일 수 있다.