웹 프로그래밍/[ Spring ]

[ Spring ] 00. OOP(객체지향 프로그래밍)

kim.svadoz 2021. 6. 30. 16:28
반응형

스프링 프레임워크에 대해 알아보기 전에 먼저, 객체지향에 대해서 먼저 살펴볼 것이다.

OOP


Object-Oriented Programming

객체지향 패러다임이 나오기 이전의 패러다임들부터 간단하게 살펴보자.

패러다임의 발전 과정을 보면 점점 개발자들이 편하게 개발할 수 있도록 개선되고 있다는 것을 알 수 있다.

가장 먼저, 순차적, 비 구조적 프로그래밍이 있다. 말 그대로 순차적으로 코딩하는 것이다.

필요한 게 있으면 계속 순서대로 추가해가며 구현하는 방식이다. 직관적이야 하겠지만 점점 규모가 커지면 어떻게 될까?

이러한 순차적, 비 구조적 프로그래밍에서는 goto문을 활용한다.

만약 이전에 작성했던 코드가 다시 필요하면 그 곳으로 이동하기 위한 것이다. 규모가 커지면 커질수록 goto문을 무분별하게 사용하게 되고, 마치 스파게티 처럼 베베 꼬이게 된다.(스파게티 코드). 나중에 코드가 어디가 어떻게 연결되어 있는지 확인하기 조차 어렵게 되는 문제점이 발생한다.

오늘날 공부하면서 goto문은 사용하지 않는게 좋다!라는 말을 들어봤을 것이다. goto문은 장기적으로 봤을 때 도움이 되지 않는 방식이라는 것은 자명하기 때문이다.

이런 문제점을 해결하기 위해 탄생한 것이 바로 절차적, 구조적 프로그래밍이다.

반복될 가능성이 있는 것들을 재사용이 가능한 함수(프로시저)로 만들어 사용하는 프로그래밍 방식이다.

여기서 '절차'라는 의미는 함수(프로시저)를 뜻하고, 구조는 모듈을 뜻한다. 모듈이 함수보다 더 작은 의미긴 하지만, 요즘은 큰 틀로 같은 의미로 쓰인다.

프로시저란?

반환값(리턴)이 따로 존재하지 않는 함수를 말한다.

예를들면, printf와 같은 함수는 반환값을 얻기 위한 것보단, 화면에 출력하는 용도로 쓰이는 함순데, 이와 같은 함수를 프로시저라 부른다.
(정확히는 printfint형을 반환하긴 하지만, 목적 자체는 프로시저에 가깝다.)

하지만 이런 절차적 프로그래밍에도 문제점이 존재하는데, 너무 추상적이라는 것이다.

실제로 사용되는 프로그램들은 추상적이지만은 않다. 함수는 논리적 단위로 표현되지만, 실제 데이터에 해당하는 변수나 상수 값들은 물리적 요소로 연결되어 있기 때문이다.

도서 관리 프로그램이 있다고 가정해보자.

책에 해당하는 자료형(필드)를 구현해야 한다. 또, 책과 관련된 함수도 구현해야 한다. 구조적인 프로그래밍에서는 이들을 따로 만들어야 한다.

결국 많은 데이터를 만들어야 할 때, 구분하기 힘들고 비효율적으로 코딩할 가능성이 높아진다.

책에 대한 자료형, 책에 대한 함수가 물리적으로 같이 있을 수 는 있지만(같은 위치에 기록)

논리적으로는 함께할 수 없는 구조가 구조적 프로그래밍이다.

따라서, 이를 한 번에 묶기 위한 패러다임이 탄생한다.

바로 객체지향 프로그래밍이다.

우리가 VO를 만들때와 같은 형태다.

클래스마다 필요한 필드르 선언하고, gettersetter로 구성된 모습으로 해결한다. 바로 특정한 개념의 함수와 자료형을 함께 묶어서 관리하기 위해 탄생한 것이다.

가장 중요한 점은, 객체 내부에 자료형(필드)와 함수(메소드)가 같이 존재하는 것이다.

