자바를 공부하는 예비 개발자로서 객체 지향 프로그래밍이 뭔지정도는 알고 있어야 하니까 한 번 정리를 해보려고 합니다..
객체 지향과 함께 자주 등장하는 절차적 프로그래밍도 함께 다뤄보고요.
들어가기 전, 객체란 무엇이고, 절차란 무엇인지를 먼저 알아야겠습니다.
객체는 실제 세계를 모델링한 것이라고 표현합니다. 모델링된 어떤 주체를 객체라고 할 수 있습니다. 프로젝트를 하든, 알고리즘 문제를 풀든, 항상 어떤 일을 하는 주체가 있어야 할 것입니다.
이해를 돕기 위해, 제가 읽었던 책에(객체지향의 사실과 오해 - 조영호)서 다룬 예를 인용하면,
"우리가 손님이 되어 카페에 갑니다. 사계절 내내 아이스 아메리카노밖에 못 먹는 저는 캐셔에게 아아 한 잔을 주문합니다. 이곳 카페는 상당히 규모가 커서, 캐셔는 주문만을 받고, 바리스타가 커피를 만들어 줍니다. 주문을 받은 바리스타는 신선한 원두로 시원한 아이스 아메리카노 한 잔을 만들고, 이를 캐셔에게 전달합니다. 캐셔는 주문번호를 불러 손님인 저에게 아아가 나왔음을 알려주고, 저는 시원한 아아를 받아 들고 기분 좋게 카페를 나갑니다."
이 인용문에서, 진하게 표현된 단어들이 바로 "객체"라고 할 수 있습니다. 각 객체들이 하는 일은 명확합니다.
손님은 원하는 것을 주문하고, 캐셔로부터 결과물(아아)을 받습니다.
캐셔는 주문을 받아 바리스타에게 전달하고, 결과물(아아)을 받아 손님에게 전달합니다.
바리스타는 주문을 받고 요청된 주문에 대한 결과물(아아)을 만들어 다시 캐셔에게 전달합니다.
이제 조금은 이해가 되실 것 같습니다. 저 또한 이 예제를 보고 객체에 대해 조금 더 이해할 수 있었습니다..ㅎ
자 이제 절차에 대해 알아봅시다.
절차는 생각보다 쉽습니다. 말그대로 절차입니다. 어떤 일을 하기 위한 순서라고 할 수 있습니다. 이건 따로 예를 들거나 하진 않겠습니다..
자 이제 본론으로 들어가서,
** 지향?
먼저, 지향이라는 말은 무엇을 의미할까요? 어떤 목표를 정해두고, 이를 향해가는 것을 의미합니다.
그렇다면, 객체 지향 프로그래밍은 객체를 중심, 목표로 프로그래밍을 하는 것일테고, 절차 지향 프로그래밍이라는 말은 절차를 중심으로 프로그래밍하는 것을 의미할 것 같습니다.
절차적 언어는 대표적으로 C언어가 있고, 객체 지향 언어로는 Java와 Python, 그리고 C# 등이 있습니다.
근데, 과연 지향이라는 말이 무조건적인 것을 의미할까요?
그건 아닙니다. 솔직히 내가 쓰는 언어로 어떻게 짜든 상관은 없습니다. 지향이라는 말 자체가 단순히 하나의 패러다임을 의미하는 것이지, 무조건 그 방식을 강제하는 것은 아니기 때문입니다.
그리고 사실, 모든 코드는 절차적인 틀이 있기 때문에 어떤 언어건 간에 절차적 프로그래밍이 가능합니다.
따라서, 절차 지향이 아닌 절차적 프로그래밍이라는 표현이 더 맞는 표현입니다. (그래서 위에서도 절차적 언어라는 표현을 사용했습니다.)
**지향이란 코딩하는 방식 또는 방법론의 차이이지, 특정 언어가 특정 지향만을 지원한다는 것은 아닙니다.
그렇다면 이 두 개념은 서로 반대되는 개념일까요? 이 또한 부정입니다.
- 절차적 프로그래밍은 데이터를 중심으로 함수를 만들어 사용하는 방식이고,
- 객체 지향 프로그래밍은 데이터와 기능을 묶어 하나의 객체를 만들어 사용하는 방식입니다.
따라서 이 둘은 상이한 개념일 뿐인데, 왜 이 둘을 나누어 생각할까요?
이에 대해 알아보기 위해, 짧막한 프로그래밍 패러다임의 역사를 살펴보려 합니다. 역사라서 재미없을 순 있지만, 짧막하니 읽어보시면 재밌을 듯 합니다.
과거에는 지금과 같은 큰 규모와 소프트웨어가 필요하지 않았습니다. 따라서, 과거의 프로그래밍 패러다임 중심은 컴퓨터였습니다. 컴퓨터가 사고하는 대로 프로그래밍하는 것입니다. 하지만 점점 소프트웨어의 발전 속도가 빨라지면서 코드도 복잡해지기 시작했습니다. 처음엔 C, 코볼, 포트란과 같은 절차적 언어들로 구현되었던 알고리즘이 점점 더 복잡해져 결국 사람이 읽으면서 로직을 이해할 수 없게 되는 스파게티 코드 문제점이 대두되었습니다. 이는 곧 유지보수에 있어 문제를 초래하게 되었습니다. 따라서 이 문제를 해결하기 위해 새로운 패러다임인 객체 지향 프로그래밍이 등장했습니다. 이는 인간 중심적 프로그래밍으로, 현실 세계를 프로그램으로 가져와 프로그래밍하는 새로운 방식이었습니다.
결국, 둘은 상이한 패러다임이었고, 기술의 발전을 거치면서 패러다임의 전환이 발생한 것입니다.
절차적 프로그래밍(Procedure Programming)
데이터에 대한 순서를 파악하고, 필요한 기능을 함수로 만들어 절차적으로 진행하는 방식입니다.
그렇다면 절차적 프로그래밍은 어떤 장점이 있을까요?
- 객체나 클래스의 생성 없이 바로 프로그램을 작성할 수 있습니다.
- 필요한 기능을 함수로 만들어 사용해 코드 재사용이 가능합니다.
- 프로그램의 흐름 이해가 쉬워집니다.
단점으로는,
- 코드들이 유기적으로 연결되어 새로운 기능이나 데이터의 추가와 디버깅이 어렵습니다.
객체 지향 프로그래밍(Object Oriented Programming)
특정한 개념의 함수와 데이터를 묶어 관리하기 위해 등장한 개념으로, 기능들을 묶어 하나의 객체로 생성합니다.
이때, 추상화라는 개념을 이용해 현실 세계의 사물들을 객체로 생성합니다. 그 객체로부터 개발하고자하는 애플리케이션에 필요한 특징들을 가져와 프로그래밍에 사용하는 방식입니다.
객체 지향 프로그래밍은 다음과 같은 특징이 있습니다. 그리고 이러한 특징을 갖추었다면, 객체 지향적 언어라고 할 수 있습니다.
추상화 : 필요로 하는 속성이나 행동을 추출하는 작업을 말합니다.
- 추상적인 개념에 의존해 설계해야 유연함을 갖출 수 있다.
- 세부적인 사물들의 공통적인 특징을 파악한 후 하나의 집합으로 만들어내는 것입니다.
- 예를 들어, K3, K5, 소나타, 그랜저의 특징을 뽑아내 각 객체로 구현할 수 있습니다.
캡슐화 : 낮은 결합도를 유지할 수 있도록 설계하는 것입니다.
- 객체의 내부 구현을 감춤으로써, 한 곳에서 변화가 일어나더라도 다른 곳에 미치는 영향을 최소화하는 것입니다.
상속 : 일반화 관계라고도 하며, 여러 객체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정입니다.
- 상속은 또 다른 캡슐화로, 자식 클래스를 외부로부터 은닉하는 캡슐화의 일종입니다.
- 상속 관계에서는 하나의 클래스 안에서 속성 및 연산들의 캡슐화에 한정되지 않아, 자식 클래스 자체를 캡슐화하여 외부에 은닉하는 것으로 확장되는 것이다.
- 하지만 상속을 재사용하는 경우, 다음과 같은 단점이 있습니다.
- 상위 클래스의 변경이 어려워집니다.
- 부모 클래스에 의존하는 자식 클래스가 많은 경우, 부모 클래스의 변경은 모든 자식 클래스의 변경을 요구합니다.
- 유사 기능 확장 시, 필요 이상의 불필요한 클래스를 만들어야 하는 상황이 발생할 수 있습니다.
- 그리고 이에 대한 해결책으로 객체 조립이 등장합니다.
- 필드에서 다른 객체를 참조하는 방식으로 구현됩니다. 말그대로 클래스의 필드에서 특정 객체의 구현체를 선언하는 방식입니다.
- 상속에 비해 비교적 런타임 구조가 복잡해지고, 구현이 어렵다는 단점이 있지만, 변경 시 유연함을 확보하는 장점이 매우 큽니다.
- 따라서, 같은 종류가 아닌 클래스를 상속하고 싶은 경우에는 객체 조립을 우선적으로 적용하는 것이 좋습니다.
- 상속은, 두 클래스간 IS-A 관계가 성립하거나, 기능의 확장 관점일 경우 사용하도록 합니다.
- 두 클래스가 IS-A 관계가 아니라면, 서로 다른 역할을 수행한다는 의미로 해석됩니다.
다형성 : 서로 다른 클래스의 객체가 같은 메세지를 받았을 때, 각자의 방식으로 동작하는 능력입니다.
- 이는 객체 지향의 꽃이라고 할 수 있는데, 부모 클래스의 메소드를 자식 클래스가 Overriding하여 자신의 역할에 맞게 활용하는 것입니다.
- 이는 상속과 함께 활용될 때 효율적인데, 코드를 간결하게 해주고, 유연함을 갖추게 해줍니다.
- 상속 관계에 있으면, 새로운 자식 클래스가 추가되어도 부모 클래스의 함수만을 참조하기에 다른 클래스의 영향을 받지 않습니다.
결합도란, 어떤 기능을 실행할 때 다른 클래스나 얼마나 의존적인가를 나타냅니다.
- 객체들 간 의존도가 높다면, 굳이 객체 지향으로 설계하는 의미가 없어집니다. 독립적으로 만들어진 객체들간의 의존도가 최대한 낮게 만들어야 합니다.
- 또한, 결합도와 함께 사용되는 개념으로 응집도라는 것이 있는데, 이는 객체 안의 모듈 간 요소가 얼마나 밀접한가를 나타냅니다.
- 따라서 좋은 설계방법은 응집도를 높이고 결합도를 줄이는 것입니다.
그렇다면 이러한 객체 지향 프로그래밍은 어떤 장점과 단점을 가지고 있을까요?
장점
- 모듈화, 캡슐화로 인해 디버깅과 유지보수에 용이합니다.
- 객체 자체가 하나의 프로그램이기에, 재사용성이 높습니다. 자주 사용되는 로직을 라이브러리로 만들어 재사용할 수 있습니다.
- 객체 지향적으로 현실 세계와의 유사성이 높아 코드 이해가 쉽습니다.
단점
- 대부분 많은 메모리를 사용하고, 속도가 느린 경향이 있습니다.
- 코드 이해도를 높이기 위해 설계에 많은 시간이 소모됩니다.
- 객체는 변수를 가질 수 있고, 이로 인해 상태를 가지게 됩니다. 이는 내부 버그의 요인이 될 수 있고, 이로 인해 함수형 패러다임이 등장하고 있습니다.
객체 지향 설계의 원칙
객체 지향 설계는 다음과 같은 원칙을 지켜야 합니다. 이를 지킴으로써, 유지보수와 변경의 용이를 가질 수 있도록 합니다. 이 5개의 원칙은 앞글자만 따서 SOLID 원칙이라고도 불립니다.
SPR(Single Responsibility Principle) : 단일 책임 원칙
한 클래스는 단 하나의 책임을 가져야 하며, 클래스를 변경하는 이유는 단 하나의 이유여야 합니다.
OCP(Open-Closed Principle) : 개방-폐쇄 원칙
확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 합니다.
- 기능을 변경하거나 확장할 수 있으면서, 그 기능을 사용하는 코드는 수정되지 않아야 합니다.
- 그리고 이를 위해서는 추상화에 의존해야 합니다.
LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 합니다.
- 상속 관계가 아닌 클래스들을 상속 관계로 설정하면, 이 원칙이 위배됩니다.
- 상속 관계가 아니기에, 객체 치환이 불가능해집니다.
ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 합니다. 이를 통해 클라이언트에게는 목적과 용도에 부합한 인터페이스만을 제공할 수 있도록 합니다.
DIP(Dependency Inversion Principle) : 의존 역전 원칙
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안됩니다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 하고, 저수준 모듈이 변경되어도 고수준 모듈은 변경할 필요가 없어야 합니다.
- 여기서 고수준 모듈은 변경이 없는 추상화된 클래스나 인터페이스를 의미하고,
- 저수준 모듈은 변하기 쉬운 구체 클래스를 의미합니다.
- 그리고 이 원칙은 곧 추상화에 의존하며 구체화에는 의존하지 않는 설계 원칙을 의미합니다.
이렇게 절차적 프로그래밍과 객체 지향 프로그래밍에 대해 알아보았습니다.
물론 부족한 점이 넘치고, 이해가 안되는 부분이 있을 수 있지만, 읽어주셔서 감사합니다.
잘못된 점이나 질문있으시면 편하게 댓글 남겨주시면 감사하겠습니다 🙂
참고
https://st-lab.tistory.com/151
https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense
https://mangkyu.tistory.com/194
'Java지식' 카테고리의 다른 글
Java의 effectively final이란? (3) | 2023.06.10 |
---|---|
기본 자료형과 참조 자료형 (7) | 2022.11.30 |
Overloading과 Overriding (1) | 2022.09.19 |