01.25 객체 지향 프로그래밍(Object Oriented Programming)

2023. 1. 26. 08:58개발일지

객체란

  • 우리가 실생활에서 쓰는 모든 것을 객체라고 합니다.

객체 지향 프로그래밍

  • 객체들을 먼저 만들고 이것들을 하나씩 조립 및 연결해서 전체 프로그램을 완성하는 기법입니다.

추상화

  • 공통의 속성이나 기능을 묶어서 이름을 붙이는 것을 의미합니다.
  • 예를 들어서 토끼랑 거북이에 대해서 공통속성인 이름과 속도를 묶었고, 달리기라는 메서드를 정의하면
public abstract class Animal {
    String name;
    int speed;
    Animal(String name, int speed){
        this.name = name;
        this.speed = speed;
    }
    abstract void run();
}
public class Turtle extends Animal {
    Turtle(String name, int speed) {
        super(name, speed);
    }
    @Override
    void run() {
        System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
    }
}
public class Rabbit extends Animal {
    Rabbit(String name, int speed) {
        super(name, speed);
    }
    @Override
    void run() {
        System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
    }
}
    public static void main(String[] args) {
        Rabbit rabbit = new Rabbit("토끼", 5);
        Turtle turtle = new Turtle("거북이", 1);

        rabbit.run();
        turtle.run();
    }   
}

동물이라는 객체로 추상화해서 공통의 속성이나 메서드를 묶어서 이름을 붙여 놓았습니다. 속성들을 재정의 할 필요가 없어졌고 메서드도 통일감있게 작성할 수 있게 되었습니다.
(결론적으로 추상화를 하는 이유는 중복코드가 줄고 코드의 재사용성이 증가하기 때문에 추상화를 하는 목적이 됩니다.)

 

상속

  • 상위(부모) 객체를 기반으로 하위(자식) 객체를 생성하는 관계를 말합니다.
  • 상위 개념의 특징을 하위 개념이 물려받는 것이 상속이라고도 합니다.
  • 위에 있는 코드로 비교를 하면
public class Rabbit extends Animal {
    Rabbit(String name, int speed) {
        super(name, speed);
    }
    @Override
    void run() {
        System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
    }
}

Animal객체가 상위 개념이며 거북이와 토끼가 속성들을 물려 받은 것입니다. (extends)

 

캡슐화

  • 실제로 구현되는 부분을 외부에 드러나지 않도록 캡슐로 감싸 이용방법만을 알려주는 것이며, 메서드(이용방법)만 접근 가능하고 속성값들은 접글할 수 없습니다.
  • ex)를 들자면
public class Turtle extends Animal {
    private int hungry = 10;
    Turtle(String name, int speed) {
        super(name, speed);
    }
    @Override
    void run() {
        System.out.println(this.name+"는 "+speed+"의 속도로 달린다.");
        hungry(); // 달렸더니 허기가 지는군요.
    }
    public void hungry(){
        hungry++;
        System.out.println("열심히 달렸더니 허기가 지군요.");
    }
    public void eat(){
        hungry--;
        System.out.println(name+" 이(가) 밥을먹어서 배고픔 지수가 " +this.hungry+"가 되었습니다.");
    }
    public void status(){
        System.out.println(name+"의 배고픔 지수는 " +this.hungry+" 입니다.");
    }
}
    public class Match {

        public static void main(String[] args) {
            Turtle turtle = new Turtle("거북이", 1);
            turtle.status();
            turtle.run();
            turtle.status();
            turtle.eat();
            turtle.status();
        }
    }
//거북이의 배고픔 지수는 10 입니다.
//거북이는 1의 속도로 달린다.
//열심히 달렸더니 허기가 지군요.
//거북이의 배고픔 지수는 11 입니다.
//거북이 이(가) 밥을먹어서 배고픔 지수가 10가 되었습니다.
//거북이의 배고픔 지수는 10 입니다.
  • 거북이의 배고픔은 거북이 스스로 관리하면 되는 것입니다. 다른 코드에서 거북이의 배고픔을 조절한다면 거북이 객체는 혼선이 올 것이며, 밥을 먹어야하는데 허기짐지수가 높아서 밥을 먹어야 하는 상황이 오지 않는 오류가 생깁니다.
  • 정리를 해보자면 중심적인 구조적 프로그래밍 언어에서는 프로그램 내부에서 데이터가 어디서 어떻게 변경되는지 파악하기 어렵습니다 그로 인해 유지보수가 어려웠지만 객체지향에서는 캡슐화를 통해서 유지보수성이 향상된 것입니다.

 

