포스트

스프링입문을위한 자바 객체지향의 원리와 이해(2)

스프링입문을위한 자바 객체지향의 원리와 이해(2)

3. 객체지향과 Java

이전 포스팅-스프링입문을위한 자바 객체지향의 원리와 이해(1)

에서는 왜 객체지향이고 왜 Java인지 간단히 살펴봤다. 이번 포스팅에서는 객체지향프로그래밍에 대해 더 자세히 살펴보자.

3.1 객체지향의 4대 특성

  1. 캡슐화 (Encapsulation)
    • 데이터와 메서드를 하나의 객체로 묶어 외부로부터 내부 구현을 숨긴다.
    • 정보를 보호하고, 시스템의 복잡도를 줄인다.

    👉 예시: public, private 같은 접근제어자 사용

  2. 상속 (Inheritance)
    • 기존 클래스(부모 클래스)의 특성과 기능을 새로운 클래스(자식 클래스)가 재사용하고 확장할 수 있게 한다.
    • 코드 재사용성과 유지보수성을 높인다.

    👉 예시: extends, implements

  3. 추상화 (Abstraction)
    • 복잡한 현실 세계를 필요한 부분만 골라 모델링한다.
    • 객체의 핵심적인 부분만 외부에 드러내고, 불필요한 세부사항은 감춘다.

    👉 예시: class와 객체

  4. 다형성 (Polymorphism)
    • 하나의 인터페이스로 여러 다른 형태의 객체를 다룰 수 있게 한다.
    • 코드의 유연성과 확장성을 높여준다.

    👉 예시: 오버로딩과 오버라이딩





3-2. 캡슐화 자세히 살펴보기

✏️ 캡슐화란?

캡슐화(Encapsulation)

  • 데이터(속성)와 데이터를 다루는 메서드(동작)를 하나의 객체 안에 묶고,
  • 외부에서 직접 데이터에 접근하지 못하도록 막는 것을 의미한다.

즉, 객체 내부의 복잡한 로직은 숨기고,
외부에는 꼭 필요한 기능만 공개하는 것이다.


📚 왜 캡슐화가 필요한가?

  1. 데이터 보호(Data Protection)
    • 중요한 데이터가 외부에서 무분별하게 수정되는 것을 막을 수 있다.
  2. 코드 유지보수성 향상(Maintainability)
    • 내부 구현을 수정해도 외부에는 영향이 없으므로, 유지보수가 쉬워진다.
  3. 프로그램의 복잡도 감소(Reduction of Complexity)
    • 객체가 스스로 자신의 상태를 책임지기 때문에 시스템이 더 이해하기 쉬워진다.

✏️ Java에서 캡슐화 구현 방법

1. 접근 제어자 사용 (Access Modifiers)
Java는 접근 제어자를 통해 멤버(변수, 메서드)의 공개 범위를 설정한다.

접근 제어자설명UML 기호
private같은 클래스 내부에서만 접근 가능 (가장 엄격)-
default (package-private)같은 패키지 내에서 접근 가능~
protected같은 패키지 + 다른 패키지의 자식 클래스에서 접근 가능#
public어디서든 접근 가능 (가장 개방적)+

2. Getter와 Setter 메서드 제공
직접 변수에 접근하지 않고, 메서드를 통해 간접적으로 데이터를 읽거나 수정한다. -> Getter Setter 사용에 대해서는 Setter는 값을 바꾸는 이유를 드러내지 않고, 다른 객체가 해당 객체(Setter가 있는 객체)의 할 일을 하는 등 지양해야 한다는 의견도 존재함. 참고자료


📚 예시: Java에서의 캡슐화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Car {
    private int speed;  // 외부에서 직접 접근 불가

    public int getSpeed() {  // 데이터 읽기 (Getter)
        return speed;
    }

    public void accelerate() {  // 데이터 변경 (Setter 역할)
        speed += 10;
    }

    public void brake() {
        if (speed >= 10)
            speed -= 10;
    }
}

public static void main() {
   Car c=new Car();
   c.speed=10; // C에서는 됐지만 Java에서는 안됨
}
  • speed 변수는 private로 설정되어 외부에서 직접 접근할 수 없다.

  • 대신 getSpeed(), accelerate(), brake() 메서드를 통해 안전한 방법으로 객체 상태를 조작한다.





3-3. 상속 자세히 살펴보기

🧬 상속이란?

상속(Inheritance)
기존 클래스(상위 클래스, Superclass)의 속성과 기능(메서드)
새로운 클래스(하위 클래스, Subclass)가 재사용하고, 확장할 수 있도록 하는 객체지향의 핵심 개념이다.


