본문 바로가기
스파르타 내일배움캠프/TIL(Today I learned)

25.03.05 TIL - 캡슐화, 상속, 추상화

by pandastic 2025. 3. 5.
반응형

 

 

1. Chapter 2 - 7 : 객체지향 PART1 - 캡슐화(접근제어자)

1. 캡슐화

    - 객체의 정보를 외부에서 직접 접근하지 못하게 보호하는 개념.

    - 클래스 혹은 객체의 캡슐화는 접근 제어자를 통해 구현 가능.

 

* 캡슐화가 필요한 이유

     - 외부에 노출하고 싶지 않은 정보를 캡슐화를 통해 보호하고 필요한 경우에만 안전하게 접근할 수 있도록 함.

     - 캡슐화를 구현하려면 접근제어자에 대해 이해가 필요.

 

2. 접근제어자

    - 클래스, 변수, 메서드, 생성자의 접근 범위를 제한하는 키워드.

    - 캡슐화 구현을 위해 사용됨.

 

접근제어자 클래스 내부 패키지 내부 상속한 클래스 전체 공개
public
protected
default
private

 

 

접근제어자 활용 예시(public, private)

public class Person {      // ✅ 외부에서 접근 가능
    public String name;    // ✅ 외부에서 접근 가능
    private String secret; // ❌ 외부에서 접근 불가
   
    public Person() {}    // ✅ 외부에서 접근 가능
    public void methodA() {}  // ✅ 외부에서 접근 가능
    private void methodB() {} // ❌ 외부에서 접근 불가
}

 - 일단 private으로 시작하는 것이 좋음 → 데이터 보호 목적.

 - 외부에 필요한 기능들만 public으로 바꿔서 노출해서 사용하는 것을 권장.

 

public class Person {
    // 속성
    private String name;
    private String secret;
    // 생성자
    public Person(String name, String secret) {
        this.name = name;
        this.secret = secret;
    }
    // 기능
    public void methodA(){}
    public void methodB(){}
}

 - 속성들은 객체가 만들어질 때 한 번 설정이 되면 변경될 일이 거의 없음.

 - 속성이 private일 경우 생성자를 public으로 두고 public Person(String name, String secret) 이런 식으로 매개변수를 통해 생성 시점에 한 번에 받아서 객체에 대한 인스턴스 멤버들을 설정해주는 것이 더 효율적임.

 - 생성 시점에 한 번 설정하면 그 다음에 외부에서 바꿀 수 없음.

 

3. 데이터 접근 - Getter/ Setter

    - 캡슐화가 잘 적용된 클래스는 내부 데이터를 private으로 보호하고 있음.

    - 데이터 조회나 변경이 필요한 경우 안전한 접근 방법 필요 → Getter/ Setter 메서드

 

* Getter 활용법

   - 데이터를 안전하게 접근하기 위해 사용.

 

 

Person.java

public class Person { 
    private String secret;
    
    public String getSecret() {
		    return this.secret; // ✅ 객체의 secret 속성 반환
    }
}

 

Main.java

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.secret; // ❌ 직접 접근 불가능
        String newSecret = p1.getSecret(); // ✅ 게터를 활용해 접근가능
    }
}

 

 

* Setter 활용법

   - 데이터를 안전하게 설정/ 변경하기 위해 사용.

 

Person.java

public class Person { 
    private String secret;
    public void setSecret(String secret) {
	this.secret = secret; // ✅ secret 속성 설정 및 변경
    }
}

 

Main.java

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.secret = "password"; // ❌ 직접접근, 변경 불가능
        p1.setSecret("newPassword"); // ✅ 세터를 활용해 접근, 변경가능
    }
}

 

 

