객체지향 프로그래밍(Object-Oriented Programming, OOP)은 소프트웨어 설계 패러다임 중 하나로, 데이터를 객체(object)라는 단위로 묶어 이를 중심으로 프로그램을 구성하는 방법입니다. OOP는 코드의 재사용성, 확장성, 유지보수성을 높이기 위해 도입된 개념으로, 주요 특징으로는 캡슐화, 상속, 다형성, 추상화가 있습니다.
객체는 상태(state)와 행동(behavior)을 가진 소프트웨어 구성 요소입니다. 상태는 객체의 속성을 나타내는 데이터(필드, 변수 등)로, 행동은 객체가 수행할 수 있는 동작(메서드, 함수 등)으로 정의됩니다.
예: 자동차 객체는 속성으로 색상, 모델, 속도를 가질 수 있고, 행동으로는 가속, 감속, 방향 전환 등을 가질 수 있습니다.
클래스는 객체를 생성하기 위한 설계도(blueprint)입니다. 객체는 클래스의 인스턴스(instance)로, 클래스는 객체의 속성과 행동을 정의합니다.
예: Car 클래스는 색상, 모델, 속도를 속성으로 가지며, 가속, 감속, 방향 전환 메서드를 가질 수 있습니다.
객체지향 프로그래밍의 주요 원칙
캡슐화는 객체의 데이터를 외부로부터 보호하고, 데이터에 접근하는 메서드를 통해서만 조작할 수 있도록 하는 원칙입니다. 이를 통해 데이터의 무결성을 유지하고, 객체 내부 구현을 숨겨 외부에서 객체를 쉽게 사용할 수 있도록 합니다.
접근 제어자(Access Modifiers): private, protected, public 등을 사용하여 클래스 멤버(필드, 메서드 등)의 접근 범위를 제어합니다.
예: private 접근 제어자를 사용하여 클래스의 속성을 외부에서 직접 접근하지 못하게 하고, public 메서드를 통해서만 속성에 접근하도록 합니다.
public class Car {
private String color;
private int speed;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
if (speed >= 0) {
this.speed = speed;
}
}
}
상속은 기존 클래스(부모 클래스, 슈퍼 클래스)를 확장하여 새로운 클래스(자식 클래스, 서브 클래스)를 만드는 원칙입니다. 자식 클래스는 부모 클래스의 속성과 행동을 물려받아 재사용할 수 있으며, 필요에 따라 새로운 속성과 행동을 추가하거나 변경할 수 있습니다.
상속을 통해 코드의 재사용성을 높이고, 계층 구조를 통해 클래스 간의 관계를 명확히 할 수 있습니다.
예: Vehicle 클래스를 상속받아 Car 클래스와 Bike 클래스를 만드는 경우, 두 클래스는 공통된 속성과 메서드를 상속받습니다.
public class Vehicle {
protected String color;
protected int speed;
public void start() {
System.out.println("Vehicle started");
}
public void stop() {
System.out.println("Vehicle stopped");
}
}
public class Car extends Vehicle {
private int doors;
public void setDoors(int doors) {
this.doors = doors;
}
public int getDoors() {
return doors;
}
@Override
public void start() {
System.out.println("Car started");
}
}
다형성은 동일한 메시지에 대해 서로 다른 방식으로 응답할 수 있는 능력을 의미합니다. 이는 메서드 오버로딩(Method Overloading)과 메서드 오버라이딩(Method Overriding)을 통해 구현됩니다.
메서드 오버로딩: 같은 이름의 메서드를 여러 개 정의하지만, 매개변수의 타입이나 개수가 다릅니다.
메서드 오버라이딩: 부모 클래스의 메서드를 자식 클래스에서 재정의하여 사용할 수 있습니다.
예: Animal 클래스의 makeSound 메서드를 Dog 클래스와 Cat 클래스에서 다르게 구현할 수 있습니다.
public class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
추상화는 복잡한 시스템에서 필요한 부분만을 모델링하여 단순화하는 원칙입니다. 이를 통해 불필요한 세부 사항을 숨기고 중요한 속성 및 동작에 집중할 수 있습니다.
추상 클래스(Abstract Class)와 인터페이스(Interface)를 사용하여 추상화를 구현합니다.
추상 클래스: 일부 메서드가 구현되지 않은 클래스로, 자식 클래스에서 이를 구현해야 합니다.
인터페이스: 모든 메서드가 구현되지 않은 추상 타입으로, 이를 구현하는 클래스에서 모든 메서드를 구현해야 합니다.
예: Shape 추상 클래스를 만들고, 이를 상속받는 Circle과 Rectangle 클래스를 구현할 수 있습니다.
public abstract class Shape {
protected String color;
public abstract double calculateArea();
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
상속을 통해 기존 코드를 재사용하고, 중복을 줄일 수 있습니다.
모듈화된 코드를 작성하여 여러 프로젝트에서 재사용할 수 있습니다.
유지보수성:
캡슐화를 통해 객체 내부 구현을 숨기고, 인터페이스를 통해 상호작용하도록 함으로써 코드의 수정이 용이합니다.
코드 변경이 다른 부분에 미치는 영향을 최소화할 수 있습니다.
새로운 기능을 추가할 때 기존 코드를 변경하지 않고, 새로운 클래스와 메서드를 추가하여 확장할 수 있습니다.
다형성을 활용하여 다양한 객체를 동일한 방식으로 처리할 수 있습니다.
현실 세계의 개체와 상호작용을 객체와 클래스 형태로 쉽게 모델링할 수 있습니다.
복잡한 시스템을 더 쉽게 이해하고 설계할 수 있습니다.
객체지향 프로그래밍은 이러한 개념과 원칙을 통해 복잡한 소프트웨어 시스템을 더 쉽게 설계, 구현 및 유지보수할 수 있도록 도와줍니다. Java는 객체지향 프로그래밍을 지원하는 대표적인 언어 중 하나로, 이러한 개념을 효과적으로 활용할 수 있는 다양한 기능과 도구를 제공합니다.