📚 상속이 왜 중요한가?

  1. 코드 재사용
    • 공통 기능을 상위 클래스에 모아두고, 중복을 줄인다.
  2. 기능 확장
    • 기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있다.
  3. 유지보수성 향상
    • 상위 클래스만 수정해도, 하위 클래스 전반에 영향이 반영된다.

🤔 그런데… 우리가 아는 ‘상속’과 다른가?

왼쪽 이미지 오른쪽 이미지

일반적으로 ‘상속’이라고 하면
부모가 자식에게 유산을 넘겨주는 시간적·가계적 개념을 떠올린다.

1
할아버지 h = new 아버지();

이런 코드처럼 위에서 아래로 유산을 전하는 듯한 상속 구조를 연상하기 쉽다.
하지만 객체지향에서의 상속은 그와는 성격이 다르다.


📊 객체지향의 상속은 ‘분류’에 가깝다

객체지향에서의 상속은 단순히 무언가를 넘겨주는 관계가 아니라,
상위 개념을 하위 개념으로 구체화하는 과정이다.

1
2
스마트폰 s = new 갤럭시();  // Galaxy는 스마트폰의 일종이다

갤럭시는 스마트폰의 하위 개념이며,

스마트폰이 가진 기본 특성(전화, 인터넷 등)을 물려받고 확장한다.

📌 핵심 개념 ① — 상속은 ‘재사용’이다

상속을 통해 상위 클래스의 속성과 메서드를 물려받아 반복 코드를 줄이고 효율적인 재사용이 가능해진다.

1
2
3
4
5
6
7
class Animal {
    void breathe() { System.out.println("숨 쉰다"); }
}

class Dog extends Animal {
    void bark() { System.out.println("멍멍"); }
}

Dog는 별도의 코드 없이도 breathe()를 사용할 수 있다. 이게 바로 재사용의 장점이다.

📌 핵심 개념 ② — 상속은 ‘확장’이다

상속받은 클래스는 기능을 그대로 쓰는 데 그치지 않고, 새로운 기능을 추가하거나 기존 기능을 재정의(Override) 해서 확장할 수 있다.

1
2
3
4
class Dog extends Animal {
    @Override
    void breathe() { System.out.println("혀 내밀고 숨 쉰다"); }
}

이처럼 하위 클래스는 상위 클래스의 틀을 기반으로 자신만의 특성을 추가할 수 있다.

❓상속은 (is a) vs (is a kind of) 관계를 만족해야 한다

결론부터 말하자면 is a kind of 관계를 만족해야 한다. 즉,

1
하위클래스 is a kind of 상위클래스 // 하위클래스는 상위클래스의 한 분류이다. 

왜 그런지는 예시를 통해 이해해 보자.

1
2
3
4
is a 관계

루피(뽀로로) is a 동물 // 루피는 한 마리 동물이다.
이상혁 is a 사람 // 이상혁은 한 명의 사람이다.

하위클래스와 상위클래스는 Class 즉 “분류” 이다. 그런데 is a 관계로 나타내게 되면 자칫 클래스와 클래스 사이가 아니라 객체와 클래스 사이의 관계로 오해될 소지가 있다.

1
2
3
4
is a kind of 관계

비버 is a kind of 동물 // 비버는 동물의 한 종류이다.
남성 is a kind of 사람 // 남성은 사람의 한 종류이다. 

이렇게 is a kind of 로 나타내게 되면 슈퍼 클래스 와 서브 클래스 사이의 관계에 더 집중하여 나타낼 수 있다.

➕ 상속과 인터페이스

Java에서 상속은 extends 라는 키워드로 나타나고 개념적으로 is a kind of 관계를 가진다. 그렇다면 재사용 및 확장의 관점에서 빠질 수 없는 인터페이스는 어떻게 나타날까?

이미지 없음

다음과 같은 구조를 생각했을때 인터페이스는 주로 ~할 수 있는(be able to)의 형태로 나타나게 된다. 실제로 Java에서 쓰이는 몇몇 인터페이스들을 살펴보면 다음과 같다.

1
2
3
Runnable 인터페이스 //실행 가능한
Serializable 인터페이스 //직렬화 가능한
Cloneable 인터페이스 // 복제 가능한

Java에서 인터페이스를 구현하는 키워드는 implements 라고 하는데 ~할 수 있는 형태로 주어지는 인터페이스들을 실제로 구현 한다는 점을 고려한다면 Java의 키워드 선정에 이러한 배경이 있다고 추측해 볼 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.