이제 도서관리 프로그램을 만들 때, 책의 제목, 저자, 페이지와 같은 자료형과 읽기, 예약하기 등 메소드이라는 객체에 한번에 묶어서 저장하는 것이 가능해졌다.

이처럼 가능한 모든 물리적, 논리적 요소를 하나의 객체로 만드려는 것이 객체지향 프로그래밍이라 할 수 있다.

객체지향 프로그래밍을 사용하게 되면, 객체간의 독립성이 생기고 중복코드의 양이 줄어드는 장점이 있따. 또한, 독립성이 확립되면 유지보수에도 도움이 될 것이다.

특징

객체 지향의 패러다임이 생겨나면서 크게 4가지 특징을 갖추게 되었다.

이 4가지 특징을 잘 이해하고 구현해야 객체를 통한 효율적인 프로그래밍이 가능해진다.

1. 추상화

Abstraction

필요로 하는 속성이나 행동을 추출하는 작업

추상적인 개념에 의존하여 설계해야 유연함을 갖출 수 있다.

즉, 세부적인 사물들의 공통적인 특징을 파악한 후 하나의 집합으로 만들어내는 것이 추상화이다.

아우디, 벤츠, BMW는 모두 '자동차'라는 공통점이 있다.
자동차라는 추상화 집합을 만들고, 자동차들이 가진 공통적인 '특징'들을 만들어 활용한다.

예를 들면, 현대와 같은 다른 자동차 브랜드가 추가될 수 있다.
이 때 추상화로 구현해 두면 다른 곳의 코드는 수정할 필요 없이 추가되는 부분만 새로 생성해주면 된다.

- 특징

  • 추상클래스 - abstract의 의미
    • 미완성된 클래스, 모든 내용이 구현이 되어있지 않은 클래스로 완성되지 않았으므로 객채생성을 할 수 없다.
    • 메소드의 body가 구현되지 않은 메소드를 갖고 있는 클래스(추상메소드)
  • 추상 메소드를 선언하는 방법
    • 접근제어자 abstract 리턴타입 메소드명(매개변수 list 1, 2, 3, ...)
    • 추상메소드가 정의된 클래스는 미완성된 추상클래스가 되므로, 일반클래스와 다르다.
    • 따라서, 추상클래스를 정의하는 경우 클래스 선언부에도 abstract을 추가해야 한다.
  • 추상 클래스의 특징
    • 일반메소드와 추상메소드 모두 정의할 수 있다.
    • 내가 기능을 작성하는게 아니라 하위클래스에서 기능을 한다.
    • 추상메소드가 한 개라도 정의되어 있는 클래스는 반드시 abstract을 추가해야 한다.
    • 추상클래스는 인스턴스화 할수 없다.(객체 생성 불가능)
    • 추상클래스(abstract클래스)를 상속하면 에러가 발생한다?
    • AbstractSub클래스가 abstract메소드를 갖고있는 AbstractSuper클래스를 상속하면서 AbstarctSub클래스도 추상클래스로 변경된 것이다.
    • [해결방법]
      • 상위클래스로 사용될 목적으로 만들어진 클래스라면 클래스 선언부에 abstract을 추가한다.
      • 모든 "추상메소드"를 반드시 오버라이딩해야 한다.
  • 추상클래스와 추상메소드를 사용하는 이유?
    • 다형성을 정의하기 위해
    • 상위클래스로 사용하기 위한 목적(객채생성을 문법적으로 제한하기 위해)
    • 하위클래스에서 반드시 재정의해야하는 메소드를 문법적으로 정의하여 반드시 재정의하도록 하기위해

2. 캡슐화

Encapsulation

낮은 결합도를 유지할 수 있도록 설계하는 것

쉽게 말하면, 한 곳에서 변화가 일어나도 다른 곳에 미치는 영향을 최소화 시키는 것을 말하낟.

객체가 내부적으로 기능을 어떻게 구현하는지 감추는 것이다.

결합도가 낮게 만들어야 하는 이유가 무엇일까?

