'''이론 컴퓨터 과학 {{{#!wiki style="display: inline-block; font-family:Times New Roman, serif;font-style:italic"''' | |||||
{{{#!wiki style="margin: 0 -10px -5px; min-height: calc(1.5em + 5px)" {{{#!folding [ 펼치기 · 접기 ] {{{#!wiki style="margin: -5px -1px -11px" | <colbgcolor=#a36> 이론 | ||||
기본 대상 | 수학기초론{수리논리학(논리 연산) · 계산 가능성 이론 · 범주론 · 집합론} · 이산수학(그래프 이론) · 수치해석학 · 확률론 및 통계학 · 선형대수학 | ||||
다루는 대상과 주요 토픽 | |||||
계산 가능성 이론 | 재귀함수 · 튜링 머신 · 람다대수 · 처치-튜링 명제 · 바쁜 비버 | ||||
오토마타 이론 | FSM · 푸시다운 · 튜링 머신(폰노이만 구조) · 정규 표현식 · 콘웨이의 생명 게임 · 형식언어 | ||||
계산 복잡도 이론 | 점근 표기법 · 튜링 기계^고전, 양자, 비결정론적, 병렬 임의접근 기계^ · 알고리즘 · 자료구조 · 알고리즘 패러다임(그리디 알고리즘, 동적 계획법) | ||||
정보이론 | 데이터 압축(무손실 압축 포맷 · 손실 압축 포맷) · 채널 코딩(채널 용량) · 알고리즘 정보 이론(AIT) · 양자정보과학 | ||||
프로그래밍 언어이론 | 프로그래밍 언어(함수형 언어 · 객체 지향 프로그래밍 · 증명보조기) · 메타 프로그래밍 · 유형 이론 · 프로그래밍 언어 의미론 · 파싱 · 컴파일러 이론 | ||||
주요 알고리즘 및 자료구조 | |||||
기초 | 정렬 알고리즘 · 순서도 · 탐색 알고리즘 | ||||
추상적 자료형 및 구현 | 배열^벡터^ · 리스트^연결 리스트^ · 셋(set)^레드-블랙 트리, B-트리^ · 우선순위 큐^힙, 피보나치 힙^ | ||||
수학적 최적화 | 조합 최적화 | 외판원 순회 문제 · 담금질 기법 · 유전 알고리즘 · 기계학습 | |||
볼록 최적화 | 내부점 방법 · 경사하강법 | ||||
선형계획법 | 심플렉스법 | ||||
계산 수론 및 암호학 | 밀러-라빈 소수판별법 · Pollard-rho 알고리즘 · 쇼어 알고리즘 · LLL 알고리즘 · 해시(MD5 · 암호화폐 · 사전 공격(레인보우 테이블) · SHA) · 양자 암호 | ||||
대칭키 암호화 방식 | 블록 암호 알고리즘(AES · ARIA · LEA · Camellia) · 스트림 암호 알고리즘(RC4) | ||||
공개키 암호화 방식 | 공개키 암호 알고리즘(타원 곡선 암호 · RSA) · 신원 기반 암호 알고리즘(SM9) | ||||
계산기하학 | 볼록 껍질 · 들로네 삼각분할 및 보로노이 도형^Fortune의 line-sweeping 알고리즘^ · 범위 탐색^vp-tree, R-tree^ · k-NN | ||||
그래프 이론 | 탐색^BFS, DFS, 다익스트라 알고리즘, A* 알고리즘^ · 에드몬드-카프 · 크루스칼 알고리즘 · 위상 정렬 · 네트워크 이론 | ||||
정리 | |||||
정지 문제대각선 논법 · 암달의 법칙 · P-NP 문제미해결 · 콜라츠 추측미해결 | |||||
틀:이산수학 · 틀:수학기초론 · 틀:컴퓨터공학 | }}}}}}}}} |
[[컴퓨터공학|컴퓨터 과학 & 공학
Computer Science & Engineering
]]- [ 펼치기 · 접기 ]
- ||<tablebgcolor=#fff,#1c1d1f><tablecolor=#373a3c,#ddd><colbgcolor=#0066DC><colcolor=white> 기반 학문 ||수학(해석학 · 이산수학 · 수리논리학 · 선형대수학 · 미적분학 · 미분방정식 · 대수학(환론 · 범주론) · 정수론) · 이론 컴퓨터 과학 · 암호학 · 전자공학 · 언어학(형태론 · 통사론 · 의미론 · 화용론 · 음운론) · 인지과학 ||
하드웨어 구성 SoC · CPU · GPU(그래픽 카드 · GPGPU) · ROM · RAM · SSD · HDD · 참조: 틀:컴퓨터 부품 기술 기계어 · 어셈블리어 · C/C++ · C# · Java · Python · BIOS · 절차적 프로그래밍 · 객체 지향 프로그래밍 · 해킹 · ROT13 · 일회용 비밀번호 · 사물인터넷 · 와이파이 · GPS · 임베디드 · 인공신경망 · OpenGL · EXIF · 마이크로아키텍처 · ACPI · UEFI · NERF · gRPC · 리버스 엔지니어링 · HCI · UI · UX · 대역폭 · DBMS · NoSQL · 해시(SHA · 브루트 포스 · 레인보우 테이블 · salt · 암호화폐) · RSA 암호화 · 하드웨어 가속 연구
및
기타논리 회로(보수기 · 가산기 · 논리 연산 · 불 대수 · 플립플롭) · 정보이론 · 임베디드 시스템 · 운영 체제 · 데이터베이스 · 프로그래밍 언어{컴파일러(어셈블러 · JIT) · 인터프리터 · 유형 이론 · 파싱 · 링커 · 난해한 프로그래밍 언어} · 메타데이터 · 기계학습 · 빅데이터 · 폰노이만 구조 · 양자컴퓨터 · 행위자 모델 · 인코딩(유니코드 · MBCS) · 네트워크 · 컴퓨터 보안 · OCR · 슈퍼컴퓨터 · 튜링 머신 · FPGA · 딥러닝 · 컴퓨터 구조론 · 컴퓨터 비전 · 컴퓨터 그래픽스 · 인공지능 · 시간 복잡도(최적화) · 소프트웨어 개발 방법론 · 디자인 패턴 · 정보처리이론 · 재귀 이론 · 자연어 처리(기계 번역 · 음성인식) · 버전 (버전 관리 시스템 · Git · GitHub)
1. 개요
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 프로그램 설계 방법론의 일종으로 명령형 프로그래밍에 속한다.프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체(object)'라는 기본 단위로 나누고 이들의 상호 작용으로 서술하는 방식이다.
객체란 '메소드, 변수'를 가지며 특정 역할을 수행하도록 인간이 정의한 추상적인 개념이다.
서술의 편의상 객체 지향을 Java 위주로 소개하고 있고 class나 public 같은 용어를 사용했다. 이 경우만 객체 지향에 해당하는 것으로 오해하지 않게 주의해야 한다. 모든 언어가 class나 접근 제한자(public이나 private)를 사용하지는 않는다. 대표적인 예로 JavaScript는 프로토타입 객체 지향을 사용하고 있고 Python에는 접근 제한자가 없다. 객체 지향은 특정 언어가 아니라 개념이다. "클래스는 객체이며 구조체는 객체가 아닌 데이터의 집합"이라는 설명 역시 틀렸고, 특정 언어가 객체 지향 언어라는 말도 완전히 틀린 표현이다.
2. 역사
2.1. 시작과 발전
초기 프로그래밍 방식은 절차적 프로그래밍 방식이었다. 학교대사전의 고등학생 알고리즘처럼 입력을 받아 명시된 순서대로 처리한 다음, 그 결과를 내는 것뿐이라는 생각이 지배적이었다. 프로그램을 명령어의 모음으로 인식한 것이다. 또한 프로그래밍이란 어떻게 어떤 논리를 어떤 순서대로 써나가는 것인가로 간주되었다. 즉, 프로그램 자체가 가지는 기능에 대해서만 신경을 썼지, 이 프로그램이 대체 어떤 데이터를 취급하는 것인가에는 그다지 관심이 없었던 것이다.그러나, 이 방식은 간단한 알고리즘이면 모를까 조금만 복잡해지면 순서도로 나타내는 것이 불가능할 정도로 꼬인 "스파게티 코드"를 만들게 된다. 이렇게 꼬여버린 코드는 다른 사람이 보고 이해하는 것이 거의 불가능할뿐더러 심지어는 작성한 본인조차도 유지 보수에 어려움을 겪게 된다. 명령어의 양이 많아지는 것은 기본이고, 특정 코드 부분은 어디에 사용되는 코드고 해당 코드 부분은 어디까지 이어지는지의 흐름을 파악하기도 힘들어지며, 중복 코드 대처도 매우 골치 아프다. OOP를 사용하면 코드의 중복을 어느 정도 줄일 수 있고 입력 코드, 계산 코드와 결과 출력 코드 등 코드의 역할 분담을 좀 더 확실하게 할 수 있어서 가독성이 높아질 수 있다.
이 문제를 해결하기 위해 에츠허르 다익스트라가 1968년 GOTO문의 해로움이라는 논문에서 프로그램을 프로시저(procedure) 단위로 나누고 프로시저끼리 호출을 하는 구조적 프로그래밍 방식을 제안하면서 이러한 위기를 벗어나게 된다.[1] 프로그램이라는 큰 문제를 해결하기 위해 그것을 몇개의 작은 문제들로 나누어 해결하기 때문에 하향식(Top-down) 방식이라고도 한다.
하지만 함수는 데이터의 처리 방법을 구조화했을 뿐, 데이터 자체는 구조화하지 못했다. 이는 전역 네임스페이스 포화 문제(변수 이름을 다 써서 이름 짓기도 힘든 상황(...))를 낳게 되었다.[2] 게다가 실행 콘텍스트를 저장할 마땅한 방법이 없어지는 문제가 생겼다. 실행 콘텍스트는 특히 GUI에서 중요해지는데 어떤 창의 현재 상태에 따라 함수가 실행해야 하는 동작 방식이 달라지기 때문이다. 또한 엉뚱한 데이터가 엉뚱한 함수에 전달돼서 데이터를 오염시키는 문제가 발생하고 그런 가능성 때문에 프로그래머가 한 함수의 작동에 영향을 받는 변수를 조사해야 할 때 모든 변수를 다 조사해야 하는 어려움에 봉착했다. 변수의 개수가 수백 개 이하인 코드에서야 이게 사람의 힘으로 가능했지 코드의 덩치가 커지면서 함수가 접근할 수 있는 데이터의 범위에 명시적인 제한을 걸어야 하는 상황이 도래했다. 이를 지역 변수나 구조체(struct) 등으로 어찌 제어하고 있기는 했지만 더 근본적인 해결책이 필요했다.
이를 극복하기 위한 대안으로 등장한 것이 바로 객체 지향 프로그래밍이다. 큰 문제를 작게 쪼개는 것이 아니라, 먼저 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 상향식(Bottom-up) 해결법을 도입한 것이다. 이 객체란 것을 일단 한번 독립성/신뢰성이 높게 만들어 놓기만 하면 그 이후엔 그 객체를 수정 없이 재사용할 수 있으므로 개발 기간과 비용이 대폭 줄어들게 된다.
객체 지향 프로그래밍은 등장 당시에는 기존의 절차적 프로그래밍과 비교해 매우 이질적이고, 당시 컴퓨터의 처리 능력이 별로 좋지 않아서 별 주목을 받지 못하였다. 그러다가 GUI가 등장하면서 객체 지향 프로그래밍이 급부상하게 된다. 화면에 떠 있는 여러 개의 창은 각자의 실행 콘텍스트를 가지는데 콘텍스트의 현재 상태(활성화, 비활성화, 최소화 등)에 따라 같은 명령에도 다른 결과를 내보내야 했으며 사용자 상호 작용을 위해 이벤트 처리도 수행해야 했다. 특히 이벤트 처리는 비동기적인 속성 때문에 기존 절차적 프로그래밍에서는 일종의 횡단 관심사가 되어 버려 코드 전체에 이벤트 처리 코드가 흩어져 있게 되는 문제가 있었다. 그래서 OOP를 도입하여 이벤트를 받았을 때 수행되는 기능(Event Handler, Callback)을 구현할 수 있는 단일 인터페이스를 정의하고, 프로그래머들은 이를 필요한 형태로 알아서 구현하며, 특정 이벤트가 일어났을 때 실행되어야 하는 기능들을 등록한 다음, 운영 체제나 응용 프로그램이 실제로 해당 이벤트가 발생했을 때 해당 이벤트에 등록된 이벤트 핸들러/콜백을 주욱 실행하기만 하면 되는 구조가 본격적으로 확산되면서 OOP 또한 빠르게 확산되었다.
단, 이벤트 드리븐 방식은 객체 지향 프로그래밍과 별개의 개념이다. 사실 이벤트 드리븐 방식은 객체 지향보다 함수형 프로그래밍 언어에서 훨씬 더 잘 지원한다. 그 예시로, 같은 일을 하는 Java와 Scala 코드의 GUI 로직을 보면 Scala 쪽이 압도적으로 단순하다는 것을 알 수 있다. 다만 기존 절차형 프로그래밍 언어가 함수를 일급 객체로 지원하지 않아 콜백 구현이 어려운 문제가 있었고(절차형인 C언어에서는 함수 포인터를 사용한다.) 마침 등장한 객체 지향 언어가 인터페이스라는 개념을 제공했기에 이벤트 드리븐 방식에 좀 더 어울렸을 뿐이다. 객체 지향 프로그래밍 개념이 나올 당시에도 LISP 등 함수형 언어가 있긴 했으나 기존 프로그래머가 사용하기에는 너무 고수준의 개념을 다루고 있었고 성능도 너무 낮았기 때문에 당시에는 주목받지 못했다. 당장 for 루프로 리스트 처리가 가능한데 리스트 해석이니 map/reduce니 떠들어봤자 현업 프로그래머의 귀에 들어갈 리가 없었다. 물론 지금은 고차 함수나 클로저 같은 개념까지 도입해야 할 정도로 프로그램의 복잡도가 증가했기 때문에 관심을 보이고 있다. 현대에 만들어지는 프로그램은 그 정도로 복잡해졌다.
객체 지향 프로그램이 복잡해지면서 이를 간결하게 정리할 필요성이 생긴 관계로 '디자인 패턴'이라는 것이 생겼다. 프로그래밍 형식을 정하는 일종의 약속으로, 이는 협업을 전제로 한 환경에서 특히 강조되고 있다.
2.2. 지원하는 언어
- Smalltalk
앨런 케이가 1972년 팔로 알토 리서치 센터(PARC)에서 만든 Smalltalk가 최초로 OOP를 지원한 프로그램이다. 시뮬레이션 프로그래밍 언어인 시뮬라-67에서 영향을 받았다.
스몰토크는 앨런 케이가 "누구나 쉽게 사용할 수 있는 컴퓨터"를 만들려고 했던 목적에 따라 만들어졌다. 문제는 앨런 케이가 글을 읽고 쓸 수만 있으면 4~5세의 아이들도 프로그래밍할 수 있는 것을 이상적인 목표로 했기 때문에 프로그래밍을 하고자 하는 목표를 수학적 논리 구조(알고리즘)로 개념화한 뒤에 그에 따라 프로그래밍하는 게 아니고[3] 비수학적인 사고로 문제를 해결하도록 언어가 설계되어 있었고, 이 때문에 모든 것을 객체 단위로 분해하고 그 객체들이 메시지를 전달하여 문제를 해결하도록 프로그래밍을 해야만 한다. - Ruby와 Python
- Ruby: Smalltalk의 계보를 잇는 순수 객체 지향 언어. 기존의 C++나 Java 등에 비해서 난이도가 낮다.
- Python: 역시 순수 객체 지향을 지원하고 있다. Ruby와 비슷한 구조를 가지고 있으며, 미세한 명령어나 기법 차이 등이 있을 뿐, 거의 형제처럼 가까운 언어들이다.
- C언어에 객체 처리 기능 추가
브레스 콕스와 톰 러브는 스몰토크를 보고 새로운 시각으로 객체 지향을 바라보았는데 그것은 소스 코드의 수정 없는 재활용이었다. 그들은 이 개념을 실제 언어에 적용하여 1983년도에 스몰토크의 객체 처리 방식을 C언어에 추가했다. C언어의 표준을 지키면서 스몰토크 방식의 객체 처리 기능을 추가한 것이다. 이렇게 표준 언어에 기능을 추가하는 것을 슈퍼셋(Superset)이리고 한다. 반대로 표준언어의 기능을 축소한 것을 서브셋(Subset)이라고 한다. 컴파일러 개발 방법을 교육할 목적으로 만든 Small-C가 대표적인 서브셋이다. - C++
1983년에 비아르네 스트로우스트루프가 C언어를 확장시킨 C++를 발표했다. - Objective-C
1983년에 브래드 콕스와 톰 러브가 C언어에서 파생된 Objective-C를 만들어 발표했고 실제 유용하다는 것을 실증하였다. 그리고, Objective-C는 1989년 당시 가장 혁신적인 운영 체제였던 NeXTSTEP을 개발할 때 사용되었다. NeXTSTEP은 1996년도에 Apple에 인수되어 2001년도에 출시된 Mac OS X의 기반이 되었다. 이후에는 Swift가 Objective-C를 계승한 상태다. - 기타 여러 언어들
이 두 언어의 성공으로 이후 Java, C#, Objective-Pascal 등 많은 객체 지향 언어들이 순수한 객체 지향보다는 기존의 프로그래밍 언어에 객체 지향 요소를 확장하거나 추가한 형태로 만들어지게 되었다.
3. 요소
3.1. 캡슐화(encapsulation)
변수와 함수를 하나의 단위로 묶는 것을 의미한다. 즉, 데이터의 번들링(bundling)이다. 대개 프로그래밍 언어에서 이 번들링은 클래스를 통해 구현되고, 해당 클래스의 인스턴스 생성을 통해 클래스 안에 포함된 멤버 변수와 메소드에 쉽게 접근할 수 있다. 클래스는 객체 지향 프로그래밍을 지원하는 거의 대부분의 언어가 제공하는 제1요소이다.3.1.1. 정보 은닉(information hiding)
프로그램의 세부 구현을 외부로 드러나지 않도록 특정 모듈 내부로 감추는 것이다. 내부의 구현은 감추고 모듈 내에서의 응집도를 높이며, 외부로의 노출을 최소화하여 모듈 간의 결합도를 떨어뜨려 유연함과 유지 보수성을 높이는 개념은 거의 모든 현대 프로그래밍 언어에 녹아 있다. 많은 객체 지향 언어에서 사용되는 클래스를 기준으로 보면, 클래스 외부에서는 바깥으로 노출된 특정 메소드에만 접근이 가능하며 클래스 내부에서 어떤 식으로 처리가 이루어지는지는 알지 못하도록 설계된다.일반적으로 세 종류의 접근 제한이 사용된다.
- public: 클래스의 외부에서 사용 가능하도록 노출시키는 것이다.
- protected: 다른 클래스에게는 노출되지 않지만, 상속받은 자식 클래스에게는 노출되는 것이다.
- private: 클래스의 내부에서만 사용되며 외부로 노출되지 않는다.
보통 캡슐화와 정보 은닉을 묶어서 생각하는 경우가 많은데, 정보 은닉은 캡슐화로부터 파생된 보조 개념이지 '캡슐화 = 정보 은닉'은 아니다.
파이썬의 경우 캡슐화가 특이한데, 접근 한정자를 프로퍼티의 접두사 언더바로 사용한다. 게터 세터의 경우 데코레이터를 이용한다. 다만 파이썬 프로그래밍에서 대다수는 객체 지향을 늦게 배우는 경향이 있기에 캡슐화가 있는지 모르는 사람도 상당히 많다.
3.2. 상속(inheritance)
상속은 자식 클래스가 부모 클래스의 특성과 기능을 그대로 물려받는 것을 말한다. 기능의 일부분을 변경해야 할 경우 자식 클래스에서 상속받은 그 기능만을 수정해서 다시 정의하게 되는데, 이러한 작업을 '오버라이딩(overriding)'이라고 한다. 상속은 캡슐화를 유지하면서도 클래스의 재사용이 용이하도록 해 준다.자세한 내용은 상속(프로그래밍) 문서 참고하십시오.
3.3. 다형성(polymorphism)
하나의 변수, 또는 함수가 상황에 따라 다른 의미로 해석될 수 있는 것을 말한다.- 서브타입 다형성(subtype polymorphism / inclusion polymorphism / subtyping)
우리가 일반적으로 접하는 OOP의 그것. 기초 클래스 또는 어떠한 인터페이스를 구현하는 상위 클래스를 생성하고, 해당 클래스를 상속받는 다수의 하위 클래스들을 만들어 상위 클래스의 포인터나 참조변수 등이 하위 클래스의 객체를 참조하게 하는 것이다. 이 때 각각의 하위 클래스는 상위 클래스의 메소드 위에 자신의 메소드를 덮어 쓰는 메소드 오버라이딩(method overriding)을 수행하며, 상위 클래스의 참조 변수가 어떤 하위 클래스의 객체를 참조하느냐에 따라 호출되는 메소드가 달라진다.[4] Java, C++, C#, Python, Ruby 등의 객체 지향 언어들은 기본적으로 지원하는 개념. - 매개 변수 다형성(parametric polymorphism)
타입을 매개 변수로 받아 새로운 타입을 되돌려주는 기능이다. 타입 매개 변수를 정의한 클래스 혹은 메소드는 사용할 때 매개 변수에 타입을 지정하게 되며, 컴파일 시 지정한 타입에 따라 해석된다. - 템플릿(template)
C++에서 사용하는 개념으로, 타입 매개 변수를 입력한 타입으로 치환한 코드를 생성하는 방식이다. 타입뿐 아니라 변수도 입력할 수 있으며, 객체 내부에서 연산이나 함수 호출을 할 수 있지만, 해당 연산이나 함수가 정의되지 않은 타입을 매개 변수로 넣으면 컴파일 에러가 발생하며 컴파일이 느려지고 파일이 커진다.
C++20에서 추가된 Concept 문법으로 타입 제약을 걸 수 있도록 보완되었다. - 제네릭(generic)
Java와 C# 등에 도입된 개념으로, 지정한 타입 매개 변수에 해당하는 타입만을 사용하겠다고 약속하는 방식이다. 타입 매개 변수가 특정 객체를 상속할 경우 상속하는 객체의 함수는 호출할 수 있지만 그렇지 않을 경우 타입 매개 변수로 지정된 객체의 멤버에는 접근할 수 없다. - 타입 클래스(type class)
하스켈에 도입된 개념이다. - 임시 다형성(ad hoc polymorphism)
- 함수 오버로딩(function overloading)
C++과 C#, Java에서는 함수 오버로딩을 통해 동일한 이름의 함수를 매개 변수에 따라 다른 기능으로 동작하도록 할 수 있다. 함수 오버로딩을 너무 많이 사용하면 전체적인 코드의 유지 보수가 어려워지므로, 템플릿 또는 제네릭으로 대체하는 것이 일반적이다. - 연산자 오버로딩(operator overloading)
C++, C# 등에서는 연산자를 오버로딩해서 기본 연산자가 해당 클래스에 맞는 역할을 수행하게 하는 것이 가능하다. Java에서는 연산자의 오버로딩이 불가능하다. Perl 6나 Smalltalk, F#, Kotlin 등 연산자의 신규 정의가 가능한 언어도 있다. 한글 위키백과의 연산자 오버로딩에 대한 프로그래밍 언어 분류 - 강제 다형성(coercion polymorphism)
- 묵시적 형 변환(implicit type coercion)
'double a = 30;'이라는 식이 실행되면 int형 값 30은 double로 묵시적 형 변환이 이루어진다. double은 int보다 크기가 큰 자료형이므로, 이러한 형 변환을 자료형 승급(type promotion)이라고 한다. C++의 변환 생성자에 의한 형 변환도 묵시적 변환에 속하며, 이를 막으려면 생성자 앞에 explicit 키워드를 추가해야 한다. - 명시적 형 변환(explicit type coercion)
'double a = (double)30;'이라는 식은 위와 동일한 결과를 내지만, (double)을 통해 int형 값 30이 double형으로 변환됨을 명시적으로 표현하였다.
4. 원칙
자세한 내용은 객체 지향 프로그래밍/원칙 문서 참고하십시오.5. 장단점
- 데이터 클래스의 상속이라는 개념은 굉장히 뛰어나지만 객체를 설계할 때부터 추상화를 해야 하기 때문에 난이도가 어려워진다. 상속의 쉬운 예제는 집합론이나 생물학에서 종을 분류하는 것을 들 수 있고, 상속 덕분에 집합의 부분 집합 관계나 생물의 분류 같은 것을 그대로 코드로 구현할 수 있다. 하지만 객체 사이의 상속 구조를 잘 추상화하기 위해서는 집합론자나 생물학자 수준으로 논리에 강하고 지식이 많아야 하고, 또 다른 개발자들이 엉뚱하게 활용하지 않도록 디자인 패턴까지 정의해야 하는 등 어려워진다. 특히, 다중 상속이 되면 엄청 복잡해지기 때문에 여러 개발자들이 협업할 때는 다중 상속은 아예 금지하는 경우가 많다. 상속이 복잡하게 얽혀 소스 분석이 어려워진 상태는 라자냐 코드라고 불리고 있다.
- public, private 접근자 때문에 보안성이 증가하고 개발자들이 내부 구현과 외부 비즈니스 로직을 분리하는 데 도움을 준다. 그러나 여기에서 말하는 보안성은 파일 변조, 메모리 변조 같은 것을 막아주는 것이 아니기 때문에 private 변수만 쓴다고 해커들에게서 보호해 줄 수 있는 것이 아니다. 그리고 private 접근자의 불투명성은 디버깅용 코드에도 예외가 아니기 때문에 디버깅이나 테스트를 어렵게 하는 원인이 되기도 한다. 그리고 private 설정 때문에 일일이 getter, setter 정의를 해 주어야 하는 것이 귀찮기도 하고, 사소하다고 느끼는 개발자들이 많다. [5]
- 클래스의 정의는 최초로 생성한 프로그램뿐 아니라 다른 OOP에서도 똑같이 사용될 수 있다. 그리고, 이런 이유로 네트워크에 쉽게 분산 사용이 가능하지만 프로그래머에게는 아주 힘든 노력을 강요한다. 네트워크 통신이라는 것은 호환성을 위해 7bit ASCII 코드로 전송한다. 1960년대 만들어진 통신 기기를 사용하는 곳도 아직 있기 때문이다. 이 때문에 Quarter-Print, Base64나 UTF-8 같은 것이 만들어진 것이다. 결국 객체도 7bit ASCII 코드로 전송을 해야 한다. 메모리상의 객체 정보를 ASCII 코드화하는 것을 직렬화(Serialization)라고 하고 ASCII 코드를 다시 객체화하는 것을 역직렬화(Deserialization)라고 한다. 하지만 마법처럼 그냥 되는 게 아니고 "직렬화 인터페이스"를 프로그래머가 직접 구현했을 때만 가능하다. Java를 비롯한 대다수의 객체 지향 언어들은 직렬화-역직렬화 인터페이스를 기본적으로 제공하고 있다. 이러한 아이디어를 발전시킨 것이 CORBA와 MS의 COM/DCOM/COM+이다. 최근에는 SOAP나 JSON, XML-RPC 등 텍스트 기반의 직렬화 기술도 많이 사용된다. Java에는 Java Runtime끼리 통신하기 위한 RMI(Remote Method Invocation)도 지원한다.
- 데이터 클래스 개념은 언어에 정의되지 않은 새로운 데이터 형식을 프로그래머가 임의로 정할 수 있도록 해준다.
- 캡슐화와 격리 구조 설계로 인한 성능 하락이 있다. 거의 대부분의 객체 지향 언어에서 기능을 묶으면 결국 함수 호출이 추가로 들어가거나 계산식 중간에 포인터 연산 등이 필요해지며, 멤버 함수 같은 경우 어느 객체의 함수인지 지정해야 하기 때문에 추가 포인터 크기와 연산 비용이 들어간다. 인라인 함수와 컴파일러 최적화(특히 RVO(Return Value Optimization)) 등의 방법으로 어느 정도는 격차를 줄여주나 역시 그냥 절차적 프로그래밍보다는 무거워진다.
- 개념을 기준으로 나누다 보니 반복 연산이 컴퓨터 친화적이지 않고, 특히 배열 자료 구조를 적용하기 힘들어진다. 객체 하나하나를 따로 캡슐화시키고 상속 시 부모만 같으면 자식의 종류를 신경 쓰지 않다 보니 각자의 메모리 크기가 달라지며, 결국 고정된 연속 메모리에 담을 수가 없게 된다. 메모리 할당을 배열로 하지 못하게 되니 따로따로 생성하게 되는데 이렇게 각각의 객체의 생성과 파괴가 반복되면 메모리 단편화라는 문제가 생기게 된다. 가비지 컬렉션 기능이 만들어진 이유 중 하나. 또 연속 메모리를 쓰기 힘들어진다는 건 캐시의 효율적 사용에 큰 문제가 생긴다는 뜻이기도 해서 성능 격차는 더 벌어지게 된다. 이를 해결하는 코딩 패러다임으로 DOP(Data Oriented Programming)가 있고 구현 방법으로 메모리 풀이 있다.
- 객체 하나하나를 따로 나누는 데 주력하다 보니 서로 비슷한 처리를 하는 코드가 서로를 건드릴 수 없게 되었고 이를 해결하기 위해 getter(접근자: 값을 반환하는 메소드), setter(설정자: 필드에 값을 저장하는 메소드) 사용이 너무 많아졌다. 이 과정에서 캡슐화가 깨지고 그냥 public으로 공개한 경우나 마찬가지인 상태가 되어 의미가 퇴색되었고, 다른 프로그래밍 패러다임이 필요해졌다. AOP(Aspect Oriented Programming)는 모든 코드 하나하나를 별개의 객체로 분리하기보다 '어떤 일을 어디서 처리하는가'에 더 중점을 두어 큰 범위로 묶어주어 모듈화 효율을 개선시켰고, 현대 프로그래밍 언어와 코드들은 원본 객체 지향 방식을 그냥 그대로 적용하는 경우가 드물다.
6. 기타
최근 주목을 받고있는 함수형 패러다임과는 다소 상반된 위치에 있다. OOP의 경우 프로그램 유지 보수 시 데이터 추가는 새로운 클래스를 더하는 것으로 비교적 간단하게 가능하지만 operation set을 변경할 때는 관련된 다수 클래스를 수정해야 하므로 난잡해지는 경향이 있다. 반대로 함수형 패러다임에서는 operation set의 추가는 간단하지만 데이터 추가는 관련된 다수의 함수를 바꿔야 하므로 난해한 점이 있다.주의할 점은 OOP와 함수형 패러다임이 상반된 위치에 있긴 하지만 대비되는 개념은 아니며, 요즘에는 함수형 언어에도 OOP 개념을 추가한다든가(F#), 반대로 객체 지향 언어에 함수형 패러다임을 추가하는(C#, C++, Python, Java[6]...) 등 멀티패러다임 추세로 가고 있다.
클래스가 있어야만 객체 지향 프로그래밍 언어라고 생각할 수도 있지만, 사실 클래스 없는 OOP 언어도 꽤 있다. 프로토타입을 사용하는 JavaScript[7], 액션스크립트 2.0[8], 메타테이블을 활용하는 Lua, 트레이트(Trait)를 사용하는 Rust 등이 그 예시이다.
6.1. 교육의 어려움
프로그래밍을 배울 때 만나게 되는 난관이기도 하다. C를 배운 뒤 C++을 배우는 상황에서 특히 심하긴 한데, 곧바로 Java나 Python으로 배우기 시작하는 경우에도 마찬가지다.[9] 왜냐하면 이전까지 배웠던 것은 프로그래밍 언어의 문법이었다면(절차적 프로그래밍으로서의 문법), OOP는 가장 문제가 덜 생기는 방향으로 코딩하게끔 하는 가이드라인이기 때문이다(권장 사항). 이제 막 알파벳과 기초 영문법을 뗀 학생에게 수사법 내지는 논리적 작문을 가르치는 것과 동일한 것이다.이 문제라는 것도 수천, 수만 줄짜리 코드를 수년간 유지 보수 할 때나 몸에 와닿을 텐데, 기껏해야 과제 제출하고 다시 손대지 않을 길어야 수백 줄짜리 코드나 짜봤을 학생들에게는 전혀 체감할 수 없다. 게다가 대부분의 언어들은 기존의 언어에 객체 지향을 얹어놓은 형태기 때문에 굳이 OOP에 맞춰서 짜지 않아도 원하는 결과는 일단 나온다. 이러한 이유들로 인해 학생들에게 C++ 과제를 내주면 C++ 문법을 사용한 C 프로그램인 경우가 태반이다. 이는 Java 같은 언어에서 마찬가지로 나타나는 현상이다. Java 문법이지만 C 프로그램처럼 짜는 것을 '씨자바'라고 한다. 각 프레임들의 입출력 인터페이스를 설계하고 이벤트 드리븐 방식을 정의한 다음 학생들에게 그에 맞춰 오브젝트를 가공하라고 과제 내주는 게 OOP 과제인데, 현실은 덜렁 몇 줄의 요구사항으로 전체 프로그램 짜 와라가 전부인 게 보통이다. 그러니 학생들이 기껏 몇백 줄 짜리 코드 짜는 데 procedural 대신 OOP를 일반적으로 사용하지 않게 되는 것이다. 물론 학교 수준이나 교수님 교육 방식마다 다르며 무조건 객체 지향으로 구현해 과제를 제출하라고 하거나 시험 방식에 객체 지향적인 코딩을 하는 방식으로 시험을 내기도 한다. 여러 명에서 대규모 프로젝트로 만든 결과물도 아니고 1~2년 동안 일주일에 3~4시간 교육받은 학생에에게 이런 방식으로 시키는 게 맞나 생각할 수도 있지만 실무가 아니라 교육이기 때문에 어쩔 수 없는 부분이 있다.
의외로 클래스와 객체를 헷갈리는 것도 OOP의 개념을 어렵게 하는 요인. 대개 붕어빵 틀(클래스)과 붕어빵(객체)의 관계로 가르치는 경우가 많다. 하지만 사람 - 김연아, 펭귄 - 뽀로로 이런 식으로 property의 유무로 구분하라는 서적도 존재한다.[10] 또한 인스턴스라는 용어와도 헷갈려 하는 경우가 많은데, 인스턴스는 객체를 실체화한 것이라고 보면 된다. 즉 프로그램 코드상에서 자료형이 임의의 클래스로 선언된 식별자는 '객체'라 하고, 코드 컴파일 후 프로그램이 실행될 때 해당 객체가 메모리에 적재되면 '인스턴스'라고 불린다.[11] 어느 단계를 기준으로 하느냐의 차이일 뿐 객체와 인스턴스는 같은 것이라고 할 수 있다.
전문가가 아닌 일반인들에게 이 객체 지향에 대한 설명은 더더욱 어렵다. 단순히 어떤 것이 객체 지향이고, 어떤 것이 객체 지향이 아닌지에 대한 구분법을 설명하면 차라리 그나마 이해가 갈 텐데, 전문가들은 "모든 것은 객체다!"라는 선문답 스타일의 명제만 던지고 알아서 이해하라는 식으로 대처하는 경우가 많아서 일반인들은 대부분 들어는 봤지만 정작 그게 뭔지 모르는 대표적인 개념 중 하나가 되어버렸다.[12]
6.2. C언어와 객체 지향
you can write object-oriented code (useful for filesystems etc) in C, _without_ the crap that is C++.
리눅스 커널을 만든 리누스 토르발스의 말.[13]C++가 구리다는 말은 넘어가자.
C언어는 문법적으로 객체 지향을 지원하지 않으나, C언어로 객체 지향적인 구현이 불가능한 것은 아니다.리눅스 커널을 만든 리누스 토르발스의 말.[13]
C를 C++로 확장시키고 최초의 C++ 컴파일러를 만든 비아르네 스트로우스트루프의 경우 처음 컴파일러를 구현할 때 C++를 C 언어로 변환시키는 인터프리터를 통해 만들었다. (물론 현존하는 대부분의 컴파일러는 C++을 C로 바꾸는 작업을 하지 않고 바로 컴파일을 수행한다.) 즉, C로 객체 지향 코딩이 가능하다는 말. 스트로우스트루프가 직접 저술한 The C++ Programming Language 책에 해당 내용을 언급한다.
이걸 실용적으로 쓰는 분야도 있다. 임베디드나 펌웨어 관련 프로젝트에서는 여러 가지 사정으로 (여러 플랫폼에 포팅 가능해야 한다거나, 툴체인을 지원하지 않는다거나) C언어를 사용하여 큰 규모의 소스 코드를 작성해야 할 경우가 많은데 이런 경우에는 객체 지향적인 설계를 도입해야 모듈이나 레이어 사이의 혼잡을 피할 수 있다. 따라서 이런 프로그래머들은 객체 지향적인 C의 설계는 반드시 익혀야 할 내용이다.
객체 지향은 Java나 Python처럼 복잡하지 않아도, 구조체에 함수 포인터 정도만 할당해도 쉽게 구현할 수 있다. 이는 객체 지향이라는 것은 어디까지나 언어의 패러다임에 불과하므로[14] 그 구현을 직접 구현해야 하는 것만 빼면 (상속은 Struct 안에 부모의 Struct을 포함한다거나, C++ 클래스의 vtable을 받아 오고 클래스 멤버에 그 클래스 컨텍스트의 포인터를 지정해
this
포인터를 구현하거나 하는 등) 아무 문제 없이 구현이 가능하고 임베디드와 같이 메모리 용량이 한정적인 환경에서는 C++ 구동에 필요한 C++ 런타임의 용량 또한 무시할 수 없는 크기를 가지고 있기 때문에 중요하게 사용된다.임베디드뿐만 아니라 다른 영역에서도 자주 쓰이는 방법 중 하나로 라이브러리의 래퍼를 만들 때도 자주 사용된다. 예를 들어 어떤 라이브러리가 C++로 만들어져 있고 API는 클래스와 템플릿으로만 이루어져 있을 때, 이를 C언어 그 차체나 Rust|, Python등의 다른 언어에서 사용할 수 있도록 다른 언어로 래핑하는 경우에도 C를 사용해 객체 지향을 구현하고 이 심볼을 cdecl로 노출시켜 다른 언어에서 사용(Python의
ctypes.cdll.LoadLibrary
와 같이)하는 경우에도 사용된다.실제로 Windows 개발에 사용되는 COM이 이런 식으로 구현되어 있어 언어에 상관없이 ABI를 노출시켜 객체 지향적인 방법으로 사용되고 있다.
C가 아닌 다른 언어를 예를 들면 자바스크립트에서는 해시 테이블 하나로 객체 지향을 몽땅 구현하는 것을 참고하자. Java나 Python 프로그래머들조차 객체 지향에서 필요한 기능만을 반복적으로 사용하지, 모든 기능을 뒤섞어 사용하면 코드가 난잡해지기 때문에 피하는 경우가 많다.
대부분의 경우에는 굳이 C로 구현해서 쓰기보다는 Java나 C# 같은 고수준 언어를 찾아보는 것이 좋다. ABI(Application Binary Interface)가 필요한 경우가 아니라면 C 같은 저수준 언어는 무의미하다.
하지만 C를 이용해 객체 지향 프로그래밍을 한다는 것 자체가 난해한 프로그래밍 언어에 가까운 기술이고, C를 이용해 프로그래밍을 하는 대부분의 상황 자체가 로봇이나 기계를 어떤 식으로 움직이라는 등 단순한 논리를 하드웨어에 맞추어 가장 빠르게 구현하는 것이 대부분이기 때문에 객체 지향을 도입할 필요가 있다면 비즈니스 요구 사항부터 잘못되었을 가능성이 크다.
만약에 객체 지향을 굳이 C로 하는 게 필요하다면, 일반적인 상황이 아니고 정부 기관, 연구소, 군대 등 굳이 C만을 써야 하는 레거시 시스템이 존재하는 곳에서 어쩔 수 없이 큰 비용을 지출하며 전문가들을 뽑아서 프로그래밍을 시켜야 할 것이다.
일반적인 경우 고성능과 객체 지향이 동시에 필요하다면 아예 C++나 Rust를 공부하고 새로 개발하는 게 더 낫다.
7. 용어 번역 관련
OOP 의 Object를 객체라고 번역할 것인가 개체라고 번역할 것인가에 대한 논쟁이 있다. OOP라는 개념이 처음 한국에 들어올 때 Object가 객체로 번역되었고 현재에 이르렀지만 한국어의 객체라는 단어는 OOP의 Object를 뜻하는 단어가 아니기 때문에 생긴 논쟁이다. 언어의 사회성 때문에 이미 고착화되었고, 사용자들조차 두 단어의 차이를 몰라 이제 와서 고쳐야 할 필요성을 느끼는 경우는 별로 없지만 분명 개념상으로 잘못 번역된 단어이다.* 개체
1. 전체나 집단에 상대하여 하나하나의 낱개를 이르는 말.
* 우리는 정반대로 법칙에서 자유를, 전체에서 개체를 그리고 격식에서 어떤 혼돈을 희구하려고 했던 것이 아닐까?
1. 생명 하나의 독립된 생물체. 살아가는 데에 필요한 독립적인 기능을 갖고 있다.
* 다세포 생물은 생식 세포가 존재하는데, 그 수정의 결과 새로운 개체가 발생한다.
1. 철학 단일하고 독립적인 통일적 존재. 철학 사상의 발전 과정에서 이 통일성은 물질적ㆍ양적 측면, 또는 정신적ㆍ질적 측면 따위의 여러 관점에서 고찰되었다.
* 객체
1. 철학 의사나 행위가 미치는 대상.
* 묘지 침해죄는 침해의 객체에 따라 묘소와 시설물의 절취, 훼손과 시체에 대한 침해로 구분된다.
1. 언어 문장 내에서 동사의 행위가 미치는 대상.
* 피동문 '선생님이 술래에게 잡히셨다.'에서 '술래'가 주체이면 '선생님'은 객체이다.
1. 철학 작용의 대상이 되는 쪽.
* 유럽에서 시작된 과학적 세계관은 인간을 사유하는 주체로 파악한 반면 자연 세계는 사유하는 주체의 객체나 대상으로 사물화했다.
* object
1. 물건, 물체
* everyday objects such as cups and saucers
1. 욕망, 연구, 관심 등의 대상
* 문형
~ of desire, study, attention, etc.
1. 목적, 목표
* Her sole object in life is to become a travel writer.
1. 문법
* 목적어
출처: 네이버 사전
위의 사전 정의와 같이 객체라는 단어는 OOP에서 정의하는 Object의 의미를 가지고 있지 않다. 객체란, 사전 정의에서 강조한 대로 어떠한 행동의 대상을 가리키는 단어이며 Object의 2, 3, 4번 정의에 해당하는 단어이다. 주체 객체로 보통 한 묶음으로 표현되며 주체는 행위자, 객체는 행위의 대상을 의미한다.(영어 문법에서 subject verb object 할 때 object가 객체다.)1. 전체나 집단에 상대하여 하나하나의 낱개를 이르는 말.
* 우리는 정반대로 법칙에서 자유를, 전체에서 개체를 그리고 격식에서 어떤 혼돈을 희구하려고 했던 것이 아닐까?
1. 생명 하나의 독립된 생물체. 살아가는 데에 필요한 독립적인 기능을 갖고 있다.
* 다세포 생물은 생식 세포가 존재하는데, 그 수정의 결과 새로운 개체가 발생한다.
1. 철학 단일하고 독립적인 통일적 존재. 철학 사상의 발전 과정에서 이 통일성은 물질적ㆍ양적 측면, 또는 정신적ㆍ질적 측면 따위의 여러 관점에서 고찰되었다.
* 객체
1. 철학 의사나 행위가 미치는 대상.
* 묘지 침해죄는 침해의 객체에 따라 묘소와 시설물의 절취, 훼손과 시체에 대한 침해로 구분된다.
1. 언어 문장 내에서 동사의 행위가 미치는 대상.
* 피동문 '선생님이 술래에게 잡히셨다.'에서 '술래'가 주체이면 '선생님'은 객체이다.
1. 철학 작용의 대상이 되는 쪽.
* 유럽에서 시작된 과학적 세계관은 인간을 사유하는 주체로 파악한 반면 자연 세계는 사유하는 주체의 객체나 대상으로 사물화했다.
* object
1. 물건, 물체
* everyday objects such as cups and saucers
1. 욕망, 연구, 관심 등의 대상
* 문형
~ of desire, study, attention, etc.
1. 목적, 목표
* Her sole object in life is to become a travel writer.
1. 문법
* 목적어
출처: 네이버 사전
반면 OOP에서 Object란 대부분의 프로그래밍 언어에서 class를 이용하여 new로 생성한 하나의 개체를 의미하며 Object의 1번 정의에 해당한다. 대학 컴공에서 흔히 비유하듯 class가 붕어빵틀 이라면 거기서 찍혀 나온 붕어빵 개개는 하나의 독립된 개채로서 작동한다.
해당 번역의 문제점을 인지한 것인지 마이크로소프트 learn의 OOP 항목의 한국어 번역 페이지도 개체 지향 프로그래밍으로 번역되어 있다.[15]
8. 관련 문서
[1] 사실 베이직 프로그램에서는 이 GOTO의 문제를 해결하기 위해 초창기부터 GOSUB/RETURN 이라는 대체재가 있었다.[2] 참고로 위 도표는 이런 네임스페이스 포화에 대한 개그이기도 하지만, 정말로 클래스/메소드/변수 이름을 짓는 게 어렵기 때문에 나온 개그이기도 하다. 용도를 잘 표현할 수 있고, (함수의 경우라면) 입력과 출력까지 잘 명시할 수 있으며, 가독성을 해치지 않을 정도로 짧고 간결한 데다가, 거기에 중복까지 회피할 수 있는 이름을 짓는 건 상당한 창작의 고통(…)을 수반하는 일이다. 클래스/메소드 이름만 똑바로 지어놔도 가독성과 유지 보수성의 절반 이상은 먹고 들어갈 수 있기 때문에 매우 중요한 일이다. '시작이 반이다'라는 유명한 격언이 괜히 프로그래밍에도 적용되는 게 아니다.[3] "1+2" 라는 수식도 수식으로 보지 않는다. "1"이라는 객체에 "+2"라는 메시지를 보내는 것으로 본다. 괄호를 제외한 사칙 연산의 순서도 생각하지 않는다. 1+2X2이면, 순서대로 "1"이라는 객체에 "+2","X2"라는 메시지를 보내서 연산할 뿐이다. 객체 지향이지만 사칙 연산 곱셈부터 하는 언어들은 본받아야 한다[4] 단, C++의 경우 이런 동작을 내려면 해당 메소드를 가상 함수로 선언해야 한다.[5] 예를 들면 Visual Studio 6.0의 C++는 데이터베이스의 자료 처리 시 변수를 모두 public으로 처리했었다. 이 이유는 데이터베이스의 필드가 50개이면 get과 set 메소드를 모두 구현해 주면 100개의 메소드가 만들어져야 했기 때문이다. 변수 50개와 메소드 100개를 다 150개를 키보드로 쳐야 한다. 다만 중고급 수준의 경력을 쌓을 정도로 다양한 코드들을 보다 보면 상속 등을 이용해서 자신만의 라이브러리를 만들게 되면서 저런 무의미한 노가다를 생략하는 코드를 짤 수 있게 된다.[6] 버전 8에서 대격변이 일어났으며, 장황한 면이 있지만 함수형 프로그래밍을 꽤나 괜찮게 지원한다. 스트림과 멀티스레드 활용 등에서 적어도 앞서 언급된 파이썬보다는 제대로 지원한다.[7] ES6부터 클래스 문법이 추가되었다. 프로토타입의 문법적 설탕에 불과하다는 평도 있었으나 프로토타입에 비해 훨씬 더 엄밀한(실수할 여지를 더 줄이는) 방식으로 프로그래밍해야 되기 때문에 단순히 평가하기는 어렵다.[8] 3.0에서 클래스가 추가되었다.[9] 파이썬의 경우 낮은 레벨에서는 객체 지향 의존도가 상당히 덜하다. 따라서 C# 같은 언어가 객체 지향이 없으면 안 돌아가는 수준인 데 반해 파이썬은 객체 지향이 없어도 얼마든지 코드를 굴릴 수 있다. 이것은 파이썬의 입문 장점 중 하나이지만 나중에 어려워지기 시작하면 파이썬도 결국에는 객체 지향을 배워야 하기 때문에 결국 배움의 시점 차이에 불과하다.[10] 주로 Java 같은 강자료형 언어가 취하는 자세인데, 이런 언어에서는 클래스명이 곧 자료형인 경우가 많기 때문.[11] Java는 '객체'를 뜻하는 영단어 Object로 명명된 클래스가 존재하기 때문에, 구분을 위해 인스턴스라는 표현을 공식적으로 사용하고 있다.[12] 과거 포스트 모더니즘 유행과 비슷.[13] http://harmful.cat-v.org/software/c++/linus[14] 어떤 언어가 절차 지향이든 객체 지향이든 선언형이든 명령형이든 상관 없이 모든 언어는 기계어 및 어셈블리어와 같은 1차원적 언어로 컴파일된다는 점을 생각하면 당연한 이야기다.[15] https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/tutorials/oop