나는 자바를 사용해 프로젝트를 진행하면서 많은 클래스를 만들고 많은 객체를 사용했지만 정작 객체 지향 프로그래밍이 뭔지 설명하라고 하면 머릿속에 달리 생각나는 말들이 없었다.
그냥 사용하는 것과 이해하고 사용하는 것의 차이는 반드시 있을 것이라고 생각해 개념부터 다시 정리해보기로 했다.
: 객체를 생성하기 위한 템플릿. 클래스는 객체의 상태를 정의하는 필드와 행동을 정의하는 메소드를 포함. 사물의 설계도와 같다.
ex) '자동차'라는 클래스를 만든다고 하면 이 클래스에는 자동차의 여러 속성인 색상, 모델 등과 기능인 달리다, 정지하다 등이 정의될 것이다.
: 클래스의 인스턴스. 클래스에 정의된 대로 메모리에 할당된 실체. 클래스를 바탕을 만들어진 실제 사물이라고 볼 수 있다.
ex) '자동차' 클래스를 바탕으로 만들어진 실제 자동차가 객체에 해당한다. 각각의 자동차는 같은 클래스를 바탕으로 만들어졌어도 각자 다른 색상이나 모델을 가질 수 있다.
: 상속은 한 클래스가 다른 클래스의 특성을 상속받을 수 있게 하는 것. 코드의 재사용성 ↑, 유지보수 용이.
ex) '전기자동차' 라는 클래스를 만들고 '자동차' 클래스를 상속받게 되면 '전기자동차' 클래스는 '자동차' 클래스의 모든 속성과 기능을 가지면서 추가적인 특성을 가질 수 있다.
: 객체의 데이터와 데이터를 조작하는 메소드를 하나로 묶는 것. 외부에서 객체의 내부 구조를 모르더라도 정의된 인터페이스를 통해 상호작용할 수 있음.
→ 객체의 내부 데이터를 숨기고 외부에서 특정 기능만 사용할 수 있게 하는 것.
ex) 자동차의 엔진이 어떻게 작동하는지 몰라도 자동차를 운전할 수 있는 것과 비슷하다.
: 같은 이름의 메소드나 연산자가 다른 클래스에 따라 다른 방식으로 작동할 수 있음을 의미. 같은 이름의 메소드가 서로 다른 클래스에서 서로 다른 행동을 하는 것.
ex) '속도를 올리다'라는 기능이 자동차마다 각각 다르게 작동할 수 있다.
: 복잡한 현실을 단순화 시키는 것. 필요한 정보만을 추출해 프로그램에 통합하는 과정.
ex) '자동차'를 '이동 수단'으로 일반화 하는 것과 비슷하다
각각의 객체는 실제 세계의 사물처럼 독립적으로 존재하며, 각자의 데이터와 기능을 가진다.
이런 객체 지향 방식으로 프로그램을 구성하면 재사용이 가능하고 유지보수가 용이한 코드를 작성하는데 큰 도움이 된다.
절차적 프로그래밍(Procedural Programming) 이란 프로그램을 일련의 절차나 함수의 시퀀스로 구성하는 방법이다. 대표적으로 C언어가 있음.
객체 지향 프로그래밍의 이해를 돕기 위한 작성한 자동차를 모델링하는 예시이다.
// 자동차 클래스 정의
class Car {
// 속성(변수)
String color;
String model;
// 생성자
Car(String color, String model) {
this.color = color;
this.model = model;
}
// 행동(메소드)
void accelerate() {
System.out.println(model + " is accelerating.");
}
void brake() {
System.out.println(model + " is braking.");
}
}
// 메인 클래스
public class Main {
public static void main(String[] args) {
// 객체 생성
Car myCar = new Car("Red", "Tesla Model S");
// 객체의 메소드 사용
myCar.accelerate();
myCar.brake();
}
}
여기서 'Car' 클래스는 자동차의 모델과 색상을 속성으로 가지고, 가속하고 멈추는 행동을 메소드로 정의한다. 이렇게 객체를 사용하면 실제 자동차를 다루는 것처럼 코드를 작성할 수 있고, 각 자동차 객체가 독립적인 속성과 행동을 가질 수 있다.
클래스 -> 위의 이미지에서 설계도는 클래스를 나타낸다.
객체 -> 자동차들이 클래스의 인스턴스인 객체이다. 각각 독립적인 속성과 행동을 가진다.
상속 -> 화살표가 한 자동차가 다른 자동차로부터 속성과 행동을 물려받는 상속 관계이다.
캡슐화 -> 자동차 주변의 보호막이 클래스가 데이터와 메소드를 하나의 단위로 묶어 외부에서 접근을 제한하는 것.
다형성 -> 여러 자동차가 같은 기능(가속 등)을 다르게 수행하는 모습.
절차 지향
객체 지향 프로그래밍이 '객체'에 초점을 맞춘다면, 절차적 프로그래밍은 '함수'와 '절차'에 더 중점을 둔다.
아래는 C언어를 사용한 간단한 예시 코드이다.
#include <stdio.h>
// 함수 정의
void printHello() {
printf("Hello, World!\n");
}
void printGoodbye() {
printf("Goodbye!\n");
}
// 메인 함수
int main() {
printHello(); // Hello, World! 출력
printGoodbye(); // Goodbye! 출력
return 0;
}
이 코드에는 'printHello'와 'printGoodbye'라는 두 개의 함수가 정의되어 있고, 'main'함수에서 순차적으로 호출한다. 이처럼 절차적 프로그래밍은 특정 작업을 수행하는 함수를 정의하고 이러한 함수들을 순차적으로 호출하여 프로그램의 흐름을 제어한다.
절차적 프로그래밍에서는 프로그램을 "무엇을 할 것인가"와 "어떻게 할 것인가"로 나누는데 "무엇을 할 것인가"에서는 프로그램이 해야할 일을 정의하고 "어떻게 할 것인가"는 그 일을 수행하기 위한 구체적인 명령을 나열한다.
절차적 프로그래밍을 사용하면 다음과 같은 장점이 있다.
하지만,
절차적 프로그래밍은 프로그램의 규모가 커질수록 관리가 복잡해지고, 유지 보수와 확장이 어려워질 수 있다. 코드의 중복이 많아지고 변경에 따른 영향을 예측하기 어려워질 수 있기 때문이다.
그런데 객체 지향과 절차 지향의 차이를 찾아보면서 객체 지향도 코드이 실행은 순차적인데 왜 '순차적임'이 절차적 프로그래밍의 특징으로 강조되는건지 궁금했다.
그 이유에 대해서는,
절차적 프로그래밍 스타일이 전통적으로 프로그램의 흐름을 시간의 흐름에 따른 일련의 명령어로 보기 때문이다. 이 접근 방식은 컴퓨터 프로그램을 마치 레시피나 조립 설명서처럼 볼 수 있다. 즉, 특정 작업을 수행하기 위한 단계별 지시사항을 차례대로 나열하는 방식인 것이다.
객체 지향 프로그래밍에서도 코드의 실행은 순차적이지만 강조점은 다른 측면에 있다.
1. 객체 상호작용 : 프로그램은 객체들 간의 상호작용으로 이해된다. 각 객체는 독립적으로 작동하고, 다른 객체와 메시지를 주고 받으면서 상호작용한다.
2. 코드의 구조 : 코드는 객체로 구성, 각 객체는 내부 상태(데이터)와 행동(메소드)을 캡슐화. 이는 프로그램의 흐름보다는 객체 간의 관계에 초점을 맞춘다.
3. 디자인 패턴과 원칙 : 객체 지향 프로그래밍은 디자인 패턴, SOLID 원칙을 등을 통해 프로그램의 설계를 안내한다. 이러한 원칙들은 프로그램의 구조와 객체 간의 관계를 중시한다.
좀 더 이해하기 쉽게 설명해보자면,
절차적 프로그래밍 은 레고 집을 만들 때 설명서대로 차근차근 한 단계씩 따라하는 것과 비슷하다. '빨간 블록을 먼저 놓고, 그 위에 파란 블록을 올린다.' 이런 식이다. 모든 단계가 정해진 순서에 따라 엄격하게 진행된다.
객체 지향 프로그래밍 은 레고 집을 만드는 과정을 조금 다르게 접근한다. 객체 지향 프로그래밍에서 레고 블록들을 '객체'로 보는데 각각의 레고 블록은 특성한 색깔, 크기, 기능 등을 가지고 있다. 이런 방식을 사용하면 각 블록들이 어떻게 상호작용하는지를 생각하고 '문을 열 수 있는 파란 블록은 여기에 놓고, 빨간 블록을 저기에 놓으면 창문을 만들 수 있겠다.' 하며 레고 집을 만들 수 있다. 물론 이런 방식에서도 집을 만드는 과정 자체는 한 단계씩 진행되지만 각 단계에서 어떤 블록을 사용할지, 그리고 어떻게 블록을 결합할 지에 대한 더 많은 자유와 유연성을 가질 수 있다.
결국 두 방식 모두 순서대로 진행되지만 절차적 프로그래밍은 '단계별 지시사항'에 더 집중하는 반면, 객체 지향 프로그래밍은 '객체들이 서로 어떻게 작동하는지'에 더 관심을 둔다고 볼 수잇다.