코드스테이츠 - OOP 기초2 (상속, 캡슐화)

kwj1830·2022년 5월 11일
0

코드스테이츠

목록 보기
12/26
post-thumbnail

제목을 잘못지은거 같은데, 걍 ㄱㄱ...

1. 상속

1. 상속이란?

상속은 확장이라는 단어로도 많이 쓰인다. 국어사전 들고 올 필요 없다. 네가 맞으니까...

일단 내 말 좀 들어보셈

임의의 클래스 A가 있다. 이놈을 좀 더 상세하게 쓰려고 클래스 B를 만들었다.

근데 B를 새로 만들기가 너무 귀찮다. 그러면 A를 상속받아 B를 만들고, 메서드나 필드를 조금 고치면 된다.

즉 클래스 B는 A를 상속받아 만들었다.
그러므로 클래스 A를 확장시켜서 클래스 B를 만들었다.

라는 말이 된다. Java에서도 상속이라는 말 보단 extends라는 말을 더 많이 쓰는거 같음.

참고로 이 모든 클래스들의 최상위 클래스가 있는데 그놈이 Object다.

괜히 java가 "모든 것은 Object다" 라고 말하는게 아님


2. 예시

이전 글에서 Item 이라는 클래스를 만들었었다.

public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {this.id = id;}

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getQuantity() {
        return quantity;
    }

    public void setQuantity(Integer quantity) {
        this.quantity = quantity;
    }

    public void introduce() {
        System.out.println("itemName = " + itemName);
        System.out.println("price = " + price);
        System.out.println("quantity = " + quantity);
    }

    public void introduce(String s) {
        System.out.println("itemName = " + itemName);
        System.out.println("price = " + price);
        System.out.println("quantity = " + quantity);
        System.out.println("s = " + s);
    }
}

게터세터때문에 드릅게 코드가 길다. 다음엔 lombok을 쓰자.. ㅋㅋㅋㅋㅋ 하여튼

이 클래스 필드는 id, itemName, price, quantity를 가지고 있다. 이것만 가지고도 대충 홈페이지 만들때 쓸 수 있지만, 뭔가 더 세분화 하여 다른 클래스를 만들 필요성이 생겼다고 하자.

즉 Item 클래스를 확장시킬 필요가 생긴거다. 이 때 이미 만들어진 Item클래스를 버리고 완전 새로운 클래스를 만들 필요가 없다.

public class Laptop extends Item{

    private String model;

    public Laptop() {}

    public Laptop(String itemName, Integer price, Integer quantity, String model) {
        super(itemName, price, quantity);
        this.model = model;
    }

    public void turnOn() {
        System.out.println("Laptop 기동");
    }
        @Override
    public void introduce() {
        System.out.println("모델명 :" + model);
    }
}

Item을 상속 받아 Laptop이라는 새로운 클래스를 만들었다.

자세히 보면 뭔가 훵하다.

Item을 상속 받았기 때문에 Item의 필드, 메서드가 그대로 있다. (안보이더라도)

     Laptop laptop = new Laptop();

     laptop.turnOn();
     laptop.introduce("ㅋㅋㅋㅋ오졌쥬?");

출력
Laptop 기동
itemName = null
price = null
quantity = null
s = ㅋㅋㅋㅋ오졌쥬?

introduce(String s) 메서드가 그대로 출력됨을 알 수 있다. 물론 생성시 itemName, price, quantity 입력을 안했기 때문에 값은 null이 출력 된다.

거기에 turnOn() 메서드도 정상 작동한다. 이건 Laptop 클래스에서만 지정한거라 Item 클래스로는 turonON() 메서드를 부를 수 없다.

Item클래스는 부모 혹은 상위 클래스고
Laptop 클래스는 자식 혹은 하위 클래스라고 한다.


- 오버라이드