Getter & Setter

  • 클래스를 선언할 때 가능하다면 필드를 private로 선언해서 외부로부터 보호하고 필드에 대한 Setter/Getter 메서드를 작성해서 필드값을 안전하게 변경 / 사용하는 것이 좋습니다.

Setter

  • 객체 지향 프로그래밍에서 객체의 데이터는 객체 외부에서 직접적으로 접근하는 것을 막습니다. 객체 데이터를 외부에서 읽고 변경 시 객체의 무결성이 깨질 수 있기 때문입니다. 따라서 객체 지향 프로그래밍에서는 메서드를 통해 데이터를 변경하는 방법을 선호합니다.
  • 데이터는 외부에서 접근하지 않도록 막고, 메서드는 공개해서 외부에서 메서드를 통해 데이터에 접근하도록 유도합니다.(메서드는 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있기 때문입니다.)

Getter

  • 외부에서 객체의 데이터를 읽을 때도 메서드를 사용하는 것이 좋습니다. 객체 외부에서 객체 필드값을 사용하기 부적절한 경우가 있으며, 이런 경우 메서드로 필드값을 가공 후, 외부로 전달합니다.

Getter & Setter 예시

private 타입 test;        //  필드 접근 제한자 : private
// Getter
public 리턴타입 getTest(){
    return test;
}

// Setter
public void SetTest(타입 test){
    this.test = test;
}

Getter : getTest() 

- 접근 제한자 : public

- 리턴 타입 : 필드의 리턴 타입

- 메서드 이름 : get + 필드 이름 (첫문자는 대문자)

- 리턴값 : 필드값

===========================================================

Setter : setTest(타입 test)

- 접근 제한자 : public

- 리턴 타입 : void

- 메서드 이름 : set + 필드 이름 (첫문자는 대문자)

- 매개 변수 타입 :필드 타입
(만약 필드 타입이 boolean일 경우, Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례입니다.)

    private boolean stop;       //  필드 접근 제한자 : private

//    Getter
    public boolean isStop() {
        return stop;
    }

//    Setter
    public void setStop(boolean stop) {
        this.stop = stop;
    }

Getter : isStop()

- 접근 제한자 : public

- 리턴 타입 : 필드 타입

- 메서드 이름 : is + 필드이름 (첫문자는 대문자)

- 리턴값 : 필드값

===========================================================

Setter : setStop(boolean stop)

- 접근 제한자 : public

- 리턴 타입 : void

- 메서드 이름 : is + 필드이름 (첫문자는 대문자)

- 매개 변수 타입 : 필드 타입

  • 외부에서 필드값을 읽을 수만 있고, 변경하지 못하도록 하려면(읽기 전용) Getter 메서드만 선언하거나, Setter 메서드를 private 접근 제한을 갖도록 선언합니다.

 

클래스와 인스턴스

클래스

  • 객체를 생성하기 위한 필드와 메서드가 정의된 것입니다.
  • 간단한 객체를 만들기 위한 틀 또는 설계도라고 생각하면 쉽습니다.

인스턴스

  • 클래스로부터 만들어진 객체입니다.
  • 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체입니다.

 

다형성

  • 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말하며, 코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입합으로써 다양한 기능을 이용할 수 있도록 해줍니다.
  • 부모-자식 상속 관계에 있는 클래스에서 상위 클래스가 동일한 메시지로 하위 클래스들을 서로 다르게 동작시키는 객체 지향 원리입니다.
  • 다향성을 활용하면 부모클래스가 자식클래스의 동작 방식을 알수없어도 오버라이딩을 통해 자식 클래스를 접근 할 수 있습니다.

