본문 바로가기

컴퓨터 과학(CS)

객체 지향 프로그램이란 무엇인가

Object Oriented Programming

 

 

객체 지향 프로그래밍 이전의 프로그래밍 패러다임을 살펴보면, 중심이 컴퓨에 있었다.

컴퓨터가 사고하는대로 프로그래밍을 하는것이다. 하지만, 객체지향 프로그램밍이랑 인간 중심적 프로그래밍 패러다림이라고 할 수 있다.

즉, 현실 세계를 프로그래밍으로 옮겨와 프로그래밍하는 것을 말한다.

현실 세계의 사람들을 객체라고 보고 그 객체로부터 개발하고자 하는 애플리케이션에 필요한 특징들을 뽑아와 프로그래밍 하는 것이다.  이것을 추상화라 한다.

 

OOP 로 코드를 작성하면 이미 작성한 코드에 대한 재사용성이 높다. 자주 사용되는 로직을 라이브러리로 만들어두면 계속 사용할 수 있으며 그 신뢰성을 확보 할 수 있다. 또한, 라이브러리를 각종 예외상황에 맞게 잘 만들어두면 개발자가 사소한 실수를 하더라도 그 예외를 컴파일 단계에서 잡아낼 수 있으므로 버그 발생이 줄어든다.

또한 내부적으로 어떻게 동작하는지 몰라도 개발자는 라이브러리가 제공하는 기능들을 사용할 수 있기 떄문에 생산성이 높아지게 된다.

객체 단위로 코드가 나눠져 작성되기 떄문에 디버깅이 쉽고 유지보수에 용이하다. 또한, 데이터 모델링을 할 때 객체와 매핑하는 것이 수월하기 때문에 요구사항을 보다 명확하게 파악하게 프로그래밍을 할 수 있다.

 

객체 간의 정보 교환이 모두 메시지 교환을 통해 일어나므로 실행 시스템에 많은 overhead가 발생하게 된다.

하지만, 이것은 하드웨어의 발전으로 많은 부분 보안되었다.

객체 지향 프로그래밍의 치명적인 단점은 함수형 프로그래밍 패러다임의 등장 배경을 통해서 알 수 있다. 바로 객체가 상태가 갖는다는 것이다. 변수가 존재하고 이 엽ㄴ수를 통해 객체가 예측할 수 없는 상태를 갖게 되어 애플리케이션 내부에서 버그를 발생시킨다는 것이다.

이러한 이유로 함수형 패러다임이 주목받고 있다.

 

 

정의

 

객체 지향의 가장 기본은 객체이며, 객체의 핵심은 기능을 제공하는 것이다.

실제로 객체를 정의할 때 사용하는 것은 객체가 제공해야 할 기능이며, 객체가 내부적으로 어떤 데이터를 갖고 있는 지로는 정의되지 않는다.

이러한 기능들을 오퍼레이션(operation)이라고 부른다.

즉, 객체는 오퍼레이션으로 정의가 된다.

 

 

시그니처

 

객체 지향 설계하기 위해서는 오퍼레이션의 사용법을 알아야 한다.

오페레이션의 사용법은 다음 세 가지로 구성된다.

기능 식별 이름

파라미터 및 파라미터 타입

기능 실행 결과 값 및 타입

이 세 가지를 시그니처(Signature) 라고 부른다.

 

 

인터페이스

 

객체가 제공하는 모든 오퍼레이션 집합을 객체의 인터페이스(interface)라고 부른다.

JAVA 언어에서의 인터페이스가 아니라, 객체를 사용하기 위한 명세를 의미한다고 보면 된다.

 

 

메시지

 

오퍼레이션의 실행을 요청하는 것을 메시지를 보낸다라고 표현한다.

자바에서 메서드를 호출하는 것이 메시지를 보내는 과정에 해당된다.

 

 

책임

 

객체가 자신이 제공하는 기능으로 정의된다는 것은 객체마다 자신만이 제공할 수 있는 기능에 대한 책임이 있다.

객체가 갖는 책임의 크기가 적을수록 좋다.

즉, 객체가 제공하는 기능의 개수가 적은 것이 좋은 것이다.

한 객체에 많은 기능이 포함되면, 그 기능과 관련된 데이터들도 한 객체에 모두 포함된다.

이 경우, 객체에 정의된 많은 오퍼레이션들이 데이터들을 공유하는 방식으로 프로그래밍 되는데, 정차지향 방식과 다를 바가 없다.

따라서 책임의 크기는 작을수록 유연해진다.

이것을 단일 책임 원칙(Single Reponsibility Principle ) 라고 한다.

 

 

 

의존성

 

한 객체가 다른 객체를 이용한다는 것은 실제 구현에서는 한 객체의 코드에서 다른 객체를 생성하거나 다른 객체의 메서드를 호출한다는 것을 의미한다.

의존의 영향은 꼬리에 꼬리를 문 것처럼 전파된다.

이러다가 변경한 여파가 다시 자기 자신까지 변화시킬 수 있는데, 이것을 순환 의존이라고 한다.