여기서 결합도(coupling)이란, 어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인가를 나타내는 정도이다.

즉, 독립적으로 만드어진 객체들간의 의존도가 최대한 낮게 만드는 것이 중요하다. 객체들 간의 의존도가 높아지면 굳이 객체지향으로 설계하는 의미가 옅어지기 때문이다.

우리는 소프트웨어 공학에서 객체 안의 모듈 간의 요소가 밀접한 관련이 있는 것으로 구성하여 응집도를 높이고 결합도를 줄여야 요구사항 변경에 대처하는 좋은 설계 방법이라고 배운다.

이것이 바로 캡슐화와 연관된 부분이라고 할 수 있다.

그렇다면 캡슐화는 어떻게 높은 응집도납은 결합도를 갖게 할까?

바로 정보 은닉을 활용하는 것이다.

외부에서 접근할 필요가 없는 것들은 private으로 접근하지 못하도록 제한을 두는 것이다.

객체안의 필드를 선언할 때는 private으로 선언하라는 말이 바로 이 때문이다.

3. 상속

일반화 관계(Generalization)이라고도 하며, 여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정이다.

일반화(상속)은 또 다른 캡슐화다.

자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이라고 말할 수 있다.

하위 객체는 상위 객체(부모)의 특징을 물려 받는데, 이 상위 객체의 메소드나 변수를 구현하는가 그대로 사용하는가에 따라서 상속의 형태가 갈리게된다.

  1. extends
    • 부모에서 선언 / 정의 를 모두하며 자식은 메소드 / 변수를 그대로 사용할 수 있음
  2. implements (interface 구현)
    • 부모 객체는 선언만 하며 정의(내용)은 자식에서 오버라이딩(재정의)해서 사용해야 함
  3. abstract
    • extends와 interface의 혼합으로. extends하되 몇 개는 추상 메소드로 구현되어 있음
# 요약
1. `extends`는 일반 클래스와 abstract 클래스 상속에 사용되고, `implement`는 interface상속에 사용된다.
2. class가 class를 상속받을 땐 extends를 사용하고 interface가 interface가 상속 받을 땐 extneds를 사용한다.
3. class가 interface를 사용할 땐 implements를 써아햐고
4. interface가 class를 사용할 땐 implements를 쓸 수 없다.
5. extends는 클래스 한 개만 상속 받을 수 있다.(단일상속)
6. extends 자신 클래스는 부모 클래스의 기능을 사용한다.
7. implements는 여러 개 사용 가능하다. (다중상속 해법)
8. implements는 설계 목적으로 구현이 가능하다.
9. implements한 클래스는 implements의 내용을 다 사용해야 한다.

extends는 클래스를 확장하는 것이고 implemtns는 인터페이스를 구현하는 것이다.
인터페이스와 보통 클래스의 차이는 인터페이스는 정의한 메소드를 구현하지 않아도 된다.
인터페이스를 상속받는 클래스에서 인터페이스에 정의된 메소드를 구현하면 된다.

아까 자동차를 예로 들어 추상화를 설명했다. 여기에 추가로 대리 운전을 하는 사람 클래스가 있다고 생각해보자.

이 떄, 자동차의 자식 클래스에 해당하는 벤츠, BMW, 아우디 등은 캡슐화는 통해 은닉해둔 상태이다.

사람 크랠스의 관점으로는 구체적인 자동차의 종류가 숨겨져 있는 상태이다. 대리 운전자 입장에서는 자동차의 종류가 어떤 것인지는 운전하는 데 크게 중요하지 않다.

새로운 자동차들이 추가된다고 해도, 사람 클래스는 영향을 받지 않는 것이 중요하다. 그러므로 캡슐화를 통해 사람 클래스 입장에서는 확인할 수 없도록 구현하는 것이다.

이처럼, 상속 관계에서는 단순히 하나의 클래스 안에서 속성 및 연산들의 캡슐화에 한정되지 않는다. 즉, 자식 클래스 자체를 캡슐화하여 '사람 클래스'와 같은 외부에 은닉하는 것으로 확장되는 것이다.

