객체 지향 (OOP)

김태웅·2023년 7월 14일
0

개발스터디

목록 보기
8/8
post-thumbnail

객체지향

프로그램 설계 방법론의 일종, 프로그램을 수많은 '객체'의 단위로 나누고 이들의 상호작용으로 서술하는 방식

역사

초기 프로그래밍 방식은 절차적 프로그래밍 방식이었으나, 날이 갈수록 프로그램이 복잡해지며 중복 코드가 많아지고 코드 자체가 복잡해지기 시작한다.

이를 해결하기 위해, 1968년 다익스트라는 구조적 프로그래밍을 제안한다. 프로그램이라는 큰 문제를 몇개의 작은 문제로 나누어 해결하는 탑다운 방식이다. 하지만, 이는 함수의 데이터 처리 방법을 구조화 했을 뿐, 데이터 자체는 구조화 하지 못해 전역 네임스페이스 포화 문제를 불러일으켰다. 더불어, GUI의 발전과 함께 콘텍스트 문제 등의 대두된다.

이를 극복하기 위한 대안으로 등장한 것이 객체 지향프로그래밍이다.

날이 갈수록 프로그램의 복잡도는 증가하고 있고, 객체 지향 프로그램도 복잡해지면서 이를 간결하게 정리할 필요성이 생기며 '디자인 패턴'이라는 객체 지향 프로그래밍의 약속이 생겼다.

객체 지향은 특정 언어가 아니라 개념 이다. '클래스는 객체이지만 구조체는 객체가 아니다' 라는 식의 설명은 옳지 않다.

요소

캡슐화

변수와 함수를 하나의 단위로 묶는 것, 데이터의 번들링

대개의 언어에서 '클래스'를 통해 구현되고, 해당 클래스의 인스턴스 생성을 통해 클래스 안에 포함된 멤버 변수와 메소드에 쉽게 접근할 수 잇다.

정보 은닉

프로그램의 세부 구현을 외부로 드러나지 않도록 특정 모듈 내부로 감추는 것
내부의 구현은 감추고, 모듈내에서의 응집도를 높이며 외부로의 노출을 최소화 하여 모듈 간의 결합도를 떨어뜨려 유지보수성을 높인다.
대개의 언어에서 사용되는 클래스 기준으로, 클래스 외부에서는 바깥으로 노출된 특정 메소드에만 접근이 가능하며 클래스 내부에서 어떤 식으로 처리가 이루어지는지는 알지 못하도록 설계된다.

public/protected/private와 같은 접근 제한자가 있다.

정보 은닉은 캡슐화로부터 파생된 보조 개념이며, 캡슐화와 같은 의미는 아니다.

상속

자식 클래스가 부모 클래스의 특성과 기능을 그대로 물려받는 것을 말한다. 기능의 일부분을 변경해야 하는 경우, 자식 클래스에서 상속받으 그 기능만을 수정해서 다시 정의하는 '오버라이딩'이 있다.

상속은 캡슐화를 지키면서도 클래스의 재사용이 용이하도록 한다.

다형성

하나의 변수, 또는 함수가 상황에 따라 다른 의미로 해석될 수 있는 것을 말한다.

  • 서브타입 다형성
    상속에서 설명한 오버라이딩과 같은 것을 의미한다. 하위클래스는 상위 클래스의 메소드 위에 자신의 메소드를 덮어쓰고, 상위 클래스의 참조 변수가 어떤 하위 클래스의 객체를 참조하느냐에 따라 호출되는 메소드가 달라진다.
  • 매개타입 다형성
    타입을 매개변소루 받아 새로운 타입을 되돌려주는 기능이다.
    C++의 템플릿, Java/C#의 제네릭
  • 임시 다형성
    • 함수 오버로딩
      C++/C#/Java에서는 함수 오버로딩을 통해 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 할 수 있으나, 너무 많이 사용하는 경우 전체적인 코드의 유지보수가 어려워 지므로 템플릿/제네릭으로 대체한다.
    • 연산자 오버로딩
      C++/C# 등에서는 기본 연산자를 해당 클래스의 역할에 맞게 수행하게 하는 것이 가능하다. Java에선 불가능하며, Kotlin/F# 등 연산자의 신규 정의도 가능한 언어도 있다.
  • 강제 다형성
    • 묵시적 형 변환
      'double a = 30;'이라는 식이 실행되면 int형 값 30은 double로 묵시적 형 변환이 이루어진다.
    • 암시적 형 변환
      'double a = (double)30;'이라는 식은 위와 동일한 결과를 내지만, (double)을 통해 int형 값 30이 double형으로 변환됨을 명시적으로 표현하였다.

원칙

SOLID로 불리는 객체지향 5원칙
설명보다는 예시가 이해하기 편한 것 같다.

SRP: 단일 책임 원칙

객체는 오직 하나의 책임을 가져야 한다. (객체는 오직 하나의 변경의 이유만을 가져야 한다.)

사칙연산 함수를 가지고 있는 계산 클래스가 있다고 치자. 이 상태의 계산 클래스는 오직 사칙연산 기능만을 책임진다. 만일 프로그램이 대대적으로 공사를 들어가게 되더라도 계산 클래스가 수정될만한 사유는 누가 봐도 사칙연산 함수와 관련된 문제뿐이다. 이처럼 단일 책임 원칙은 클래스의 목적을 명확히 함으로써 구조가 난잡해지거나 수정 사항이 불필요하게 넓게 퍼지는 것을 예방하고 기능을 명확히 분리할 수 있게 한다.