* 무분별한 세터란?

   - 접근을 막아 놓고 다시 세터로 외부에 노출한다면 접근제어를 하는 의미가 사라짐 → 올바른 데이터만 저장할 수 있도록 제한.

   - 접근제어자를 사용하거나, 세터를 사용하거나 결과값은 똑같게 되어서 의미가 없는 것이 무분별한 세터.

   - 해결책 1: 접근제어자로 데이터 보호.

   - 해결책 2: 세터 추가.

   - 해결책 3: 안전한 데이터 설정 로직을 추가.

 

public void setStore(String store) {
  if(store.equals("B")){
     System.out.println("B가 입력되면 안됩니다.");
  }else{
     this.store = store;
     }
}

위처럼 setter에 로직을 추가하는 방법이 있음.

 

 

 

2. Chapter 2 - 8 : 객체지향 Part2 - 상속

1. 상속(Inheritance)

     - 클래스 간의 관계를 부모(상위), 자식(하위)로 바라보는 개념.

     - 이 구조를 통해 상속에서는 재사용성, 확장 가능 → 물려받은 속성과 기능은 자식 클래스에서 재사용 또는 확장 가능.

     - extends 키워드를 사용하여 상속관계 구현.

     - 상속을 통해 코드 중복을 줄이고 유지보수성 높일 수 있음.

     - 추상화, 다형성 구현에 활용됨.

 

* 상속의 장점

  ● 재사용성

      - 부모 클래스의 내용을 물려받아 그대로 재사용 가능.

 

super() - 부모 인스턴스

  - 부모 클래스의 멤버(변수, 메서드)에 접근할 때 사용하는 키워드.

   - 자식 클래스에서 부모의 변수나 메서드를 명확하게 호출할 때 사용.

 

super() - 부모 인스턴스의 생성자

   - 부모가 먼저 생성되고 자식이 생성됨.

   - 부모자 먼저 생성되어야 하므로 super()는 항상 생성자의 첫 줄에 위치해야함 → 그 후에 추가 로직 작성!!

 

public class Child extends Parent {
		...
    public Child() {
        super(); // ✅ (1)부모클래스 생성자를 먼저 호출
        // 추가로직은 여기에 작성
    }
}

  - super(); 를 누르면 커서가 Parent  클래스의 생성자로 이동함.

 

 

* 확장

   - 부모 클래스의 기능을 유지하면서 자식클래스에서 기능을 확장.

 

2. 메서드 오버라이딩(Overriding)

   - 부모 메서드를 자식 클래스에서 변경하여 재정의하는 것.

   - 오버라이드된 메서드에서는 @Override 키워드를 붙이는 것을 권장(없어도 동작함)

   - @Override를 붙이면 컴파일러가 부모 클래스에 동일한 메서드가 없다고 경고를 줘서 실수 방지 가능.

   - 메서드 이름, 매개변수, 반환 타입이 완전히 동일해야함.

   - 접근 제어자는 부모보다 더 강한 수준으로만 변경 가능.

 

3. 추상 클래스

   - 공통 기능을 제공하면서 하위 클래스에 특정 메서드 구현을 강제하기 위해 사용됨.

   - 객체를 생성할 목적이 아니라 "설계도" 역할을 할 때 적합함.

   - abstract 키워드로 클래스를 선언하면 추상클래스.

   - 추상클래스로 객체 생성 불가.

   - 일반 클래스처럼 변수와 메서드를 가질 수 있음.

 

예시)

더보기

Animal.java - 추상클래스

abstract class Animal {
    private String name; // ✅ 변수선언가능
    abstract void eat(); // ⚠️ 추상메서드: 상속 받은 자식은 강제 구현해야합니다.
    public void sleep() { // ✅ 자식클래스에서 재사용가능합니다.
        System.out.println("쿨쿨");
    }
}

 

Cat.java

public class Cat extends Animal {
    
    @Override
    void eat() {
        System.out.println("냠냠"); //  ⚠️ 자식클래스에서 강제 구현해야합니다.
    }
}

 

