[객체 지향 프로그래밍 입문: 객체의 특징과 클래스 구분]
객체 지향 프로그래밍(Object-oriented Programming, 이하 OOP) 개요
객체 지향 프로그래밍(OOP)은 절차 지향적 프로그래밍 기법을 대체하는 새로운 패러다임으로, 현재 소프트웨어 개발에서 가장 널리 사용되는 개념이다. OOP의 핵심은 객체(Object)이며, 각각의 객체는 사용자에게 공개된 특정 기능과 내부적으로 감춰진 구현을 가지고 있다. 개발자는 원하는 기능을 수행하는 객체를 불러와 내부 구현을 알 필요 없이 해당 객체를 활용하여 프로그램을 개발할 수 있다.
전통적인 절차 지향 프로그래밍(Procedural Programming)에서는 먼저 문제 해결을 위한 절차(Algorithm)를 정의하고, 그 후 데이터를 저장하는 방식을 결정한다. 대표적인 예로 파스칼 언어(Pascal Language)가 있다. 그러나 OOP에서는 이와 반대로 데이터를 먼저 정의한 후, 데이터를 조작하는 알고리즘을 구성하는 방식을 따른다.
절차 지향 프로그래밍은 작은 문제를 해결하는 데 효과적이지만, 객체(Object)는 보다 복잡한 문제를 해결하는 데 적합하다. OOP에서는 객체를 중심으로 프로그램을 구성하며, 객체 간의 관계를 정의하고 협업할 수 있도록 설계한다.
4.1.1 클래스(Class)
클래스란?
클래스(Class)는 객체를 생성하는 틀(Template) 또는 청사진(Blueprint) 역할을 한다. 쉽게 말해, 클래스는 일종의 쿠키 커터(Cookie Cutter, 쿠키 틀)와 같으며, 객체는 클래스에 의해 특정한 형태로 생성된다. 따라서 동일한 클래스를 기반으로 여러 개의 객체를 생성할 수 있으며, 이를 클래스의 인스턴스(Instance)를 생성한다고 한다.
캡슐화(Encapsulation)
캡슐화(Encapsulation)는 OOP의 핵심 개념 중 하나로, 데이터와 기능(메서드)을 하나의 패키지로 묶어 외부에서 직접 접근할 수 없도록 보호하는 기법이다. 캡슐화를 통해 객체의 내부 구현을 감추고, 객체의 상태(State)는 외부에서 직접 변경할 수 없도록 제한된다.
객체의 내부 데이터는 인스턴스 필드(Instance Field, 멤버 변수)라고 하며, 데이터를 처리하는 행동은 메서드(Method)라고 한다. 하나의 클래스를 기반으로 여러 개의 객체를 생성할 수 있으며, 각 객체는 고유한 상태(State)를 가지며, 실행되는 동안 상태가 변할 수 있다.
캡슐화의 중요성
캡슐화를 올바르게 구현하려면 외부에서 직접적으로 인스턴스 필드에 접근할 수 있도록 허용해서는 안 된다. 프로그램은 반드시 객체의 메서드를 통해서만 데이터에 접근하고 조작해야 한다.
이를 통해 클래스 내부의 데이터 구조가 변경되더라도, 외부에서는 동일한 방식으로 데이터를 접근할 수 있다. 즉, 프로그램의 안정성을 유지하면서도 내부 구현을 유연하게 변경할 수 있다.
4.1.2 객체(Object)
객체 지향 프로그래밍을 하기 위해서는, 객체의 세 가지 핵심 특성을 이해해야 한다.
객체의 3가지 특성
- 행동(Behavior)
- 객체가 수행할 수 있는 동작, 즉 어떤 메서드를 적용할 수 있는지를 의미한다.
- 예를 들어, 자동차(Car) 객체는
start(),accelerate()등의 동작을 가질 수 있다.
- 상태(State)
- 객체가 가지고 있는 데이터(속성, 변수)로, 현재 상태를 나타낸다.
- 예를 들어, 자동차(Car) 객체의
speed값은 가속할 때마다 변한다.
- 정체성(Identity)
- 동일한 클래스로 생성된 객체라 하더라도, 각 객체는 고유한 존재로 구별된다.
- 예를 들어, 동일한
Car클래스를 기반으로 여러 자동차 객체를 생성하더라도, 각각의 자동차는 서로 다른 개체로 구분된다.
객체의 행동(Behavior)
정의
객체가 수행할 수 있는 동작(Behavior)을 의미하며, 일반적으로 메서드(Method)로 정의된다.
역할
- 특정 객체가 어떤 작업을 수행할 수 있는지를 결정한다.
- 객체의 상태(State)를 변화시킬 수 있다.
예제 코드
class Car {
void start() {
System.out.println("자동차에 시동을 겁니다.");
}
void accelerate() {
System.out.println("자동차가 가속합니다.");
}
}
객체의 상태(State)
정의
객체가 가지고 있는 데이터(속성, 변수)로, 객체의 현재 상태를 나타낸다.
역할
- 객체의 행동(메서드)에 따라 상태가 변할 수 있다.
예제 코드
class Car {
int speed = 0;
void accelerate() {
speed += 10;
System.out.println("현재 속도: " + speed + "km/h");
}
}
객체의 정체성(Identity)
정의
같은 클래스로부터 생성된 객체라 하더라도, 각각의 객체는 고유한 정체성을 가지며, 서로 다른 객체로 구별된다.
역할
- 동일한 클래스를 기반으로 생성된 객체들이 서로 다름을 보장한다.
예제 코드
public class Main {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
System.out.println(car1 == car2); // false (서로 다른 객체)
}
}
객체의 상태와 행동의 상호작용
객체의 상태(State)와 행동(Behavior)은 서로 영향을 미칠 수 있다.
예를 들어, 주문(Order) 객체의 상태가 "배송 중"이나 "결제 완료"라면 더 이상 상품을 추가하거나 삭제할 수 없다. 즉, 객체의 상태에 따라 행동이 제한될 수도 있다.
class Order {
private String status = "비어 있음";
void addItem() {
if (status.equals("배송 중") || status.equals("결제 완료")) {
System.out.println("상품을 추가할 수 없습니다.");
} else {
System.out.println("상품을 추가하였습니다.");
}
}
void setStatus(String status) {
this.status = status;
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
order.addItem(); // 상품을 추가할 수 있습니다.
order.setStatus("배송 중");
order.addItem(); // 상품을 추가할 수 없습니다.
}
}
4.1.3 클래스를 구별하는 방법
객체 지향 프로그래밍에서는 프로그램을 구성하는 다양한 개체들을 객체(Object)로 모델링한다. 하지만 무작정 객체를 나열하는 것이 아니라 적절한 기준에 따라 클래스로 구별해야 한다.
절차 지향 프로그래밍과 객체 지향 프로그래밍의 차이
전통적인 절차 지향 프로그래밍(Procedural Programming)에서는 프로그램의 실행 흐름이 TOP(최상위 지점)에서 시작된다.
예를 들어, C 언어에서는 항상 main() 함수에서 프로그램이 시작되며, 순차적으로 코드가 실행된다.
그러나 객체 지향 프로그래밍(OOP)에서는 실행 흐름이 특정한 "TOP"에서 시작되지 않는다.
어떤 객체든 프로그램의 실행을 시작할 수 있으며, 실행의 흐름은 객체들 간의 메서드 호출 및 메시지 전달을 통해 동적으로 결정된다.
즉, OOP는 데이터를 중심으로 프로그램을 구성하며, 데이터를 표현하는 클래스를 먼저 정의한 후 그 클래스에 적절한 메서드를 추가하는 방식을 따른다.
클래스를 구별하는 방법
클래스를 정의하는 가장 간단한 방법은 문제에서 "명사"를 찾는 것이다.
- 예를 들어, 온라인 쇼핑 시스템을 설계한다고 가정하자.
- 명사 → 클래스
Item(상품)Order(주문)ShippingAddress(배송지)Payment(결제)Account(계정)
- 명사 → 클래스
이제 클래스의 동작을 정의하기 위해 동사를 찾아 메서드로 구현한다.
- 예를 들어:
- Item 객체는 Order에 "추가"될 수 있다. →
addItem() - Order 객체는 "배송 중" 상태가 될 수 있다. →
shipOrder() - Payment 객체는 Order를 "승인"할 수 있다. →
approvePayment()
- Item 객체는 Order에 "추가"될 수 있다. →
이처럼 객체와 클래스의 구별은 "명사-동사" 방식으로 쉽게 식별할 수 있다.
예제 코드
아래 코드에서 Order 클래스는 Item 객체를 관리한다.
import java.util.ArrayList;
import java.util.List;
class Item {
private String name;
public Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Order {
private List<Item> items = new ArrayList<>();
void addItem(Item item) {
items.add(item);
System.out.println(item.getName() + "이(가) 주문에 추가되었습니다.");
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
Item item1 = new Item("노트북");
Item item2 = new Item("스마트폰");
order.addItem(item1);
order.addItem(item2);
}
}
출력 결과
노트북이 주문에 추가되었습니다.
스마트폰이 주문에 추가되었습니다.
이처럼 클래스를 구별하는 방법을 올바르게 적용하면, 명확하고 확장 가능한 설계를 할 수 있다.
4.1.4 클래스 간의 관계
객체 지향 프로그래밍에서는 클래스들 간의 관계를 정의하는 것이 중요하다.
클래스 간의 관계는 크게 세 가지 유형으로 구분된다.
- 의존(Dependence) - "uses-a" 관계
- 집합(Aggregation) - "has-a" 관계
- 상속(Inheritance) - "is-a" 관계
1) 의존(Dependence) - "uses-a" 관계
한 클래스가 다른 클래스의 메서드를 호출할 때 발생하는 관계이다.
예를 들어, Order 클래스는 Account 클래스를 사용하여 결제 정보를 확인해야 한다.
class Account {
void charge() {
System.out.println("결제가 처리되었습니다.");
}
}
class Order {
void processPayment(Account account) {
account.charge();
}
}
public class Main {
public static void main(String[] args) {
Account account = new Account();
Order order = new Order();
order.processPayment(account);
}
}
출력 결과
결제가 처리되었습니다.
하지만 Item 클래스는 Account 클래스와 의존 관계가 없다. 이는 Item이 주문 시 고객 정보를 직접 필요로 하지 않기 때문이다.
2) 집합(Aggregation) - "has-a" 관계
한 클래스가 다른 클래스를 포함(소유)할 때 발생하는 관계이다.
예를 들어, Order 클래스는 여러 개의 Item을 포함할 수 있다.
import java.util.ArrayList;
import java.util.List;
class Item {
private String name;
public Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Order {
private List<Item> items = new ArrayList<>();
void addItem(Item item) {
items.add(item);
System.out.println(item.getName() + "이(가) 주문에 추가되었습니다.");
}
}
public class Main {
public static void main(String[] args) {
Order order = new Order();
Item item1 = new Item("책");
Item item2 = new Item("키보드");
order.addItem(item1);
order.addItem(item2);
}
}
출력 결과
책이 주문에 추가되었습니다.
키보드가 주문에 추가되었습니다.
3) 상속(Inheritance) - "is-a" 관계
한 클래스가 다른 클래스를 확장(extend)할 때 발생하는 관계이다.
예를 들어, RushOrder 클래스는 Order 클래스를 상속받을 수 있다.
class Order {
void process() {
System.out.println("주문을 처리합니다.");
}
}
class RushOrder extends Order {
@Override
void process() {
System.out.println("빠른 배송 주문을 처리합니다.");
}
}
public class Main {
public static void main(String[] args) {
Order normalOrder = new Order();
Order rushOrder = new RushOrder();
normalOrder.process();
rushOrder.process();
}
}
출력 결과
주문을 처리합니다.
빠른 배송 주문을 처리합니다.
이처럼 클래스 간의 관계를 적절히 활용하면 객체 간의 구조를 더욱 명확하게 설계할 수 있다.
2025.03.08 - [Core Java/Chapter 4] - 4.2 표준 라이브러리 클래스 사용하기
4.2 표준 라이브러리 클래스 사용하기
4.2 표준 라이브러리 클래스 사용하기(Using Predefined Classes)자바에서는 미리 정의된 표준 라이브러리 클래스(Predefined Classes)를 제공하며, 이를 통해 개발자는 직접 구현하지 않아도 다양한 기능을
choosla.tistory.com
'Core Java > Chapter 4' 카테고리의 다른 글
| 코어자바(Core Java) 12판 Chapter 4 리뷰 : 4.6 객체 생성 (0) | 2025.03.08 |
|---|---|
| 코어자바(Core Java) 12판 Chapter 4 리뷰 : 4.5 메소드 매개변수 (0) | 2025.03.08 |
| 코어자바(Core Java) 12판 Chapter 4 리뷰 : 4.4 정적 필드와 메소드 (0) | 2025.03.08 |
| 코어자바(Core Java) 12판 Chapter 4 리뷰 : 4.3 자신만의 클래스 정의하기 (0) | 2025.03.08 |
| 코어자바(Core Java) 12판 Chapter 4 리뷰 : 4.2 표준 라이브러리 클래스 사용하기 (0) | 2025.03.08 |