상기 Item을 확장시켜 Laptop을 만든걸 보면 뭔가 이상한 구석이 있다.

    @Override
    public void introduce() {
        System.out.println("모델명 :" + model);

뭔가 @가 있고 거기에 Override라고 써 있음
이전 Item클래스의 intorduce()를 보면

    public void introduce() {
        System.out.println("itemName = " + itemName);
        System.out.println("price = " + price);
        System.out.println("quantity = " + quantity);
    }

이렇게 돼 있다. 이 때 Laptop의 introduce()를 호출해보면 어떨까?



itemName, price, quantity를 줄줄히 뱉어야 하는데 model만 뱉는다.
이건 Laptop 클래스의 introducd()메서드로 Item클래스의 introduce()를 재정립해서 그렇다.
덮어 씌웠다는 표현이 적절하겠다.
그래서 Item의 메서드는 사라지고 Laptop의 매서드가 남은거임
이걸 오버라이드 라고 한다.
오버라이드는 매우 매우 자주 쓰는 개념이니까 확실히 기억해둬야 한다.
오버로딩이랑 헷갈리면 안된다.

  • 오버라이드 : 상위 클래스의 속성을 덮어써서 새로 만듬
  • 오버로딩 : 인자별로 다르게 작동함. Laptop이나 Item의 생성자 참고

한줄로 표현하려니까 디게 어렵네, 하여튼 그렇다.

상기 예제에서
introduce() 는 오버라이드해서 사용
introduce(String s)는 오버라이드가 안됐으니까 Item클래스의 메서드가 출력됐다.
다르게 출력 된 이유 = 오버로딩, introduce()는 오버라이드 됨


super() 와 super

상기 Laptop의 생성자를 보면 이상한 구석이 있다.

public Laptop(String itemName, Integer price, Integer quantity, String model) {
        super(itemName, price, quantity);
        this.model = model;
    }

Laptop 생성자에 String model 인자가 새로 생긴건 알겠는데, super()는 뭐지?
super()는 해당 생성자를 가져온다.
여기선 itemName, price, quantity를 가져 왔는데, 이걸 좀 풀어 헤치면 이렇다.

public Laptop(String itemName, Integer price, Integer quantity, String model) {
	this.itemName = itemName;
    this.price = price;
    this.quantity = quantity;
    this.model = model;

즉 super()는 상위 클래스를 가져 온다고 생각하면 편하다.
문제는 super()도 있는데 그냥 super도 있다. 얘도 근데 별거 아님

Item에 String s = "쿠콰콰쾅오" 를 선언해뒀다고 하자
Laptop에선 이 String s를 "뽀잉뽀잉뽀잉"으로 해놨다.
Laptop의 introduce() 메서드에 이 s를 호출했다.

    @Override
    public void introduce() {
        System.out.println("모델명 :" + model);;
        System.out.println("s = " + s);
        System.out.println("super.s = " + super.s);
    }

s를 호출하면 "뽀잉뽀잉뽀잉"이 나오고 super.s를 호출하면 "쿠콰콰쾅오"가 나온다.


2. 캡슐화

클래스는 내부에서만 조작되야 한다. 외부접근을 막고 최소한의 구녕만 뚫어놔야 보안이나 객체지향을 좀 더 잘 수행할 수 있다.

1. 패키지

모든 클래스는 패키지에 속해야 한다. 만약 패키지가 없으면, 이름 없는 패키지에 속하게 됨.

이 패키지는 프로그램 유지보수에도 많은 도움을 준다. 좀 쉽게 생각하면 디렉토리라고 생각하면 되겠다.

package hello.exception.exhandler.advice;

import hello.exception.exception.UserException;
import hello.exception.exhandler.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice (basePackages = "hello.exception.api")
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandler(IllegalArgumentException e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException e) {
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX", "내부 오류");
    }
}

상기 코드는 스프링프레임워크를 활용하여 api 예외처리를 한 클래스다. 다른건 안봐도 되니까 맨 위의 package만 보자 hello 패키지에 exception 패키지에 exhandler 패키지에 advice 패키지에 속하는 클래스다. 그리고 그 아래 import를 보면 패키지 이름으로 import가 되어 있음을 확인할 수 있다. import는 다른 패키지에 속한 클래스를 쓰기 위해서 패키지를 불러온다고 생각하면 편할 것이다.


요놈은 또 다른 프로젝트의 패키지를 나열한 이미지다. 저런식으로 비슷한 기능을 하는 클래스들을 서로서로 묶어주는 역할을 한다.

패키지가 다를 경우 클래스 이름이 같더라도 서로 다르게 인식한다.


2. 접근제어자

다시 Item으로 돌아가보자

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;
    public String s = "쿠콰콰쾅오";

Long이니 String이니 하는건 타입이라고 한다 쳐도 private는 무엇일까?

얘가 바로 접근제어자다.

public -> protected -> default -> private

순으로 깝깝해진다.

  • public : 어떤 클래스라도 접근이 가능하다.
  • protected : 해당 변수, 메서드는 동일 패키지 내의 클래스 또는 해당 클래스를 상속 받은 다른 클래스에서만 접근이 가능하다.
  • default : 접근제어자를 별도로 설정하지 않으면 얘가 작동한다. protected와 다르게 해당 패키지에서만 접근 가능하다.
  • private : 해당 클래스에서만 접근 가능하다.

public은 어디서든 접근이 가능하기 때문에 public static final같은 방식으로 상수로 만들 때 자주 쓰는듯 하다.

앵간한 필드는 캡슐화를 위해서 private로만 설정해주게 된다. 그래서 예제Item 클래스도 대부분의 접근제어를 private로 설정했다.
메서드의 경우 보통 public이 아니면 외부에서 절대 접근을 못하니까 public으로 설정하는 편 물론 엄밀한건 아니다.


3. Getter Setter

이 전 글에서 미리 설명을 해놨음.

캡슐화된 필드에 접근을 못하니까, 이게 수정이 안된다.

그래서 만든 메서드인데 private로 정해진 필드 값을 바꿔줄 수 있다. 어차피 그 메서드가 발생하는건 그 클래스 내부에서 이루어지므로 private라고 해도 접근이 가능한거다.

getter는 return이 있어야 하고
set은 해당 클래스 필드의 내용만 갈아주면 되니까 return값이 필요 없다.

private String itemName;

//게터
public String getItemName() {
	return itemName;
}

//세터
public void setItemName(String itemName) {
	this.itemName = itemName;
}

get으로는 itemName을 뽑아올 수 있고, set으로는 itemName을 변경할 수 있다.


profile
https://potent-grey-e01.notion.site/2d4885cad9fa4b0a8d53a784ad10cf8c <-- 알고리즘

0개의 댓글