Main.java

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal(); // ❌ 추상클래스는 구현할 수 없습니다.
        Cat cat = new Cat();
        cat.eat(); // ⚠️ 강제 구현한 메서드 사용
        cat.sleep(); // ✅ 부모클래스의 매서드 사용
    }
}

 

 

⚠️ 추상클래스와 인터페이스 차이점

     - 상속이 계층적 구조를 선언하기 적합함.

     - 인터페이스는 표준을 제공하는 데 적합함.

     - 인터페이스는 인스턴스 변수를 선언할 수 없음.

     - 계층적 구조를 표현하면서 공통 속성과 기능을 재사용할 때 추상클래스를 사용하는 것이 적합함.

 

4. Object 클래스

  - 모든 클래스는 Object를 상속받고 있음.

  - equals() 같은 정의하지 않은 기능을 사용할 수 있었던 이유는 모든 객체는 Object 클래스를 상속받은 객체기 때문.

 

 

3. Chapter 2 - 9 : 객체지향 Part3 - 추상화

1. 추상화

   - 불필요한 정보를 제거하고 본질적인 특징만 남기는 것.

   - 객체지향 프로그래밍에서는 추상화의 계층적 특징을 활용해서 유지보수성이 좋은 프로그램을 만들 수 있음.

   - 자바의 특징(인터페이스 상속, 클래스 상속)을 이용해 계층적 구조를 만들어왔음.

 

 

인터페이스 상속을 활용한 추상 계층 표현

더보기
public interface LifeForm {
    void exist(); // ✅ 공통: 모든 생명체는 존재한다.
}

 

public interface Animal extends LifeForm {

    void makeSound(); //✅ 공통: 모든 동물은 소리를 냅니다.
}

 

public class Cat implements Animal {

    @Override
    public void exist() {
        System.out.println("고양이가 존재합니다.");

    }

    @Override
    public void makeSound() {
        System.out.println("야옹");
    }

    public void scratch() {
        System.out.println("스크래치");
    }
}

 

public class Main {

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.exist();
        cat.makeSound();
        cat.scratch();
    }
}

 

클래스 상속을 활용한 추상 계층 표현

더보기
public class LifeForm {

    public void exist() {
        System.out.println("존재합니다2"); // ✅ 공통: 모든 객체는 존재한다.
    }
}

 

public class Animal extends LifeForm {

    public void makeSound() {
        System.out.println("소리를 냅니다2"); // ✅ 공통: 모든 생명체는 성장한다.
    }
}

 

public class Cat extends Animal {

    @Override
    public void makeSound() {
        System.out.println("야옹2");
    }

    public void scratch() {
        System.out.println("스크래치!");
    }
}

 

public class Main {

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.exist();
        cat.makeSound();
        cat.scratch();
    }
}

 

[실습 과제]

더보기

💬 Q1. 스스로 주제를 정하고 인터페이스와 클래스를 활용하여 3단계 이상의 추상 계층을 구현해 보세요.

  • 수업에서 배운 계층 구조와 동일하게 만들면 됩니다.
  • 정답이 정해져 있지 않으니 자유롭게 상상하며 코딩해 보세요!

 

Perform.java(Interface)

package chapter2.abstraction.v3;
public interface Perform {
    void perform();
}

 

Music.java(Interface)

package chapter2.abstraction.v3;
public interface Music extends Perform{
    void sing();
}

 

Musical.java

package chapter2.abstraction.v3;
public class Musical implements Music{
    @Override
    public void perform() {
        System.out.println("공연을 시작합니다.");
    }
    @Override
    public void sing() {
        System.out.println("노래를 부릅니다.");
    }
    public void act(){
        System.out.println("연기를 합니다.");
    }
}

 

Main.java

package chapter2.abstraction.v3;
public class Main {
    public static void main(String[] args) {
        Musical musical = new Musical();
        musical.perform();
        musical.sing();
        musical.act();
    }
}

 

반응형