캡슐화
캡슐화는 개체의 데이터(멤버 변수)와 그 데이터를 처리하는 동작(메서드)을 하나로 묶는 개념이다. 이걸 통해 내부 데이터는 외부로부터 보호되고, 사용자는 클래스 내부의 동작에 대해 신경 쓸 필요가 없다. 마치 함수를 사용할 때 함수 내부 로직을 알 필요가 없는 것처럼, 클래스도 그 안의 세부 사항을 몰라도 사용할 수 있게 된다.
public class Person {
// private으로 멤버 변수를 선언해 외부에서 접근하지 못하게 한다.
private String name;
private int age;
// 생성자를 통해 데이터를 설정
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 외부에서 접근 가능한 메서드 (Getter)
public String getName() {
return name;
}
public int getAge() {
return age;
}
// 외부에서 데이터를 변경할 수 있는 메서드 (Setter)
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if (age > 0) {
this.age = age;
}
}
}
위 예시에서 name
과 age
는 private
으로 선언되어 외부에서 직접 접근할 수 없고, getName()
과 setAge()
같은 메서드를 통해서만 데이터를 다룬다. 이렇게 하면 데이터를 보호할 수 있으며, 잘못된 값이 들어가는 걸 방지할 수 있다.
이 캡슐화 개념은 추상화로 이어지기도 하는데, 중요한 건 객체의 세부 사항이 아닌 제공하는 기능에만 집중할 수 있게 해준다는 점이다. 그래서 코드를 짤 때, 함수를 분리했던 원칙을 클래스에 그대로 적용하는 게 좋다. 중복된 코드가 있으면 그걸 private 메서드로 묶어서 외부로부터 숨기는 방식으로 관리하면 된다.
추상화
추상화는 조금 더 높은 차원의 개념이다. 주로 두 가지 관점에서 이해할 수 있는데, 하나는 추상 자료형 관점이고, 다른 하나는 절차적 데이터 추상화 관점이다.
추상 자료형 관점에서는 사용자가 클래스 안의 멤버 변수가 무엇인지 정확히 몰라도 상관없다. 그냥 클래스를 하나의 자료형처럼 사용할 수 있다는 점이 중요하다. 클래스로부터 객체를 만들고, 그 객체로 기능을 수행하면 된다. 즉, 사용자는 클래스가 제공하는 메서드만 신경 쓰면 되고, 내부 데이터나 동작이 어떻게 구현되어 있는지는 몰라도 된다는 얘기다.
abstract class Animal {
// 추상 메서드
public abstract void makeSound();
}
class Dog extends Animal {
// 추상 메서드 구현
public void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
// 추상 메서드 구현
public void makeSound() {
System.out.println("Meow");
}
}
위 코드에서 Animal
클래스는 추상 클래스이며, makeSound()
라는 추상 메서드를 가진다. Dog
와 Cat
은 각각 이 추상 메서드를 구현하여 각자의 방식으로 소리를 낸다. 이처럼 추상화는 사용자가 Dog
와 Cat
이 각각 어떤 방식으로 소리를 내는지 몰라도, makeSound()
라는 메서드를 통해 소리를 낼 수 있다는 점에 집중하게 해준다.
절차적 데이터 추상화 관점에서는 데이터를 직접적으로 다루기보다는 메서드를 호출해서 데이터를 처리하게 된다.
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
}
여기서 BankAccount
클래스는 balance
라는 데이터를 직접적으로 접근하지 않고, deposit()
과 withdraw()
메서드를 통해서만 접근할 수 있다. 이런 방식으로 데이터는 캡슐화되고, 메서드를 통해 간접적으로 처리된다.
이 방식은 객체지향 프로그래밍(OOP)의 핵심 개념으로, 데이터를 메서드로 감싸는 것을 중시한다. 소위 동작적 객체(behavioral objects) 진영에서는 객체가 제공하는 행동에만 집중하고, 데이터는 그 행동을 통해 간접적으로 접근하는 방식을 선호한다.
추상화의 단점
하지만 추상화가 늘 좋은 것만은 아니다. 특히, 동작 없이 데이터만 존재하는 클래스는 굳이 클래스로 만들 필요가 없을 때도 있다. 웹 프로그래밍에서 자주 볼 수 있는 DTO(Data Transfer Object) 같은 게 좋은 예다.
public class UserDTO {
public String username;
public String email;
public UserDTO(String username, String email) {
this.username = username;
this.email = email;
}
}
UserDTO
는 단순히 데이터를 전달하기 위한 목적으로만 사용되며, 내부에 복잡한 로직이나 메서드를 가질 필요가 없다. 이런 경우에는 public
데이터를 사용해도 무방하다. 추상화가 과도해지면 코드만 불필요하게 늘어나게 된다.
또, 추상화의 가장 큰 문제는 ‘얼마나 추상화할 것인가’에 대한 뚜렷한 기준이 없다는 점이다. 사람마다 추상화의 정도를 다르게 이해할 수 있고, 이는 나중에 다형성, 상속, 인터페이스 같은 개념에서 더 큰 문제가 될 수 있다. 추상화를 어떻게 해야 할지에 대한 명확한 기준이 없기 때문에 코드를 이해하고 유지보수하는 데 어려움을 겪을 수도 있다.
캡슐화와 추상화는 객체지향 프로그래밍의 기본이 되는 개념들이다. 캡슐화는 데이터와 동작을 한데 묶어 외부로부터 보호하는 역할을 하며, 추상화는 사용자가 세부 사항을 알 필요 없이 클래스를 사용할 수 있도록 하는 방식이다. 하지만 추상화의 범위와 정도는 적절하게 조절해야 한다는 점도 항상 염두에 둬야 한다.
출처
- 개체지향 프로그래밍 및 설계 - 유데미 강의