[Java] 기초 - 객체 설계 단계별 발전

이지연·2025년 12월 16일

개요

아래의 내용은 java_grammer 레파지토리 C02MethodClass 디렉터리에 저장되어있는 내용을 정리하였다.


Java 객체 설계 단계별 발전 — Person 예제를 통해 이해하기

이번 포스팅에서는 객체지향 프로그래밍(OOP)의 기본 개념인 클래스와 캡슐화(Encapsulation)
회원가입 예시를 통해 하나씩 단계적으로 살펴본다.


1. 원시적인 방식 (변수 중심 설계)

가장 단순한 회원가입 예제를 생각해보자.
사용자가 입력한 이름, 이메일, 나이를 자바 서버에 저장해야 한다.
가장 먼저 떠올릴 수 있는 방법은 각각의 데이터를 변수로 따로 저장하는 것이다.

String name1 = "홍길동";
String email1 = "hong@naver.com";
int age1 = 30;
PrintMember(name1, email1, age1);

String name2 = "김땡땡";
String email2 = "kim@naver.com";
int age2 = 20;
PrintMember(name2, email2, age2);

이 방식은 간단하지만 정보들이 그룹화되어 있지 않아 구조적이지 않다.
사용자 수가 많아질수록 변수명이 계속 늘어나고, 중복 코드가 발생하며 가독성이 매우 떨어진다.


2. 클래스로 객체를 설계하는 단계

이제 name, email, age를 하나의 그룹으로 묶자.
즉, 사람의 정보를 담는 Person 클래스를 정의한다.

public class C04Person1 {
    String name;
    String email;
    int age;
}

이렇게 정의된 클래스를 기반으로 객체를 생성하면,
사람 한 명의 정보를 하나의 단위로 다룰 수 있다.

C04Person1 p1 = new C04Person1();
p1.name = "홍길동";
p1.email = "hong@naver.com";
p1.age = 30;

C04Person1 p2 = new C04Person1();
p2.name = "김땡땡";
p2.email = "kim@naver.com";
p2.age = 20;

이제 p1p2는 각각의 히힙(Heap) 메모리 공간에 독립된 데이터를 가진 객체다.
이를 이용해 출력 코드를 단순화할 수 있다.

PrintMemberForInstance(p1);
PrintMemberForInstance(p2);
public static void PrintMemberForInstance(C04Person1 person) {
    System.out.println("이름은 " + person.name + ", 이메일은 " + person.email + ", 나이는 " + person.age + "세 입니다.");
}

이제 변수명이 아닌 객체 단위로 사람을 다룰 수 있게 되었다.
가독성과 확장성이 증가한 구조지만, 여전히 두 가지 설계적 문제점이 남아 있다.

  1. name, email, age가 외부에서 직접 접근 가능 → 데이터 안정성 저하
  2. 출력 메서드가 외부 클래스마다 중복 선언될 가능성 → 유지보수 비효율

3. 접근제어자(Access Modifier) 도입과 캡슐화

변수의 직접 접근을 막기 위해, 객체의 속성을 private으로 설정하고
대신 접근 메서드(getter/setter)를 만든다.

이를 통해 외부에서 데이터를 제어하되,
직접 접근은 차단하는 구조를 만들 수 있다.

public class C04Person2 {
    private String name;
    private String email;
    private int age;

    public void setName(String name) { this.name = name; }
    public void setEmail(String email) { this.email = email; }
    public void setAge(int age) { this.age = age; }

    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }
}

이제 객체에 값을 대입할 때는 다음과 같이 setter를 이용한다.

C04Person2 p3 = new C04Person2();
p3.setName("홍길동");
p3.setEmail("hong@naver.com");
p3.setAge(30);

정보를 불러올 때도 getter를 활용한다.

PrintMemberForInstanceForPrivate(p3);
public static void PrintMemberForInstanceForPrivate(C04Person2 person) {
    System.out.println("이름은 " + person.getName() + ", 이메일은 " + person.getEmail() + ", 나이는 " + person.getAge() + "세 입니다.");
}

여기서 PrintMemberForInstanceForPrivate() 메서드는 여전히 외부에 존재하지만,
출력 로직을 Person 클래스 내부로 이동시키면 훨씬 깔끔하다.


4. 객체 내부의 공통 메서드 정의

반복되는 출력 코드를 외부 클래스마다 두는 대신,
Person 클래스 내부에 공통 메서드를 정의할 수 있다.

public String returnPersonInfo() {
    return "이름은 " + this.name + ", 이메일은 " + this.email + ", 나이는 " + this.age + "세 입니다.";
}

이제 다음과 같이 간결하게 호출할 수 있다.

System.out.println(p3.returnPersonInfo());

이제 클래스 내부에서 모든 정보와 동작이 함께 관리된다.
즉, 데이터(속성)행동(메서드)이 하나의 단위로 묶인 완전한 객체가 된 것이다.


5. 객체 메서드와 클래스 메서드의 차이

마지막으로, this 키워드의 사용과 관련된 중요한 개념을 짚고 넘어가자.

public void testMethod1() {
    String temp = this.returnPersonInfo(); // 가능
}

this현재 객체 자신을 가리키는 키워드다.
따라서 객체 메서드끼리는 자유롭게 호출 가능하다.

반면 클래스 메서드(static)에서는 this를 사용할 수 없다.
클래스 메서드는 애초에 객체 생성을 전제로 하지 않기 때문이다.

public static void testMethod2() {
    String temp = this.returnPersonInfo(); // 오류 발생
}

즉, 클래스 메서드는 객체의 상태와 무관하게 동작해야 하는 경우에만 사용해야 한다.


정리

  • 클래스는 여러 속성과 기능을 그룹화한 설계도 역할을 한다.
  • 객체를 생성하면 클래스의 독립적인 인스턴스(고유 공간)를 사용할 수 있다.
  • 변수 접근의 안정성을 위해 private 접근제어자를 사용하고,
    getter/setter 메서드로 외부 접근을 제어한다.
  • 출력 등 공통 동작은 클래스 내부의 객체 메서드로 구현하면 코드 중복을 줄일 수 있다.
  • this는 객체 자신을 의미하며, 클래스(static) 메서드에서는 사용할 수 없다.

이 과정을 통해 단순한 데이터 묶음이 하나의 구조적 객체로 발전하며,
객체지향의 핵심 개념인 “캡슐화”와 “데이터 은닉”을 이해할 수 있다.

profile
Eazy하게

0개의 댓글