다형성 장점

  • 유지보수가 쉽다
    • 개발자가 여러 객체를 하나의 타입으로 관리가 가능하기 때문에 코드 관리가 편리해 유지보수가 용이합니다.
  • 재사용성 증가
    • 다형성을 활용하면 객체를 재사용하기 쉬워지기 때문에 개발자의 코드 재사용성이 높아집니다.
  • 느슨한 결합
    • 다형성을 활용하면 클래스간 의존성이 줄어들며 확장성이 높고 결합도가 낮아져 안전성이 높아집니다.

다형성 단점

  • 처리 속도가 상대적으로 느립니다.
  • 객체가 많으면 용량이 커질 수 있습니다.
  • 설계시 많은 시간과 노력이 필요합니다.

다형성 필수 조건

  • 상속 관계
    • 다형성을 활용하기 위해서는 필수로 부모-자식 간 클래스 상속이 이루어져야 합니다.
  • 오버라이딩 필수 (자식 클래스에서 메서드 재정의)
    • 다형성이 보장되기 위해서는 하위 클래스 메서드가 반드시 재정의되어 있어야 합니다.
  • 업캐스팅 (자식 클래스의 객체가 부모 클래스 타입으로 형변환 되는 것)
    • 부모 타입으로 자식클래스를 업캐스팅하여 객체를 생성해야 합니다.

다형성 구현 방법

  • 상속 클래스 구현 (부모 - 자식 클래스 구현)
  • 메서드 오버라이딩
  • 업캐스팅하여 객체 선언
  • 부모 클래스 객체로 자식 메서드 호출

다형성 예시

  • 상속 클래스 구현 (부모 - 자식 클래스 구현)
    • 부모 자식간 상속 클래스를 구현합니다.
    • 부모 클래스는 Book 클래스 이며 자식 클래스는 Novel(소설) 클래스입니다.
    • 디폴트 생성자 외 인수 있는 생성자를 추가해 생성자를 중복 정의합니다.
class Book{
    public String name;
    public String publisher;
    Book(){
        this.name = "";
        this.publisher = "";
    }
    Book(String name, String publisher){
        this.name = name;
        this.publisher = publisher;
    }
    void print(){
        System.out.println("print : Book");
    };
}
  • 메서드 오버라이딩
    • 자식 클래스인 Novel, SF 클래스에 부모 메서드를 오버라이딩하여 재정의합니다.
class Novel extends Book{
    public String name;
    public String publisher;
    Novel(String name, String publisher){
        super(name, publisher);
    }
    @Override
    void print(){
        System.out.println("print : Novel");
    }
}
class SF extends Book{
    public String name;
    public String publisher;
    SF(String name, String publisher){
        super(name, publisher);
    }
    @Override
    void print(){
        System.out.println("print : SF");
    }
}
  • 업캐스팅하여 객체 선언
    • 업캐스팅 (자식 클래스 객체를 부모 클래스로 형변환)하여 아래와 같이 객체를 선언합니다.
Book b = new Novel("메타버스 소설","출판사(IT)");
Book c = new SF("메타버스", "SF출판사");
  • 부모 클래스 객체로 자식 메서드 호출
    • 부모 클래스 Book으로 생성된 객체의 멤버 함수를 선언합니다.
    Book b = new Novel("소설","소설출판사");
b.print();

    Book c = new SF("메타버스", "SF출판사");
c.print();
  • 결과적으로 위와 같이 다형성을 활용하면 부모 클래스로 객체를 선언했으나 실행시점에 동적 바인딩되어 자식클래스의 멤버함수가 호출되는 것을 볼 수 있습니다.
print : Novel
print : SF
  • 객체 타입 확인 : instanceof
    • instanceof는 객체 타입을 확인하는 연산자로, 객체의 실제 타입을 알아보기 위한 연산자입니다.
    • 아래와 같이 "객체 instanceof 타입"과 같이 사용합니다.
객체 instanceof 타입
  • instanceof 연산자는 해당 객체가 타입이 맞다면 true, 아니면 false를 반환합니다.
    if(c instanceof SF){
        c.print(); //true
    }
if(b instanceof Novel){
        b.print(); //true

'개발일지' 카테고리의 다른 글

01.27 TIL  (1) 2023.01.27
01.26 SOLID  (0) 2023.01.26
01.25 TIL  (0) 2023.01.25
01.19 TIL  (0) 2023.01.19
01.19 배열  (0) 2023.01.19