객체지향 프로그래밍의 개념을 공부하기 전에 그 등장배경을 알게된다면 객체지향 프로그래밍을 이해하는데 도움이 될 것 같다. 따라서 객체지향 프로그래밍이 등장하기 이전 프로그래밍 패러다임 몇 가지를 이야기 해보겠다.
정의한 기능의 흐름에 따라 순서대로 동작을 추가하며 프로그램을 완성하는 방식을 순차적 프로그래밍이라고 한다. 흐름에 따라 코드를 작성하기 때문에 간단한 프로그램의 경우에는 흐름이 눈에 보여 매우 직관적이다. 그러나, 규모가 커지고 로직이 많아지는 경우에는 흐름대로 작성하는 것이 단점이 될 수 있다. 따라서 규모가 큰 프로잭트의 경우 순차적 프로그래밍 방식은 좋지 않다.
이러한 문제 때문에 절차적(구조적) 프로그래밍이 등장했다.
절차적 프로그래밍에서 '절차'는 함수를 의미한다. 따라서 절차적 프로그래밍은 반복되는 동작을 함수 및 프로시저 형태로 모듈화하여 사용하는 방식이다.
프로시저
함수
프로시저와 함수의 차이점
프로시저 | 함수 |
---|---|
특정한 작업을 수행한다. | 특정한 계산을 수행한다. |
리턴값을 가질 수도 있고 안가질 수도 있다. | 리턴 값을 가진다. |
리턴 값을 여러개 가질 수 있다. | 하나의 리턴 값을 가진다. |
서버(DataBase)에서 기술 | 화면(Client)에서 기술 |
수식 내에서 사용이 불가능하다. | 수식 내에서만 사용이 가능하다. |
단독으로 문장 구성이 가능하다. | 단독으로 문장 구성이 불가능하다. |
함수는 프로시저를 포함하고 있는 개념이다.
이러한 프로시저를 포함한 함수들이 여러개 모여 하나의 프로그램을 구성하는 것이 절차적(구조적) 프로그래밍이다.
객체지향 프로그래밍(Object-Oriented Programming, OOP)는 프로그래밍에 필요한 데이터를 추상화 시켜서 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.
어떤 개념에 대한 자료형과 함수를 '객체' 형태로 함께 묶어서 관리하기 위해 객체지향 프로그래밍 패러다임이 등장하게 되었다. 핵심 포인트는 객체 내부에 자료형 필드와 함수가 함께 존재하는 것이다. 가능한 모든 물리적, 논리적 요소를 객체로 만드는 것이 객체지향 프로그래밍이다.
결론적으로 객체 간의 독립성이 뚜렷하게 생기고, 중복되는 코드의 양이 줄어든다. 따라서 유지보수에 용이해질 것이다.
객체지향 프로그래밍의 중심이 클래스나 상속이라고 생각하는 경우가 꽤 있다. 그러나 이것은 오해이다. 객체지향 패러다임의 핵심은 자율적인 객체들의 협력과 다형성이다.
더 자세한 정보가 궁금하다면 "객체지향의 사실과 오해(조영호 지음)"를 읽어보자
객체는 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미하며 값을 저장하는 변수와 작업을 수행하는 메소드를 서로 연관된 것들끼리 묶어서 만든 것이다. 객체지향 프로그래밍을 레고라고 한다면 객체는 레고 조각이 될 것이고, 레고 조각을 조립해서 집을 만들거나 무언가를 만드는 것이 객체지향 프로그래밍이다.
객체는 또한 레고 조각처럼 재사용 할 수 있는데 이는 부품화와 재사용성이라는 객체지향 프로그래밍의 특징을 보여주기도 한다.
객체지향 프로그래밍은 추상화
, 캡슐화
, 상속
, 다형성
4가지의 특성을 가진다.
추상적인 개념에 의존하여 설계해야 유연함을 가질 수 있다. 즉, 세부적인 사물들의 공통적인 특징을 파악한 후, 하나의 묶음으로 만들어내는 것이 추상화다.
위 그림을 예시로 보자면 자동차 브랜드인 'Audi', 'Nissan', 'Volvo' 등은 모두 다른 자동차 브랜드이다. 그러나, 모두 '자동차'라는 상위 개념에 포함된다. 이것이 추상화이다.
추상화가 왜 필요할까?
'현대'와 같은 다른 자동차 브랜드가 추가될 수도 있다. 이때 추상화로 '자동차'를 구현 해놓으면 다른 곳의 코드를 수정할 필요 없이 추가로 만들 부분만 새로 생성해주면 된다.
캡슐화를 통해서 한 곳에서 변화가 일어나도 다른 곳에 미치는 사이드 이펙트를 최소화 시키는 것이다. 즉, 객체 내부의 어떤 동작에 대한 구현이 어떻게 되어있는지 마치 캡슐 내부에 숨겨져 있는 것처럼 감추는 것이다. 이를 통해서 외부에서 실수를 해도 객체가 손상되는 일을 방지한다.
결합도?
어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인지를 나타내는 지표이다. 객체 간의 독립성을 강조하기 위해 객체지향 프로그래밍이 등장했다. 그런데 결합도가 높다면 객체지향으로 설계하는 의미가 있을까?
따라서 독립적으로 만들어진 객체들 간의 의존도가 최대한 낮게 만드는 것이 중요하다. 때문에, 객체 내의 모듈 간의 요소가 밀접한 관련이 있는 것으로 구성하여 응집도를 높이고, 결합도를 줄여야 요구사항 변경에 대처하는 좋은 설계이다.
한 줄 요약
객체 각각은 독립적으로 작용할 수 있도록 응집도가 강해야 하고, 다른 모듈을 참조하는 결합도는 낮아야 한다.
은닉화
높은 응집도와 낮은 결합도는 '은닉화'를 통해 이루어낼 수 있다. 외부에서 접근할 필요 없는 것들은 접근 지정자를private
으로 두어 접근에 제한을 두는 것이다. 외부 객체는 객체 내부의 구조를 모르게 하고, 해당 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있도록 하여 의도하지 않은 동작 오류를 방지하고 유지보수 효율을 높일 수 있다.
상속이란 여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정이다. 흔히 일반화라고 한다.
상속은 자식 클래스가 부모 클래스의 필드, 메소드를 물려받아 사용하고 응용할 수 있게 해준다. 엄밀히 따지면 상속은 자식 클래스를 외부로부터 은닉하는 캡슐화의 일종이다.
상속의 장단점
장점 | 단점 |
---|---|
재사용으로 인한 코드가 줄어든다. | 부모 클래스의 변경이 어려워진다. |
범용적인 사용이 가능하다. | 불필요한 클래스가 증가할 수 있다. |
자료와 메서드의 자유로운 사용 및 추가가 가능하다. | 상속이 잘못 사용될 수 있다. |
다형성이란 서로 다른 클래스의 객체가 같은 동작 수행 명령을 받았을 때, 각자의 특성에 맞는 방식으로 동작하는 것이다.
객체지향 프로그래밍은 하나의 클래스 내부에 같은 이름의 행위를 여러개 정의하거나 부모 클래스의 행위를 자식 클래스에서 재정의하여 사용할 수 있기 때문에 다형성이라는 특징을 가진다.
다형성은 상속과의 시너지가 좋다. 다형성 구현을 통해 코드를 간결하게 하고, 유연함을 갖게 한다. 또한, 구체적으로 현재 어떤 클래스 객체가 참조되는지에 대한 것과는 무관하게 헐렁하게 프로그래밍하는 것이 가능하다.
예를 들어 "사람이 자동차를 운전한다." 를 생각해보자.
이때 자동차는 K3, 아반떼, 테슬라 모델3 등 여러가지가 될 수 있다.
모두 같은 자동차이지만 다른 디자인과 기능을 가지고 있다.
즉, 부모 클래스의 메소드를 자식 클래스가 오버라이딩해서 자신의 역할에 맞게 활용하는 것이 다형성이다. 이처럼 다형성을 사용하면, 구체적으로 현재 어떤 클래스 객체가 참조되는 지는 무관하게 프로그래밍하는 것이 가능하다.
상속 관계에 있으면, 새로운 자식 클래스가 추가되어도 부모 클래스의 함수를 참조해오면 되기 때문에 다른 클래스는 영향을 받지 않게 된다.
오버로딩(Overloading)
오버라이딩(overriding)
장점
단점
객체지향에서는 다형성이 가장 중요하다! 자바의 스프링은 다형성을 극대화해서 이용할 수 있게 도와준다. 스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
출처
[도서] 객체지향의 사실과 오해 - 조영호 지음
객체지향 프로그래밍
객체지향 프로그래밍이란?
객체지향 프로그래밍 제대로 이해하기
유익한 글이었습니다.