객체 지향 프로그래밍을 이해하기 위해서는, 먼저 근간이 되는 절차 지향 프로그래밍과, 이를 대표하는 초창기 컴퓨터 언어인 C언어를 살펴보는 것이 중요하다.
객체 지향 프로그래밍은 절차 지향 프로그래밍으로 부터 탄생했으며, 절차 지향 프로그래밍의 여러 단점을 개선하고 객체지향 프로그래밍만의 장점과 특징을 가지고 있다.
이를 설명하기 위해서 절차 지향 프로그래밍부터 단계적으로 살펴보자.
절차 지향 프로그래밍 (POP - Procedural Oriented Programming)이란?
절차 지향 프로그래밍은 문제를 해결하기 위해 순차적으로 작업을 수행하는 방식에 기반한 프로그래밍 패러다임이다.
프로그램의 실행 절차(Procedure)에 초점을 맞춘 프로그래밍 패러다임인 만큼 함수를 중심으로 구성되며, 각각의 함수가 프로그램의 작업 단위를 담당하게 된다.
절차 지향 프로그래밍의 특징은 다음과 같다.
- 프로그램을 단계별로 함수와 절차로 나누어 처리한다.
- 데이터를 조작하는 함수들이 따로 정의되며, 데이터와 로직이 분리된다 => 해당 데이터는 함수 내에서 처리한다.
- 프로그램의 흐름이 함수 호출을 따라간다.
- 코드가 실행되는 순서가 아주 중요하며 명령어를 기반으로 처리된다.
절차 지향 프로그래밍과 C언어의 관계는?
1969년에 "데니스 리치"라는 미국의 컴퓨터 과학자이자 프로그래머는 어셈블리 언어*로 만들어진 운영체제인 "Unix"를 개발했는데, 초창기 버전이고 어셈블리 언어로 개발된 만큼 다른 하드웨어에서 범용적으로 사용하기 어려웠다.
이후 데니스 리치는 Unix를 대폭 수정하기 위해 1972년 기존의 B언어를 확장해서 C언어를 개발하고 이때 개발한 C언어를 통해 다음과 같은 기능들을 구현할 수 있었다.
- 하드웨어 제어 가능: 시스템 프로그래밍에 필요한 저수준 작업을 지원
- 고수준 언어의 유연성: 어셈블리보다 작성하기 쉽고, 유지보수가 용이
- 이식성: C 컴파일러만 새 시스템에 맞춰 작성하면, Unix를 다른 하드웨어에서도 실행 가능
1973년 데니스 리치는 C언어를 이용해서 Unix를 재작성하였다.
Unix는 기존 어셈블리 언어로 개발된 운영체제들 보다 이식성과 유지 보수성, 모듈화, 효율성 등 여러가지 이점을 가지게 되었고
C언어는 거의 모든 프로그래밍 언어에 영향을 끼치게 된다.
하지만 C언어에는 아직 객체 라는 개념이 없어 대규모 소프트웨어 개발이나 복잡한 소프트웨어에서 비교적 재사용성이 부족하다는 약점을 가지고 있었다.
어셈블리 언어란? : 어셈블리 언어(Assembly Language)는 프로그래밍 언어의 종류로, 컴퓨터 하드웨어가 직접 이해할 수 있는 **기계어(Machine Code)**에 가까운 사람이 이해하기 쉬운 형식의 명령어로 구성된 저수준 언어다.
C++의 등장
C++은 덴마크의 컴퓨터 과학자 '비야네 스트로스트룹(Bjarne Stroustrup)'가 C언어를 바탕으로 제작한 언어로 C 언어에 객체 지향 프로그래밍(OOP) 개념을 도입하여 발전시킨 언어다.
이를 통해 C++은 기존 C언어와 완전하게 호환되지만 다음과 같은 객체 지향 설계와 관련된 추가적인 특징을 가지게 된다.
C++의 주요 특징
1. 클래스와 객체
- 데이터를 캡슐화하여 코드의 재사용성과 유지보수성을 높임.
- 클래스를 사용해 데이터를 구조화하고, 객체는 이 클래스의 인스턴스로 사용됨.
2. 상속(Inheritance)
- 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)에서 상속 받아 사용할 수 있음.
- 이를 통해 코드 재사용성과 확장성이 올라감.
3. 다형성(Polymorphism)
- 함수 오버로딩(Function Overloading): 같은 이름의 함수가 다른 매개변수로 동작.
- 가상 함수(Virtual Function): 부모 클래스의 메서드를 자식 클래스에서 재정의할 수 있음.
C++은 상당한 장점을 가진 언어이면서 객체 지향 프로그래밍의 대중화에 기여했지만, 여전히 복잡한 문법과 메모리 관리로 인해 진입 장벽이 높은 언어이기도 했다.
비숙련자의 경우에 메모리 관리를 잘못해서 메모리 누수가 발생하거나 다중 상속이 반복되면서 코드의 복잡도가 기하급수적으로 증가하기도 했는데 이는 Java와 C#이 개발되는 등장 배경이 된다.
Java와 C#의 등장
Java의 등장 (1995년)
Java는 1995년에 썬 마이크로시스템즈(Sun Microsystems)사의 제임스 고슬링(James Gosling)에 의해 개발되었다.
2010년에 오라클이 썬 마이크로시스템즈(Sun Microsystems)를 인수하면서 현재는 오라클이 배포하는 프로그램의 언어로 자주 사용되는 언어다.
초창기 Java는 C++의 대안으로 설계되었으며, 플랫폼 독립성과 안정성을 강조하는 언어로 발전한다.
Java의 특징은 다음과 같다.
Java의 특징
- 순수 객체 지향 언어
- 객체 지향 프로그래밍(OOP)이 언어 설계의 핵심 철학.
- 모든 것이 객체로 표현되며, OOP의 원칙(캡슐화, 상속, 다형성)을 준수.
- GC(Garbage Collector)
- 메모리 관리를 자동화하여 개발자의 부담을 줄임.
- 메모리 할당 후 delete 호출 없이, 더 이상 참조되지 않는 객체를 자동으로 회수.
- 플랫폼 독립성
- "Write Once, Run Anywhere" 철학: 코드가 한번 작성되면 어떤 운영체제에서도 실행 가능.
- **JVM(Java Virtual Machine)**을 통해 바이트코드 형태로 실행.
- C++의 대안
- 복잡한 C++ 문법을 단순화하고, 메모리 관리와 다중 상속 문제를 제거.
- 클래스 기반 설계와 인터페이스를 통해 객체 지향을 더 쉽게 구현.
Java의 한계
- 성능
- JVM을 사용하는 실행 방식 때문에 C++에 비해 느린 성능.
- 플랫폼 독립성을 위해 하드웨어에 직접 접근하는 기능 제한.
- 제약된 네이티브 코드 활용
- 플랫폼 독립성을 강조하다 보니, 운영체제별 고유 기능 활용이 어렵거나 복잡.
C#의 등장 (2000년)
C#은 2000년에 마이크로소프트(Microsoft)가 .NET 플랫폼과 함께 출시한 언어로, Java와 유사한 설계를 기반으로 한 언어다.
안정성과 성능을 모두 충족시키며 Windows 생태계와의 통합에 중점을 둔 언어라고 광고했지만 초창기에는 Java와 유사하고 Windows 환경에서만 사용이 가능하다는 부정적인 평이 대다수였다.
이후에 여러 기능이 업데이트 되고 .NET 프레임워크를 오픈소스로 공개하면서 여러 분야에서 사용이 가능한 언어로 탈바꿈한다.
C#의 특징은 다음과 같다.
C#의 특징
- 순수 객체 지향 언어
- Java와 마찬가지로 객체 지향 설계가 중심.
- 인터페이스 기반 다중 상속 지원, 캡슐화와 상속, 다형성 구현.
- GC(Garbage Collector)
- 메모리 관리를 자동화하여, 메모리 누수와 같은 문제를 줄임.
- Java와 비슷하지만 .NET 플랫폼 최적화 덕분에 성능이 더 우수.
- .NET 플랫폼
- 강력한 라이브러리와 도구 지원으로 생산성이 높음.
- **CLR(Common Language Runtime)**에서 실행되며, 다양한 언어(C++, VB.NET 등)와 상호작용 가능.
- Windows 생태계 최적화
- Windows API와의 깊은 통합으로 네이티브 애플리케이션 개발에 강점.
- 최근에는 .NET Core와 .NET 5/6으로 발전하며 크로스 플랫폼 지원도 가능.
- 현대적 기능 추가
- LINQ, async/await, 람다 표현식 등 현대적인 프로그래밍 스타일 지원.
- 최신 버전은 C# 11까지 발전하며 지속적으로 기능을 개선.
C#의 한계
- 초기 플랫폼 의존성
- C#은 초기에 Windows 생태계에 의존적이었으며, 다른 플랫폼 지원이 제한적이었음.
- .NET Core 등장 이후 크로스 플랫폼 지원으로 극복.
- Java와의 유사성
- 초기에는 Java의 단순한 복제라는 비판도 받았지만, 이후 고유 기능과 장점으로 차별화.
객체 지향 프로그래밍 (Object - Oriented Programming)
결국 객체 지향 프로그래밍이란 '객체(Object)'라는 개념을 중심으로 문제를 모델링하고, 데이터를 처리하며, 프로그램을 설계하는 프로그래밍 패러다임이다.
절차 지향 프로그래밍에서 함수 중심으로 구성되며 발생하는 여러가지 단점(재사용성, 확장성)을 보완하기 위해서 Java와 C#등이 객체 지향 프로그래밍의 개념을 가지고 등장하였고, 객체 지향 프로그래밍 언어들은 다음과 같은 네 가지의 중요한 특징을 가진다.
1. 캡슐화(Encapsulation)
- 데이터(속성)와 그 데이터를 다루는 동작(메서드)을 하나의 객체로 묶어 관리한다.
- 데이터 보호를 위해 객체 외부에서 직접 접근하지 못하게 하고, 필요한 경우 메서드를 통해 접근을 허용함.
- 장점 :
- 코드 보안성 증가.
- 객체 내부 구현을 숨겨 복잡성을 줄임
C# 코드 예시
public class Person
{
private string name; // 캡슐화된 데이터
public string GetName() => name; // 메서드를 통해 데이터 접근
public void SetName(string value) => name = value;
}
2. 상속(Inheritance)
- 기존 클래스를 기반으로 새로운 클래스를 생성, 부모 클래스의 속성과 메서드를 재사용 가능.
- 코드 중복을 줄이고, 확장성을 높임.
- 장점 :
- 기존 코드를 재사용하여 생산성 증가.
- 계층 구조로 논리적 설계 가능.
C# 코드 예시
public class Animal
{
public void Eat() => Console.WriteLine("Eating!");
}
public class Dog : Animal
{
public void Bark() => Console.WriteLine("Barking!");
}
3. 다형성(Polymorphism)
- 동일한 이름의 메서드나 연산자가 클래스에 따라 다르게 동작.
- 오버라이딩(Overriding): 부모 클래스 메서드를 자식 클래스에서 재정의.
- 오버로딩(Overloading): 같은 이름의 메서드를 매개변수에 따라 다르게 정의.
C# 코드 예시
// 부모 클래스
public class Animal
{
// 부모 클래스의 가상 메서드 (다형성을 위해 virtual 키워드 사용)
public virtual void Speak()
{
Console.WriteLine("The animal makes a sound.");
}
}
// 자식 클래스: Dog
public class Dog : Animal
{
// 부모의 Speak 메서드를 재정의 (Overriding)
public override void Speak()
{
Console.WriteLine("The dog barks.");
}
}
// 자식 클래스: Cat
public class Cat : Animal
{
// 부모의 Speak 메서드를 재정의 (Overriding)
public override void Speak()
{
Console.WriteLine("The cat meows.");
}
}
public class Program
{
public static void Main(string[] args)
{
// 부모 타입의 리스트에 다양한 자식 클래스 객체를 저장
Animal[] animals = { new Dog(), new Cat(), new Animal() };
foreach (var animal in animals)
{
// 다형성을 통해 각 객체에 맞는 메서드 호출
animal.Speak();
}
/*
출력 결과:
The dog barks.
The cat meows.
The animal makes a sound.
*/
}
}
4. 추상화(Abstraction)
- 중요한 정보만 노출하고, 불필요한 구현 세부 사항은 숨김.
- '추상 클래스(Abstract Class)'나 '인터페이스(Interface)'를 사용해 설계의 유연성과 확장성 제공.
C# 코드 예시
using System;
// 추상 클래스
public abstract class Shape
{
// 추상 메서드: 각 도형의 넓이를 계산 (구현되지 않음)
public abstract double GetArea();
// 일반 메서드: 도형의 이름을 출력 (구현 제공)
public void PrintShapeName()
{
Console.WriteLine("This is a shape.");
}
}
// 원을 표현하는 클래스
public class Circle : Shape
{
private double radius;
// 생성자를 통해 반지름 초기화
public Circle(double radius)
{
this.radius = radius;
}
// 추상 메서드를 재정의하여 원의 넓이 계산
public override double GetArea()
{
return Math.PI * radius * radius;
}
}
// 직사각형을 표현하는 클래스
public class Rectangle : Shape
{
private double width;
private double height;
// 생성자를 통해 너비와 높이 초기화
public Rectangle(double width, double height)
{
this.width = width;
this.height = height;
}
// 추상 메서드를 재정의하여 직사각형의 넓이 계산
public override double GetArea()
{
return width * height;
}
}
public class Program
{
public static void Main(string[] args)
{
// 부모 클래스(Shape) 타입으로 다양한 도형 객체를 관리
Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };
foreach (var shape in shapes)
{
// 각 도형의 넓이를 계산
shape.PrintShapeName();
Console.WriteLine($"Area: {shape.GetArea()}");
}
/*
출력 결과:
This is a shape.
Area: 78.53981633974483
This is a shape.
Area: 24
*/
}
}
OOP와 절차 지향 프로그래밍의 차이
특징 | 절차 지향(Procedural Programming) | 객체 지향(Object-Oriented Programming) |
구조화 | 함수 중심으로 구성 | 객체 중심으로 구성 |
데이터 보호 | 데이터와 함수가 분리되어 있음 | 데이터와 메서드가 객체 안에 캡슐화 |
코드 재사용 | 함수 복사를 통한 재사용 | 상속을 통한 재사용 가능 |
확장성 | 새로운 기능 추가 시 코드 변경 필요 | 기존 코드 수정 없이 클래스 추가로 확장 가능 |
정리
- 객체 지향 프로그래밍은 절차 지향 프로그래밍을 개선하기 위해서 개발된 언어다.
- 객체 지향 프로그래밍은 "객체(Object)"를 중심으로 데이터를 설계하고 상호작용한다.
- 객체 지향의 주요 특징으로는 캡슐화, 상속, 다형성, 추상화가 있다.
- 덕분에 코드 재사용성, 확장성, 유지보수성을 크게 향상되어 대규모 소프트웨어 설계에 적합한 패러다임으로 자리잡는다.
사진 출처 : https://www.geeksforgeeks.org/oops-interview-questions/
https://www.zdnet.com/article/without-dennis-ritchie-there-would-be-no-jobs/
'CS(Computer Science)' 카테고리의 다른 글
부동 소수점(floating point) 이란? (0) | 2024.12.16 |
---|---|
메모리 정렬과 패딩 (0) | 2024.12.05 |