OCP: 개방-폐쇄 원칙

객체는 확장에 대해서는 개방적이고 수정에 대해서는 폐쇄적이어야 한다.

이동 메서드에서 이동 패턴을 나타내는 코드를 별도의 메서드로 분리하고, 구현을 하위 클래스에 맡긴다. 그러면 브루들링 클래스에서는 이동 패턴 메서드만 재정의하면 유닛 클래스의 변경 없이 색다른 움직임을 보여줄 수 있다! '유닛' 클래스의 '이동' 메서드는 수정할 필요조차 없다(수정에 대해선 폐쇄). 그냥 브루들링 클래스의 이동 패턴 메서드만 재정의하면 그만인 것이다(확장에 대해선 개방).

'오버라이딩'을 생각하면 쉽다.

LSP: 리스코프 치환 원칙

자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있다.

컴퓨터용 '마우스' 클래스가 있다고 치자. 컴퓨터에 있는 PS/2 포트나 USB 포트를 통해 연결할 수 있고, 마우스를 바닥에 대고 움직이면 컴퓨터가 신호를 받아들인다는 것을 안다. 사용 면에서는 왼쪽과 오른쪽 버튼, 그리고 휠이 있어 사용자가 누르거나 굴릴 수 있을 것이다. 마우스가 볼마우스든 광마우스든, 아니면 GPS를 이용하건 간에 아무튼 사용자는 바닥에 착 붙여 움직일 것이고, 모든 마우스는 예상대로 신호를 보내 줄 것이다. 또한 만약 추가적인 특별한 버튼이 있는 마우스(상속)라도 그 버튼의 사용을 제외한 다른 부분은 보통의 마우스와 다를 바 없으므로 사용자는 그 마우스의 그 버튼이 뭔 역할을 하든 간에 문제 없이 잘 사용한다. 여기까지 나온 마우스들은 LSP를 잘 지킨다고 볼 수 있다.

하지만 오른쪽/왼쪽 버튼 대신 옆쪽 버튼을 사용하는 펜마우스를 처음으로 접하게 되면 사용자는 평소 보던 버튼을 누를 수 없다며 이상을 호소할 것이다. 이런 경우 LSP를 전혀 지키지 못하는 것이다.

ISP: 인터페이스 분리 원칙

클라이언트에서 사용하지 않는 메서드는 사용해선 안 되며, 인터페이스를 다시 작게 나누어 만든다.

게임을 만드는데 충돌 처리와 이펙트 처리를 하는 서버를 각각 두고 이 처리 결과를 (당연히) 모두 클라이언트에게 보내야 한다고 가정하자. 그러면 아마 Client라는 인터페이스를 정의하고 그 안에 충돌전달()과 이펙트전달(이펙트)를 넣어놓을 것이다. 그리고 충돌 서버와 이펙트 서버에서 이 인터페이스를 구현하는 객체들을 모아두고 있으며, 때에 따라 적절히 신호를 보낸다. 하지만 이렇게 해두면 충돌 서버에겐 쓸모없는 이펙트전달 인터페이스가 제공되며, 이펙트 서버에겐 쓸모없는 충돌전달 인터페이스가 제공된다. 이를 막기 위해선 Client 인터페이스를 쪼개 이펙트전달가능 인터페이스와 충돌전달가능 인터페이스로 나눈 뒤, 충돌에는 충돌만, 이펙트에는 이펙트만 전달하면 될 것이다. 또한 Client 인터페이스는 남겨두되 이펙트전달가능과 충돌전달가능 이 둘을 상속하면 된다.

DIP: 의존성 역전 원칙

추상성이 높고 안정적인 고수준의 클래스는 구체적이고 불안정한 저수준의 클래스에 의존해서는 안 된다.

일반적으로 객체지향의 인터페이스를 통해서 이 원칙을 준수할 수 있게 된다. (상대적으로 고수준인) 클라이언트는 저수준의 클래스에서 추상화한 인터페이스만을 바라보기 때문에, 이 인터페이스를 구현한 클래스는 클라이언트에 어떤 변경도 없이 얼마든지 나중에 교체될 수 있다.

함수형 프로그래밍

최근 주목을 받는 함수형 프로그래밍과는 다소 상반된 위치에 있다.
OOP의 경우 프로그램 유지보수시 데이터 추가는 새로운 클래스를 더하는 것으로 비교적 간단하게 가능하지만 operation set을 변경할 때는 관련된 다수 클래스를 수정해야 하므로 난잡해지는 경향이 있다.
반대로 함수형 패러다임에서는 operation set의 추가는 간단하지만 데이터 추가는 관련된 다수의 함수를 바꿔야 하므로 난해한 점이 있다.

하지만, 객체 지향 프로그래밍과 함수형 프로그래밍이 상반된 위치에 있긴 하지만 대비되는 개념은 아니며, 요즘에는 함수형 언어에도 OOP 개념을 추가한다든가(F#), 반대로 객체지향 언어에 함수형 패러다임을 추가하는(C#, C++, Python, Java) 등 멀티패러다임 추세로 가고 있다.

profile
Everything Counts

0개의 댓글