이렇게 자식 클래스를 캡슐화 해두면, 외부에선 이러한 클래스들에 영향을 받지 않고 개발을 이어갈 수 있는 장점이 있다.

- 상속 재사용의 단점

  1. 상위 클래스(부모 클래스)의 변경이 어려워 진다.
    • 부모 클래스에 의존하는 자식 클래스가 많을 때, 부모 클래스의 변경이 필요하다면 이를 의존하는 자식 클래스들이 영향을 받게 된다.
  2. 불필요한 클래스가 증가할 수 있다.
    • 유사기능 확장 시, 필요 이상의 불필요한 클래스를 만들어야 하는 상황이 발생할 수 있다.
  3. 상속이 잘못 사용될 수 있다.
    • 같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면, 문제가 발생할 수 있다.
    • 상속 받는 클래스가 부모 클래스와 IS-A 관계가 아닐 때 이에 해당한다.

- 해결책

객체 조립(Compostion), 컴포지션이라고 부른다.

객체 조립은, 필드에서 다른 객체를 참조하는 방식으로 구현된다.

상속에 비해 비교적 런타임 구조가 복잡해지고, 구현이 어려운 단점이 존재하지만 변경 시 유연함을 확보하는 데 장점이 매우 크다.

따라서 같은 종류가 아닌 클래스를 상속하고 싶을 때는 객체조립을 우선적으로 적용하는 것이 좋다.

그럼 상속은 언제 사용해?

  • IS-A 관계가 성립할 때
  • 재사용 관점이 아닌, 기능의 확장 관점일 때

- 특징

  • 상속관계에서 멤버변수가 갖는 특징

    1. 상속관계에서는 상위클래스에서 정의된 멤버변수를 하위클래스에서 사용할 수 있다.

      (하위클래스 참조 변수를 통해서 접근할 수 있다.)

    2. 상위클래스의 변수와 동일한 이름의 변수를 하위클래스에 정의하는 경우 하위클래스의 멤버변수가 우선순위가 높다.

    3. 부모클래스의 변수를 액세스 하려면 super를 이용해서 접근한다.

      • this - 자기자신의 객체
      • super - 부모 객체
    4. private으로 선언된 변수는 상속관계에 있다고 하더라도 하위클래스에서 접근할 수 없다.

    5. 상위클래스는 하위클래스의 일반적인 내용을 정의하기 위해서 사용하는 클래스이므로 주로 하위클래스를 생성해서 사용한다.

    6. 상위클래스를 쓰는게 아니라 하위클래스(자식)를 쓰는 것이다.

  • 상속관계에서 메소드가 갖는 특징

    1. 상속관계에서는 상위클래스에 정의된 메소드를 하위클래스에서 사용할 수 있다.

      (하위클래스 참조 변수를 통해서 접근할 수 있다.)

    2. 상위클래스에 정의된 메소드와 동일한 메소드를 하위클래스에서 정의하고 사용할 수 있다. 이런 경우 하위클래스의 메소드가 호출된다.

      • 메소드를 재정의 했으면 부모보다 재정의한 메소드가 우선인식된다.

      • 상위클래스에 선언된 메소드와 동일한 메소드를 하위클래스에 정의하는 것을 메소드 재정의

        (메소드 오버라이딩)이라고 한다.

        • 메소드 선언부가 부모클래스의 메소드 선언부와 반드시 일치해야한다.

          => 메소드명, 매개변수 갯수, 매개변수 타입, 리턴타입이 모두 동일해야한다

    3. 부모클래스의 메소드를 사용하고 싶은 경우 super로 호출한다.

    4. 모든 클래스의 첫째줄에는 자동으로 object가 상속되어 있다.

  • 상속관계에서 생성자가 갖는 특징

    1. 클래스의 모든 생성자메소드의 첫 번째 문장에는 부모클래스의 기본 생성자를 호출하는 명령문이 생략되어 있다.
      • 부모클래스도 메모리에 할당되어야 사용할 수 있으므로
      • 부모의 생성자를 호출하는 방법은 super(...)
        • super()는 부모의 매개변수 없는 기본 생성자
      • 이미 다른 생성자를 호출하는 경우에는 super()를 할 수없다.
        • this()를 호출하는 경우 super를 호출할 수 있다.
    2. 모든 클래스의 최상위 클래스는 java.lang.Object이다.
      • 자바에서 실행되는 모든 객체가 갖는 공통의 특징을 정의한 클래스로 상속받고 있는 클래스가 없는 경우 컴파일러가 자동으로 상속하도록 한다.
    3. 부모클래스에 정의되어 있는 멤버변수가 값을 셋팅해야 하는 경우에도 하위클래스에서 전달될 수 있도록 정의한다.
      • super(값1, 값2, .....)를 통해 접근한다