이것을 해결하기 위해 사용한 방법을 Dependencry Inversion principle(DIP)라고 한다.

 

 

캡슐화

 

객체지향은 기본적으로 캡슐화를 통해서 한 곳의 변화가 다른곳에 미치는 영향을 최소화한다.

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

내부 기능 구현이 변경되더라도, 그 기능을 사용하는 코드는 영향을 받지 않도록 해준다.

즉, 내부 구현 변경의 유연함을 주는 기법이 캡슐화이다.

 

 

  • Tell, Don't Ask

데이터를 물어보지 않고, 기능을 실행해 달라고 말하라는 규칙이다.

데이터를 읽는 것은 데이터를 중심으로 코드를 작성하게 만드는 원인이 된다.

 

public class Customer {
   private int name;
   public int getName() {
     return name;
   }
}

 

데이터를 private 으로 클래스를 내부에 숨기고, 메소드를 통해 데이터에 접근한다.

 

  • 데미테르 법칙

메서드에서 생성환 객체의 메서드만 호출

파라미터로 받은 객체의 메서드만 호출

필드로 참조하는 객체의 메서드만 호출

 

 

상속을 통한 재사용 단점

 

1. 상위 클래스 변경의 어려움

어떤 클래스를 상속받는다는 것은 그 클래스에 의존한다는 뜻이다.

따라서 의존하는 클래스의 코드가 변경되면 영향을 받을 수 있는 것이다.

변경의 여파가 계층도를 따라 전파되게 된다.

 

2. 클래스의 불필요한 증가

유사한 기능을 확장하는 과정에서 클래스의 개수가 불필요하게 증가할 수 있다.

 

3. 상속의 오용

같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면 잘못된 사용으로 인한 문제가 발생한다.

상속을 받는 클래스가 상위 클래스와 IS-A 관계가 아닌 경우가 그 예이다.

 

이러한 문제를 해소하는 방법이 객체 조립(Composition)이다.

객체지향언어에서 객체 조립은 보통 필드에서 다른 객체를 참조하는 방식으로 구현된다.

 

상속에 비해 조립을 통해 재상용의 단점은 상대적으로 런타임 구조가 복잡해진다는 것이다.

또 상속보다 구현이 어렵다.

하지만 구현/구조의 복잡함보다 변경의 유연함을 확보하는데서 오는 장점이 크기 떄문에, 상속보다 조립하는 방법을 먼저 고려해야 한다.

 

 

그렇다면 상속은 언제 사용해야 하는가?

 

상속을 사용할때는 재상용이라는 관점이 아닌, 기능의 확장이라는 관점에서 상속을 적용해야 한다.

또한 추가로 명확한 IS-A 관계가 성립되어야 한다.

 

 

OOP의 4가지 특성

 

1) 추상화(Abstraction)

자료 추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현해 프로그램을 간단히 만듭니다.

 

2) 캡슐화(Encapsulation)

데이터와 메소드를 클래스 하나로 묶어 메소드로 접근 할 수 있도록 합니다. 또한 객체 내부에서 필요로 하는 정보를 은닉을 하는 특징 또한 존재 합니다. 저는 캡슐 알약과 비슷하다는 생각이 들었습니다. 안에 필요한 약 성분들을 담아서 은폐시키기 때문이죠!

 

3) 상속(Inheritance)

이미 정의 되어있는 상위 클래스와 메소드를 비롯한 모든 속성을 하위 클래스가 물려받습니다.

예를 들어, 동물이라는 클래스의 속성을 강아지 클래스가, 또 고양이 클래스가 물려 받을 수 있습니다.

상속이 필요한 이유는 코드의 중복을 막을 수 있어 코드가 더 간단해 집니다. 그럼 유지보수가 더 수월하겠죠?

 

4)다형성(polymorphism)

다형성은 형태가 같은데 다른 기능을 하는 것을 의미합니다.
부모 클래스로 부터 상속을 받지만, 자식클래스에서 물려받은 속성을 재정의 할 수 있습니다. 이를 오버라이딩(같은 이름의 메소드가 여러 클래스에서 다른 기능을 하는 것)이라고 합니다.

예를 들어, 슈퍼카 클래스가 일반카 클래스의 속도속성을 상속 받았지만, 속도 속성을 재정의 하여 슈퍼카의 속도에 맞게 재정의 할 수 있습니다.

 

 

객체 지향적 설계 원칙

 

1. SRP ( Signle Reponsibility Principle) : 단일 책임 원칙

    클래스는 단 하나의 책임을 가져야 하며 클래스를 변경하는 이유는 단 하나의 이유여야 한다.

 

2. OCP (Open-Closed Principle) : 개방-폐쇄 원칙

     확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

 

3. LSP (Liskov Substitution Principle) : 리스코프 치환 원칙

    상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

 

4. ISP (Interface Segregation Principle) : 인터페이스 분리 원칙

    인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야한다.

 

5. DIP (Dependency Inversion Principle) : 의존 역전 원칙

     고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

 

 

 

 

 

'컴퓨터 과학(CS)' 카테고리의 다른 글

TDD란 무엇인가  (0) 2022.09.08