이미지 출처: medium.com/@najmulhaque069
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어 설계 방법론 중 하나로, 데이터를 처리하는 방법을 객체(Object) 간의 상호작용으로 정의하는 프로그래밍 기법이다. 이 기법에서는 프로그램을 여러 개의 객체로 나누고, 각 객체는 자신만의 데이터를 가지고, 해당 데이터를 처리하는 방법(메서드)을 포함한다. 이러한 객체들이 서로 협력하여 복잡한 기능을 구현하는 것이 객체 지향 프로그래밍의 기본 개념이다.
객체 지향 프로그래밍(OOP)은 레고 블럭을 조립하듯이 컴포넌트를 유연하게 구성하고, 변경이 용이하게 설계하는 방법이다. 이렇게 나누어진 객체들은 각각의 책임을 가지고 있으며, 서로 메시지를 주고받으며 협력한다. 이 방식은 대규모 소프트웨어 개발에 특히 유용하며, Java, Python, C++, C#, Kotlin 등 다양한 언어들이 객체 지향 프로그래밍을 지원하고 있다.
객체 지향 프로그래밍(OOP)은 소프트웨어 개발의 복잡성을 관리하기 위해 등장한 방법론이다. 소프트웨어 시스템이 점점 더 복잡해지면서 기존의 프로그래밍 패러다임, 특히 절차적 프로그래밍의 한계가 드러나기 시작했다.
절차적 프로그래밍은 프로그램을 순차적으로 실행되는 일련의 절차(또는 함수)로 구성한다. 이 방법론은 상대적으로 단순한 프로그램에는 적합하지만, 소프트웨어 시스템이 대형화되고 복잡해지면서 다음과 같은 문제점이 발생했다.
이러한 문제들을 해결하기 위해 새로운 패러다임이 필요했으며, 그 결과로 객체 지향 프로그래밍이 등장했다.
객체 지향 프로그래밍은 상속과 클래스 구조를 통해 코드를 재사용할 수 있게 하였다. 이를 통해 동일한 코드를 여러 번 작성하는 것을 피하고, 재사용 가능한 컴포넌트를 만들 수 있게 되었다.
객체 지향 프로그래밍은 모듈화된 구조를 제공함으로써 시스템의 유지보수성과 확장성을 크게 향상시켰다. 각 객체는 독립적으로 개발되고 테스트될 수 있으며, 시스템의 나머지 부분에 영향을 미치지 않고도 수정이 가능하다.
객체 지향 프로그래밍은 소프트웨어를 현실 세계의 객체와 유사하게 모델링할 수 있게 했다. 이는 복잡한 도메인을 더 직관적이고 명확하게 표현할 수 있도록 하여, 소프트웨어 설계와 구현 과정이 더 쉬워졌다.
자바스크립트는 원래 절차적 및 함수형 프로그래밍 언어로 설계되었지만, 시간이 지나면서 객체 지향 프로그래밍을 지원하기 위한 기능들이 추가되었다. ES6(ECMAScript 2015) 이후에는 클래스와 모듈 개념이 도입되면서 자바스크립트에서도 객체 지향 프로그래밍의 핵심 개념인 클래스(Class), 객체, 상속, 다형성, 캡슐화 등을 자바스크립트에서도 적용 가능하게 되었다.
자바스크립트는 프로토타입 기반 언어로서, 전통적인 클래스 기반의 객체 지향 언어와는 다소 다른 방식으로 객체 지향 개념을 구현한다. 자바스크립트의 객체는 모두 프로토타입이라는 특성을 가지며, 이 프로토타입을 통해 다른 객체의 속성과 메서드를 상속받는다. 자바스크립트의 객체 지향 개념은 다른 언어와 유사하지만, 프로토타입 체인과 같은 고유의 특성을 통해 독특한 방식으로 구현된다.
객체 지향 프로그래밍은 특히 대규모 소프트웨어 개발에 유리하다. 복잡한 시스템을 여러 개의 작은 객체로 나누어 관리할 수 있기 때문이다. 예를 들어, 은행 시스템, 항공기 예약 시스템, ERP 시스템 등 대규모 시스템 개발에 OOP가 널리 사용된다.
OOP는 유연하고 확장 가능한 시스템을 개발할 때도 사용된다. 객체 지향 설계는 새로운 요구 사항이 추가될 때 기존 코드를 최소한으로 수정하면서 새로운 기능을 추가할 수 있게 한다.
많은 라이브러리와 프레임워크가 객체 지향 패러다임을 기반으로 개발된다. 이들은 재사용성을 극대화하여 다양한 애플리케이션에서 활용될 수 있다. 대표적인 예로는 Java의 표준 라이브러리, .NET 프레임워크, Spring 프레임워크 등이 있다.
객체 지향 프로그래밍은 구조가 복잡해질 수 있다. 객체 간의 관계가 많아지면 코드의 복잡성이 증가할 수 있으며, 이로 인해 프로그램의 이해와 유지보수가 어려워질 수 있다.
객체 지향 프로그래밍은 다형성, 상속 등으로 인해 프로그램의 실행 속도가 느려질 수 있다. 특히, 많은 객체 간의 메시지 전달이 빈번한 경우 성능 저하가 발생할 수 있다.
객체 지향 시스템을 설계하는 데는 초기 비용이 많이 든다. 객체 간의 관계를 정의하고, 클래스를 설계하는 과정이 복잡하고 시간이 많이 소요될 수 있다.
캡슐화는 객체의 상태(데이터)와 행동(메서드)을 하나의 단위로 묶는 것이다. 외부에서는 객체의 내부 구현을 알 필요가 없으며, 객체는 자신의 데이터를 보호하고 외부와의 인터페이스를 통해서만 상호작용한다. 이렇게 하면 코드의 보안성과 유지보수성이 향상된다.
예를 들어, 은행 계좌 객체에서 잔액(balance)은 외부에서 직접 접근할 수 없고, 오직 입금(deposit)과 출금(withdraw) 메서드를 통해서만 조작할 수 있다. 이로 인해 계좌의 잔액이 잘못된 방법으로 변경되는 것을 방지할 수 있다.
class BankAccount {
private double balance; // 캡슐화된 변수
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
public double getBalance() {
return balance;
}
}
상속은 기존 클래스의 속성(변수)과 메서드를 새로운 클래스에서 재사용할 수 있게 해주는 기능이다. 이를 통해 코드의 중복을 줄이고, 재사용성을 높이며, 계층 구조를 형성하여 프로그램의 구조를 더욱 명확하게 할 수 있다.
예를 들어, 동물(Animal) 클래스는 여러 하위 클래스들(Dog, Cat 등)에 공통된 속성(teethCount, legCount 등)을 정의하고, 각 하위 클래스는 자신만의 특성을 추가로 정의할 수 있다.
class Animal {
int teethCount;
int legCount;
int tailCount;
}
class Dog extends Animal {
void bark() {
System.out.println("Bark!");
}
}
class Cat extends Animal {
void meow() {
System.out.println("Meow!");
}
}
다형성은 같은 인터페이스를 구현하는 여러 객체가 서로 다른 방식으로 동작할 수 있도록 하는 능력이다. 이를 통해 하나의 메서드나 연산자가 다양한 객체에 대해 다른 방식으로 작동할 수 있게 된다. 다형성은 메서드 오버로딩과 오버라이딩을 통해 구현할 수 있다.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark!");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow!");
}
}
추상화는 복잡한 시스템으로부터 핵심적인 개념이나 기능을 간추려내어 정의하는 것을 의미한다. 객체 지향 프로그래밍에서 추상화는 클래스를 정의할 때 불필요한 부분을 생략하고 중요한 속성에만 중점을 둔다. 이를 통해 프로그램을 더 쉽게 이해하고 유지보수할 수 있게 된다.
추상화는 두 가지로 나뉜다.
abstract class Animal {
abstract void sound();
}
class Dog extends Animal {
void sound() {
System.out.println("Bark!");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Meow!");
}
}
클래스는 객체를 정의하기 위한 설계도나 청사진이다. 객체 지향 프로그래밍에서는 클래스가 객체를 만들기 위한 기본 단위로 사용되며, 클래스는 속성과 메서드로 구성된다.
예를 들어, 자동차(Car) 클래스를 정의할 때, 자동차의 속성(예: 색상, 모델)과 행동(예: 운전, 정지)을 클래스에 정의할 수 있다.
class Car {
String color;
String model;
void drive() {
System.out.println("The car is driving");
}
void stop() {
System.out.println("The car has stopped");
}
}
객체는 클래스에서 정의한 속성과 행동을 실제로 구현한 인스턴스(instance)이다. 객체는 메모리에 할당되며, 클래스를 기반으로 만들어진 실체로서 프로그램에서 사용된다.
Car myCar = new Car();
myCar.color = "Red";
myCar.model = "Sedan";
myCar.drive();
인터페이스는 클래스가 특정 기능을 수행하기 위해 반드시 구현해야 하는 메서드들의 집합을 정의하는 방법이다. 인터페이스는 다중 상속을 지원하지 않는 언어에서 특히 유용하며, 서로 다른 클래스가 동일한 메서드를 구현하게 하여 다형성을 구현하는 데 사용된다.
interface Drivable {
void drive();
}
class Car implements Drivable {
@Override
public void drive() {
System.out.println("The car is driving");
}
}
객체 지향 프로그래밍(OOP, Object-Oriented Programming)은 시스템의 구조와 객체 간의 관계를 명확하게 표현할 수 있지만, 특정 공통 기능이 여러 객체에 걸쳐 반복되는 문제가 발생할 수 있다. 관점 지향 프로그래밍(AOP, Aspect-Oriented Programming)은 이러한 공통 기능을 횡단 관심사로 보고, 이를 효과적으로 분리하고 재사용 가능하게 한다. 따라서, OOP와 AOP는 함께 사용되었을 때 더 깨끗하고 유지보수하기 쉬운 코드를 작성할 수 있게 해준다.