4. 다형성

Polymorphism

서로 다른 클래스가 객체가 같은 메세지를 받았을 때 각자의 방식으로 동작하는 능력

상속, 오버라이딩, 추상클래스, 객체의 형번환과 같은 개념들의 총체적 융합이다.

객체지향의 핵심과도 같다.

다형성은, 상속과 함께 활용할 때 큰 힘을 발휘한다. 코드를 간결하게 해주며 유연함을 갖추게 해준다.

부모 클래스의 메소드를 자식 클래스가 Overriding(오버라이딩)해서 자신의 역할에 맞게 활용하는 것이 다형성이다.

다형성을 사용하면, 구체적으로 현재 어떤 클래스 객체가 참조되는 지는 무관하게 프로그래밍하는 것이 가능하다.

상속관계에 있으면, 새로운 자식 클래스가 추가되어 부모 클래스의 함수를 참조해오면 되기 때문에 다른 클래스는 영향을 받지 않게 된다.

- 특징

  • 부모타입의 변수로 선언하면 모두 받을 수 있다.

  • type이 Parent라면 Parent밖에 접근 못한다.

  • 동일한 타입(상위 타입)의 변수를 선언 => 실행시점에 다양한 객체가 실행될 수 있게끔 =>

    => 다양한 객체의 다양한 메소드 실행(상속관계에 있어야 한다.)

  • 유지보수하고 추가된 것들을 반영하기 위해

  • 시스템의 모듈화

  • 이 모든것의 베이스는 "다형성"을 적용할 수 있기 때문이다. - API가 가장 좋은 예시.

  • 결국 상속관계, 클래스 설계를 잘 해야한다.

  • 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하기위해

+ 인터페이스

추상메소드(상수도 포함)만 정의하는 특별한 클래스

  • 인터페이스는 interface키워드를 이용해서 정의

  • 인터페이스는 추상메소드만 정의하는 특별한 클래스

    • public abstract이 생략 가능
    • 상속을 받으면 자동으로 추가된다.
  • 인터페이스가 인터페이스를 상속할 수 있다.(extends 이용)

    • 하위 인터페이스가 상위인터페이스의 추상메소드를 상속받는다.
  • 클래스가 인터페이스를 상속할 수 있다.(implements 이용)

    • 인터페이스를 상속받는 클래스가 이미 다른클래스를 상속하는 경우에 extends를 먼저 정의하고 implements를 정의해야 한다.
  • 인터페이스는 여러개를 상속할 수 있다. 즉, 다중 상속이 가능하다.

    • implements 뒤에서 인터페이스를 정의할 때, " , " 로 구분해서 나열
  • 클래스와 인터페이스들을 상속받는 하위클래스는 모든 클래스와 인터페이스의 하위로 인식된다.

    (상속받는 모든 클래스, 인터페이스의 하위 타입이 된다.) - 하위 메소드들에게 스펙을 제시.

  • 원래 자바는 단일상속만 가능하지만, 인터페이스를 이용해 다중상속을 구현한다.

사용목적

  1. 다중 상속을 허용하고 다형성을 적용할 수 있도록 한다.
  2. 기본적으로 구현해야 하는 기능이 무엇인지 정의하기 위한 목적
반응형