객체 지향 프로그래밍의 활용 방안
객체 지향 프로그래밍의 활용 방안

객체 지향 프로그래밍의 활용 방안

Tags
Node.js
Web Dev
Published
February 8, 2024
Author
gozneokhan

절차 지향 프로그래밍(Procedural Programming)

절차 지향 프로그래밍이란?

절차 지향 프로그래밍은 이름 그대로 물이 위에서 아래로 흐르듯이 순차적인 처리가 핵심인 프로그래밍 기법입니다. 프로그램은 일련의 절차로 나뉘고, 각 절차는 데이터나 함수 등을 조작하는 프로시저로 이루어져 있습니다. 주로 C언어와 같은 언어에서 활용되며, 프로그램 전체가 유기적으로 연결되도록 설계됩니다.
절차 지향 프로그래밍의 장점은 컴퓨터의 작업 방식과 유사하여 실행 속도가 빠르며 시간적으로 유리하다는 점이 있습니다. 그러나 단점도 존재합니다.
notion image

절차 지향 프로그램의 한계

  • 유지 보수의 어려움
모든 구성 요소가 유기적으로 연결되어 있다는 것은 한 부분이 문제가 일어나면 전체 시스템이 영향을 받는다는 의미이며, 문제 해결을 위해 전체 시스템을 수정해야 하는 어려움을 야기합니다. 유지 보수가 까다로우며 디버깅이 어려울 수 있습니다.
  • 엄격한 순서
실행 순서가 엄격하게 정해져 있기 때문에 코드의 순서를 바꾸면 결과가 달라질 가능성이 높습니다. 이는 언어의 융통성이 부족하여 생산 효율이 떨어진다는 단점으로 이어집니다.
이러한 단점들은 스파게티 코드를 유발하고, 클린 아키텍처의 관심사 분리에 위배될 수 있습니다. 이에 대한 대안으로 객체 지향 프로그래밍이 등장하였습니다. 객체 지향 프로그래밍은 모듈화와 재 사용성을 강조하며, 코드의 유지 보수와 이해를 더욱 용이하게 합니다.

객체지향 프로그래밍(Object-Oriented Programming)

객체 지향 프로그램이란?

객체 지향 프로그래밍(OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나로, 필요한 데이터를 추상화 하여 상태와 행위를 가진 객체를 생성하고, 이 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법입니다.
notion image
가장 큰 특징은 클래스를 이용해 연관된 처리 부분(함수)과 데이터 부분(변수)을 하나로 묶어 객체(인스턴스)를 생성하고 사용한다는 점입니다. 이러한 방식은 현재 가장 많이 사용되는 대표적인 프로그래밍 방법 중 하나이며, 주로 JAVA, C++, C#과 같은 언어에서 채택되고 있습니다.
객체 지향 프로그래밍은 클린 아키텍처의 관심사 분리를 지키면서도 유지 보수와 재사용성이 용이하다는 큰 장점을 가지고 있습니다. 클래스와 객체의 개념을 활용하여 코드를 모듈화하고, 각 객체가 자체적으로 상태와 행위를 관리함으로써 코드의 가독성을 향상 시키고 유연성을 제공합니다. 이는 복잡한 프로젝트에서 특히 유용하며, 코드의 확장성과 유지 보수성을 증진 시킵니다.

클래스(Class)와 인스턴스(Instence)

클래스와 객체 간의 관계를 이해하기 위해, 붕어빵 틀과 붕어빵을 비유로 사용할 수 있습니다.
notion image

클래스 ⇒ (Car)

  • 클래스는 붕어빵 틀과 유사합니다.
  • 붕어빵 틀은 붕어빵을 만들기 위한 설계도로, 클래스는 객체를 생성하기 위한 설계도입니다.
  • 클래스는 속성(멤버 변수)과 메서드(함수)를 정의하여 객체가 가져야 할 구조와 행동을 명시합니다.

객체 ⇒ (Bmw, Voikswagen, Audi)

  • 객체는 붕어빵 틀을 통해 만들어진 실제 붕어빵과 같이, 클래스를 기반으로 생성된 실체입니다.
  • 각 객체는 클래스에서 정의한 속성과 메서드를 가지며, 실제로 실행될 수 있는 인스턴스입니다.
  • 예를 들어, 클래스가 "Car"일 때, "Bmw", "Voikswagen", "Audi"는 이 클래스의 객체로서, 각각의 차량을 나타냅니다.
이러한 비유를 통해 클래스와 객체 간의 관계를 이해할 수 있습니다. 클래스는 일종의 설계도로서 구체적인 객체를 생성하기 위한 틀을 제공하며, 객체는 그 설계도를 기반으로 실제로 생성된 것으로, 데이터와 동작을 포함하는 실체입니다.

클래스 객체 그리고 인스턴스

// 클래스 정의 (Car) class Car { constructor(brand, model) { this.brand = brand; // 속성(멤버 변수) this.model = model; } // 객체 생성 (Bmw, Voikswagen, Audi) const bmw = new Car("Bmw", "X5"); const volkswagen = new Car("Volkswagen", "Golf"); const audi = new Car("Audi", "A4");

클래스

클래스는 객체를 정의하고 만들어 내기 위한 설계도 혹은 틀로써, 연관되어 있는 변수와 메서드의 집합을 의미합니다. 클래스의 특징으로는 객체의 상태를 나타내는 필드와 객체의 행동을 나타내는 메소드로 구성되어 있으며, 필드에는 변수, 메소드에는 특정 작업을 수행하기 위한 명령문의 집합이 포함됩니다. 예시 코드에는 필드와 메소드가 생략되어 있는 Car라는 클래스가 선언되어 있습니다.

객체

객체는 클래스를 기반으로 생성된 실체로써, 실제 프로그램에서 사용되는 데이터를 의미합니다. 즉, 소프트웨어 세계에 실제로 구현될 대상을 의미합니다. 예시 코드에서는 선언을 통해 Bmw, Voikswagen, Audi 등의 객체가 생성되며, 이후 메모리에 할당이 될 때 객체의 인스턴스화가 이루어집니다.

인스턴스

인스턴스는 클래스를 기반으로 생성된 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라고 부릅니다. 즉, 이러한 특징을 통해 인스턴스는 객체에 포함된다고 할 수 있습니다. 인스턴스는 추상적인 개념과 구체적인 객체 사이에 초점을 맞출 경우에 사용되고, 클래스를 기반으로 소프트웨어 세계에 구현된 실체라고 할 수 있습니다. 예시 코드에서는 Bmw, Voikswagen, Audi 등이 실제로 메모리에 할당되면서 인스턴스화가 진행됩니다.
⚠️
클래스 vs. 객체 클래스와 객체의 관계에서, 클래스는 설계도라고 표현될 수 있습니다. 이 설계도는 연관된 변수와 메서드의 집합으로 객체를 생성하기 위한 구조를 제공합니다. 객체는 이 클래스를 기반으로 생성된 실체로, 설계도로 정의된 구조에 따라 실제 데이터와 기능을 가지게 됩니다.
객체 vs. 인스턴스 객체와 인스턴스의 관계에서 객체는 타입으로 선언되었을 때를 말합니다. 이는 클래스를 기반으로 생성된 데이터와 함수의 묶음으로, 타입화된 구조를 갖습니다. 반면에 인스턴스는 객체가 메모리에 할당되어 실제로 사용될 때를 의미합니다. 객체가 클래스를 기반으로 생성되어 메모리에 할당되면 해당 객체는 인스턴스로 불립니다. 따라서 객체는 타입으로 선언되었을 때, 인스턴스는 실제로 메모리에 할당되어 사용될 때라고 할 수 있습니다.

객체 지향 프로그래밍의 장단점

장점

  1. 협업에 용이함
객체지향 프로그래밍은 프로젝트를 독립적인 객체 단위로 분리해 작업할 수 있어 여러 개발자와 협업이 용이하다.
  1. 대형 프로젝트에 적합
클래스 단위로 모듈화하여 개발할 수 있어 변경 용이성을 높이고, 대형 프로젝트에서 업무 분담의 효율을 높이는데 적합하다.
  1. 유지보수 용이
캡슐화를 통해 주변에 미치는 영향이 적어 유지보수가 쉽고, 경제적이다.
  1. 코드 재사용성 용이
다른 사람이 만든 클래스를 재사용하거나, 상속을 통해 코드를 확장하여 사용할 수 있어 코드 재사용에 용이하다.

단점

  1. 개발속도가 느림
객체의 처리에 대한 정확한 이해가 필요하므로 설계 단계에서 시간이 많이 소요된다.
  1. 실행속도가 느림
객체 지향 언어는 절차 지향에 비해 일반적으로 실행속도가 느리다.
  1. 코딩의 난이도 상승
다중 상속을 지원하는 경우 코드가 복잡해지고, 코딩 난이도가 상승할 수 있다.
  1. 용량 문제
대규모 프로젝트에서 객체가 많이 사용되면 용량이 커질 수 있다.

객체 지향 프로그래밍의 특징

캡슐화(Encapsulation)

캡슐화는 객체 지향 프로그래밍에서 중요한 원칙 중 하나로, 관련된 데이터와 기능을 하나의 단위로 묶어서 외부에서의 접근을 제한하는 것입니다. 간단한 예시를 통해 캡슐화를 이해해봅시다.

캡슐화 예시

class Car { // 캡슐화된 필드 #speed = 0; // 캡슐화된 메서드 #validateSpeed(newSpeed) { if (newSpeed < 0 || newSpeed > 200) { console.log("Invalid speed"); return false; } return true; } // 외부에서 속도 설정하는 메서드 setSpeed(newSpeed) { if (this.#validateSpeed(newSpeed)) { this.#speed = newSpeed; console.log(`Current speed: ${this.#speed} km/h`); } } // 외부에서 현재 속도 조회하는 메서드 getSpeed() { console.log(`Current speed: ${this.#speed} km/h`); } } // 객체 생성 const myCar = new Car(); // 외부에서 속도를 직접 설정할 수 없음 // myCar.#speed = 100; // 에러 발생 // 메서드를 통해 속도 설정 myCar.setSpeed(120); // 유효한 값이므로 설정됨 // 메서드를 통해 현재 속도 조회 myCar.getSpeed(); // "Current speed: 120 km/h"
위 예시에서 Car 클래스는 #speed라는 private 필드를 가지고 있습니다. 이 필드는 클래스 외부에서 직접 접근이 불가능하며, setSpeedgetSpeed라는 메서드를 통해 간접적으로 상호작용이 이루어집니다. 이것이 캡슐화의 개념으로, 클래스 내부의 상태를 숨기고 외부에서는 제공된 메서드를 통해 상호작용할 수 있도록 하는 것입니다.
⚠️
접근 제한자와 정보 은닉
캡슐화는 객체의 상태와 행동을 하나로 묶어 외부에서의 접근을 제한하는 개념입니다. 이를 구현하기 위해 접근 제한자를 사용하여 데이터의 접근 허용 범위를 설정합니다. 주로 사용되는 접근 제한자에는 public, protected, default (package-private), private이 있습니다.
  1. public: 모든 접근을 허용합니다. 클래스 외부에서 자유롭게 접근할 수 있습니다.
  1. protected: 클래스가 속한 패키지 내부와 해당 클래스를 상속받은 외부 패키지의 클래스에서만 접근이 가능합니다.
  1. default (package-private): 기본적으로 설정된 접근 제한자로, 동일한 패키지 내에서만 접근이 가능합니다.
  1. private: 해당 클래스에서만 접근이 가능하며, 외부에서의 직접적인 접근을 막습니다.
정보 은닉:
정보 은닉은 객체 내부의 상태를 외부에서 직접적으로 접근하지 못하도록 하고, 필요한 경우 메소드를 통해 간접적으로 접근하도록 하는 것을 의미합니다. 이를 위해 주로 private 접근 제한자를 사용하여 변수를 보호합니다. 예를 들어, 자동차 클래스에서 속도를 private으로 선언하고, 메소드를 통해 속도를 설정하거나 조회할 수 있도록 구현합니다.
javaCopy code public class Car { private int speed; // private으로 선언된 변수 // 메소드를 통한 간접 접근 public void setSpeed(int newSpeed) { if (newSpeed >= 0 && newSpeed <= 200) { this.speed = newSpeed; } else { System.out.println("Invalid speed"); } } public int getSpeed() { return this.speed; } }
위 예시에서 speed 변수는 private으로 선언되어 외부에서 직접적으로 접근할 수 없습니다. 대신, setSpeedgetSpeed 메소드를 통해 속도를 설정하고 조회할 수 있도록 구현되었습니다. 이로써 정보 은닉이 이루어져 외부에서의 잘못된 접근을 방지하고, 메소드를 통한 간접 접근으로 안전하게 상호작용이 가능해집니다.
 

추상화(Abstraction)

추상화는 특정한 개별 사물과 관련 없는 공통된 속성이나 동작 등을 도출하는 과정입니다. 이를 컴퓨터의 관점에서 생각하면 데이터나 프로세스를 의미가 비슷한 개념이나 표현으로 정의하는 것입니다. 예를 들어, 자동차 클래스를 추상화한다고 생각해봅시다. 자동차들은 연료를 사용하고, 속도를 조절할 수 있으며, 주행 중에는 방향을 바꿀 수 있습니다. 이 공통된 특성을 추출하여 자동차라는 클래스를 정의하는 것이 추상화입니다.
추상화를 통해 객체들이 가진 공통의 특성을 파악하고 불필요한 세부 사항을 제거하여 객체를 더 쉽게 이해하고 사용할 수 있도록 합니다.

추상화 예시

class Car { #fuel; // 연료 #speed; // 속도 #direction; // 방향 // 추상화된 동작 1 constructor() { this.#fuel = 100; this.#speed = 0; this.#direction = 'forward'; } // 추상화된 동작 2 accelerate() { this.#speed += 10; } // 추상화된 동작 3 turn(direction) { this.#direction = direction; } } // 추상화된 동작을 통해 자동차 객체 생성 const myCar = new Car(); // 추상화된 동작을 호출하여 가속 및 방향 전환 myCar.accelerate(); // 일정 속도로 가속 myCar.turn('left'); // 좌회전
위 예시에서 Car 클래스는 추상화된 속성인 연료(fuel), 속도(speed), 방향(direction)과 추상화된 동작인 생성(constructor), 가속(accelerate), 방향 전환(turn)을 가지고 있습니다. 이를 통해 자동차 객체를 생성하고 추상화된 동작을 호출하여 간편하게 자동차를 조작할 수 있습니다.

상속(Inheritance)

상속은 객체지향 프로그래밍에서 기존 클래스의 속성과 메서드를 그대로 물려받아 새로운 클래스를 생성하는 개념입니다. 이를 통해 코드의 재사용성을 높이고 새로운 기능을 추가하거나 기존 기능을 수정하여 확장할 수 있습니다. 상속 관계에서 기존 클래스를 부모 클래스 또는 상위 클래스라고 하고, 새롭게 생성되는 클래스를 자식 클래스 또는 하위 클래스라고 합니다.
상속의 주요 이유는 코드의 중복을 방지하고 유지보수성을 향상시키기 위함입니다. 부모 클래스에서 정의한 속성과 메서드를 자식 클래스에서 별도의 작업 없이 그대로 사용할 수 있기 때문입니다.

상속 예시

class Car { #fuel; // 연료 #speed; // 속도 #direction; // 방향 constructor() { this.#fuel = 100; this.#speed = 0; this.#direction = 'forward'; } accelerate() { this.#speed += 10; } turn(direction) { this.#direction = direction; } } class ElectricCar extends Car { #battery; // 배터리 용량 constructor() { super(); // 부모 클래스의 생성자 호출 this.#battery = 100; } // 추가된 동작 charge() { this.#battery += 10; } } // 전기차 객체 생성 const myElectricCar = new ElectricCar(); // 상속된 동작 호출 myElectricCar.accelerate(); // 전기차도 가속 가능 // 새로 추가된 동작 호출 myElectricCar.charge(); // 전기차만 충전 가능
위 예시에서 ElectricCar 클래스는 Car 클래스를 상속받았습니다. 이로 인해 ElectricCar 클래스는 Car 클래스의 속성과 메서드를 그대로 사용할 수 있으면서도, 새롭게 배터리 용량을 관리하는 동작을 추가로 정의할 수 있습니다.

다형성(Polymorphism)

다형성은 객체지향 프로그래밍에서 하나의 인터페이스나 메서드가 다양한 형태로 동작하는 능력을 말합니다. 다형성은 주로 상속을 기반으로 하며, 오버라이딩과 오버로딩을 통해 구현됩니다.

오버라이딩(Overriding)

오버라이딩은 부모 클래스에서 상속받은 메서드를 자식 클래스에서 재정의하여 사용하는 것입니다. 이를 통해 같은 이름의 메서드가 서로 다른 기능을 수행할 수 있습니다.
예를 들어, 자동차 클래스에서 startEngine 메서드가 있고, 전기차 클래스에서는 이 메서드를 재정의하여 전기 엔진을 시작하는 동작으로 변경할 수 있습니다.
class Car { startEngine() { console.log('일반 엔진 시작'); } } class ElectricCar extends Car { startEngine() { console.log('전기 엔진 시작'); } } const myCar = new ElectricCar(); myCar.startEngine(); // 전기 엔진 시작

오버로딩(Overloading)

자바스크립트는 명시적인 오버로딩을 지원하지는 않지만, 메서드의 매개변수나 인자의 개수 및 데이터 타입에 따라 다르게 동작하도록 설계할 수 있습니다.
예를 들어, 자동차 클래스에서 drive 메서드가 있고, 오버로딩을 통해 여러 형태로 사용할 수 있습니다.
class Car { drive() { console.log('주행 시작'); } drive(speed) { console.log(`속도 ${speed}km/h로 주행 시작`); } } const myCar = new Car(); myCar.drive(); // 주행 시작 myCar.drive(100); // 속도 100km/h로 주행 시작

다형성의 장점

  • 코드의 간결성
동일한 인터페이스로 다양한 객체를 처리할 수 있어 코드가 간결해집니다.
  • 재사용성
공통 인터페이스를 사용하므로 여러 객체에서 동일한 메서드나 속성을 사용할 수 있습니다.
  • 확장성
새로운 클래스를 추가해도 기존 코드에 영향을 덜 받아 유연한 확장이 가능합니다.

글을 마치며

객체지향 프로그래밍(OOP)은 소프트웨어 개발 패러다임 중 하나로, 현실 세계의 객체를 모델로 삼아 프로그램을 구현하는 방법입니다. 이를 자세히 설명하기 위해 자동차를 예시로 들어보겠습니다. 자동차 객체는 브랜드, 모델, 색상과 같은 속성을 가지며, 시동 켜기, 주행, 정지와 같은 기능을 수행할 수 있습니다.
객체지향 프로그래밍을 공부하면 코드의 재사용성이 향상되고 유지보수가 용이해집니다. 클래스와 객체를 사용하여 자동차의 엔진, 바퀴, 내부 시스템 등을 모듈화하고, 다형성을 활용하여 코드를 유연하게 작성할 수 있습니다.
또한, 상속을 통해 기존 자동차 클래스의 속성과 기능을 재사용하면서 새로운 자동차 모델을 만들 수 있습니다. 이러한 특성들을 이용하여 소프트웨어를 개발하면 코드의 가독성과 유지 보수성이 향상됩니다.
종합적으로 말하면, 객체지향 프로그래밍은 현실 세계의 객체를 모델로 삼아 효과적으로 소프트웨어를 개발하는 방법이며, 이를 통해 효율적이고 유연한 코드를 작성할 수 있습니다.

Reference