객체 지향 언어 Java , Python 등의 언어들은 들어보고 사용해 봤다.
하지만 객체 지향의 정확한 뜻을 알고, 제대로 쓰지는 못한 것 같아서 정리해보려고 한다.
객체 지향의 뜻, 절차적 프로그래밍과의 비교
나무 위키 출처 :
정의
객체 지향 프로그래밍(Object-Oriented Programming), 줄여서 OOP.
프로그램을 어떻게 설계해야 하는지에 대한 일종의 개념이자 방법론.
상세 절차
프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체'라는 기본 단위로 나누고 이 객체들의 상호작용으로 서술하는 방식이다.
절차적 프로그래밍과 비교
기존 절차적 프로그래밍에서는 함수를(메소드) 기계, 데이터를 원료로 생각해서 데이터가 함수 사이를 통과하면서 차츰 순서대로 가공돼 나가는 방식으로 이해한다면 객체 지향 프로그래밍에서는 데이터를 중심으로 메서드가 데이터에 접근해서 수정한다는 개념이다. 즉 원료가 움직이냐 기계(함수{메소드})가 움직이냐의 차이.
위키 백과 출처 :
객체 지향 프로그래밍(영어: Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임의 하나이다. 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 또한 프로그래밍을 더 배우기 쉽게 하고 소프트웨어 개발과 보수를 간편하게 하며, 보다 직관적인 코드 분석을 가능하게 하는 장점을 갖고 있다. 그러나 지나친 프로그램의 객체화 경향은 실제 세계의 모습을 그대로 반영하지 못한다는 비판을 받기도 한다.
| 절차 지향 언어 | 객체 지향 언어 |
장점 | 1. 완성된 코드의 실행처리 속도가 빠르다. 2. 초기 프로그래밍 언어로 컴퓨터의 구조와 비슷해 속도가 빠르다. | 1. 코드 재사용 용이 2. 코딩이 간단 3. 유지보수가 쉽다 4. 대형 프로젝트에 좋다.
|
단점 | 1. 유지 보수가 어렵다. 2. 분석이 어렵다. 3. 대형 프로젝트에 부적합 | 1. 처리속도가 상대적으로 느림 2. 객체가 많으면 용량이 커질 수 있다 3. 설계 시 많은 시간이 투자된다 4. 설계 실패 시 처음부터 다시 시작. |
참고 : http://hunit.tistory.com/151
객체 지향 구성 요소와 특징
위키 백과 출처 :
기본 구성 요소[편집]
- 클래스(Class) - 같은 종류(또는 문제 해결을 위한)의 집단에 속하는 속성(attribute)과 행위(behavior)를 정의한 것으로 객체지향 프로그램의 기본적인 사용자 정의 데이터형(user define data type)이라고 할 수 있다. 클래스는 프로그래머가 아니지만 해결해야 할 문제가 속하는 영역에 종사하는 사람이라면 사용할 수 있고, 다른 클래스 또는 외부 요소와 독립적으로 디자인하여야 한다.
- 객체(Object) - 클래스의 인스턴스(실제로 메모리상에 할당된 것)이다. 객체는 자신 고유의 속성(attribute)을 가지며 클래스에서 정의한 행위(behavior)를 수행할 수 있다. 객체의 행위는 클래스에 정의된 행위에 대한 정의를 공유함으로써 메모리를 경제적으로 사용한다.
- 메서드(Method), 메시지(Message) - 클래스로부터 생성된 객체를 사용하는 방법으로서 객체에 명령을 내리는 메시지라 할 수 있다. 메서드는 한 객체의 서브루틴(subroutine) 형태로 객체의 속성을 조작하는 데 사용된다. 또 객체 간의 통신은 메시지를 통해 이루어진다.
클래스 + 객체 + 메서드 == 객체지향 구성요소
나무 위키 출처 : 원래 객체와 인스턴스를 설명할 때는 붕어빵을 예시로 많이 드는데 위키 백과는 다른 예시를 들어 주었다.
3. 설명[편집]
3.1. 캡슐화(Encapsulation)[6][편집]
캡슐화의 목적은 코드를 재수정 없이 재활용 하는 것 *
객체 지향 방식의 기본중의 기본 개념이다. 이 캡슐화를 안지키면 나머지 상속과 다형성은 성립이 안된다. 캡슐화의 목적은 "코드의 수정 없는 재활용"을 생각해보면 당연한 개념이다. 프로그램 코드를 재활용 하려고 하는데 기능(함수/프로시저 : 메소드)이 분산되어 있고 특성(변수 : 데이터)이 분산 되어 있는 프로그램 코드는 재활용을 하기 매우 힘들다. 이 때문에 관련 기능과 특성을 한곳으로 모으고 분류할 필요성이 있다.
클래스 & 인스턴스
객체 지향에서는 이렇게 계층적으로 분류한 기능과 특성의 모음을 클래스(Class)라는 캡슐(capsule)에 분류된 집단 별로 각각 집어 넣는다. 이러한 클래스를 실체화(Instance) 하면 객체(Object)를 만들수 있다.
비유 1: 이것을 쉽게 비유하자면 "한국인"이라는 집단(클래스)에 "홍길동"이라는 한국인(인스턴스)을 소속 시킨다(만든다) 라고 할 수 있다.
비유 2: X회사의 고급형 데스크탑(Advanced-Power gaming PC) 브랜드(클래스) -> PC 생산(객체 생성) -> Advance-power gaming PC라는 상품 명으로 생산된 PC 한 대(인스턴스)는 특정한 생산일자와 고유 일련번호(2015-12-25RX, 즉 2015년 12년 25일 날 생산된 PC 한 대)를 가지고 있다. 여기에서 클래스는 일반적인 모델명인 'Advanced-Power gaming PC'(구성품과 스펙은 클래스 내부에 속한 메소드와 변수 등으로 볼 수 있다)를 가리키는 것이고, 인스턴스는 '2015-12-25RX'라는 고유 일련번호를 가진 특정한 'Advanced-Power gaming PC'라는 상품명을 가진 한 대의 PC(인스턴스)만을 가리키는 것이다.
만일 우리가 '2015-12-25RX'라는 PC를 가지고 게임을 하거나 실행하는 것(2015-12-25RX.게임 실행; 2015-12-25RX.인터넷 탐색; 2015-12-25RX.전원 오프 등)은 어디까지나 우리 앞에 놓여진 '2015-12-25RX'라는 실제의 PC를 컨트롤하면서 게임을 하거나 인터넷을 탐색 하는 등의 활동을 하는 것이지. 허상의 'Advance-power gaming PC' 브랜드를 컨트롤 하는 것은 아닌 것이다.
또한, 여기에서 더 나아가자면, 비록 'Advanced-Power gaming PC)'라는 똑같은 모델명(똑같은 클래스)을 가지고 있지만, 생산일자와 장소 등이 달라서 일련번호(2016-2-1AR)가 다른 PC 한 대(즉, 2015-12-25RX와는 완전 다른 '2016-2-1AR'라는 독립된 인스턴스 b)는 완전 PC 한 대이면서 '2015-12-25RX'라는 PC(이미 만들어졌던 과거의 인스턴스 a)와는 완전 별개의 존재다. 그래서 이론적으로는 2015-12-25RX을 포맷을 하든, 게임을 설치하든, 업그레이드를 하든, 오버클럭킹을 하든 간에 이것은 어디까지나 2015-12-25RX(인스턴스 a)에 해당되는 것이지, 완전 별 개인 2016-2-1AR의 PC(인스턴스 b)와는 아무 관련이 없는 것이다.
컴퓨터 내부 처리 절차
객체 생성(생성자): 실제 컴퓨터에서 물리적으로 일어 나는 현상은 "홍길동" 이라는 객체의 생성을 위해 "한국인 Class"에 정의된 데이터가 들어갈 메모리를 힙에 할당 하고 기본적인 한국인 클래스의 특성을 초기화 하기 위해 생성자를 호출 하여 "홍길동" 객체를 초기화 한다.
객체 소멸(소멸자): 객체를 메모리에 제거할 땐 할당된 자원(메모리,파일 등)을 정리하기 위해 소멸자(Destructor)를 호출한다. 소멸자의 경우에는 Java나 .NET Framework같이 가비지 콜렉션이 있는 객체 지향 플랫폼일 경우에는 내부적으로 사용하므로 특별한 경우 외에는 소멸자를 프로그램 코드에 넣지 않는다.
정리
이와 같이 캡슐화는 '클래스, 타입, 인스턴스, 생성자, 소멸자' 같은 객체 지향 프로그램 방식의 기초를 형성한다. 다음에 설명할 상속성과 다양성은 객체의 메카니즘(작동 원리)이기 때문에 캡슐화를 정확히 표현하지 못하면 상속성과 다형성 또한 표현을 잘못하게 되어 있다.
3.2. 상속(Inheritance)[편집]
사실 클래스 이전의 프로그래밍 기법에선 코드 재활용이 불가능하지는 않았다. 예전에도 라이브러리 등을 통해서 남이 짜놓은 코드를 그대로 가져올 수 있었다.[7]
'라이브러리'의 문제점
라이브러리는 코드의 재활용에 지대한 영향을 미쳤지만 치명적인 단점이 있었다. 라이브러리의 기능을 약간 바꾸어야 할 경우 라이브러리의 소스를 변경해야 했고 이 때문에 전혀 다른 라이브러리가 되어 버린다는 것이다. 이것은 라이브러리 버전에 따라 그 라이브러리를 사용하는 프로그램이 동작을 안할수도 있다는 것이고 불필요한 코드의 수정작업을 해야 한다는 것이다.
'상속'의 도입
객체 지향 프로그램에서는 이 문제를 해결하기 위해 "상속"을 도입 했다."포용성"으로 이전의 라이브러리보다 더 논리적이고 체계적으로 기능과 데이터를 계층적으로 분류해서 사용의 편의성을 도모하면서, 상속을 사용해 부모 클래스의 특성과 기능을 그대로 이어받고 기능의 일부분을 변경해야 할 경우 상속 받은 자식 클래스에서 그 기능만을 다시 정의하여 수정하게 하였다. 이러한 작업을 "덮어쓰기(재정의 : Override)"라고 한다.
3.3. 다형성(Polymorphism)[편집]
하나의 변수명, 함수명 등이 상황에 따라 다른 의미로 해석될 수 있는 것을 말한다. 위에서 설명한 오버라이딩 이외에, 변수에 따라 함수의 기능이 달라지는 오버로딩도 여기에 해당한다.
연산자 오버로딩
C++, C# 등에서는 기본 연산자를 오버로딩해서 기본 연산자가 해당 클래스에 맞는 역할을 수행하게 하는 것도 가능하다.
JAVA 등에서는 연산자의 오버로딩이 불가능하다.
펄 6나 스몰토크, F# 등, 연산자의 신규 정의가 가능한 언어도 있다.
위키 백과 출처 :
특징[편집]
객체 지향 프로그래밍의 특징은 기본적으로 자료 추상화, 상속, 다형 개념, 동적 바인딩 등이 있으며 추가적으로 다중 상속 등의 특징이 존재한다. 객체 지향 프로그래밍은 자료 추상화를 기초로 하여 상속, 다형 개념, 동적 바인딩이 시스템의 복잡성을 제어하기 위해 서로 맞물려 기능하는 것이다.
자료 추상화[편집]
자료 추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현함으로써 프로그램을 간단히 만드는 것이다. 자료 추상화를 통해 정의된 자료형을 추상 자료형이라고 한다. 추상 자료형은 자료형의 자료 표현과 자료형의 연산을 캡슐화한 것으로 접근 제어를 통해서 자료형의 정보를 은닉할 수 있다. 객체 지향 프로그래밍에서 일반적으로 추상 자료형을 클래스, 추상 자료형의 인스턴스를 객체, 추상 자료형에서 정의된 연산을 메소드, 메소드의 호출을 메시지라고 한다.
( abstract 메소드나 클래스와는 다른 개념인 것 같다. )
상속[편집]
상속은 새로운 클래스가 기존의 클래스의 자료와 연산을 이용할 수 있게 하는 기능이다. 상속을 받는 새로운 클래스를 부클래스, 파생 클래스, 하위 클래스, 자식 클래스라고 하며 새로운 클래스가 상속하는 기존의 클래스를 기반 클래스, 상위 클래스, 부모 클래스라고 한다. 상속을 통해서 기존의 클래스를 상속받은 하위 클래스를 이용해 프로그램의 요구에 맞추어 클래스를 수정할 수 있고 클래스 간의 종속 관계를 형성함으로써 객체를 조직화할 수 있다.
다중 상속[편집]
다중 상속은 클래스가 2개 이상의 클래스로부터 상속받을 수 있게 하는 기능이다. 클래스들의 기능이 동시에 필요할 때 용이하나 클래스의 상속 관계에 혼란을 줄 수 있고(예: 다이아몬드 상속) 프로그래밍 언어에 따라 사용 가능 유무가 다르므로 주의해서 사용해야 한다. JAVA는 지원하지 않는다.
( 다중 상속을 지원하는 언어에서도 다중 상속은 지양시키려고 한다 )
다형성 개념[편집]
다형성 개념이란 어떤 한 요소에 여러 개념을 넣어 놓는 것으로 일반적으로 오버라이딩(같은 이름의 메소드가 여러 클래스에서 다른 기능을 하는 것)이나 오버로딩(같은 이름의 메소드가 인자의 갯수나 자료형에 따라서 다른 기능을 하는 것)을 의미한다. 다형 개념을 통해서 프로그램 안의 객체 간의 관계를 조직적으로 나타낼 수 있다.
동적 바인딩[편집]
동적 바인딩은 실행 시간 중에 일어나거나 실행 과정에서 변경될 수 있는 바인딩으로 컴파일 시간에 완료되어 변화하지 않는 정적 바인딩과 대비되는 개념이다. 동적 바인딩은 프로그램의 한 개체나 기호를 실행 과정에 여러 속성이나 연산에 바인딩함으로써 다형 개념을 실현한다.
여기까지는 대부분 많이 아는 내용도 많다.
이 정도 내용은 많이 알지만 실제로 적용하기 위해서는 많은 어려움이 따르는 것 같다.
나도 이 정도는 사전에 어느 정도 알고 있었지만, 코딩은 실제로 객체지향적이 아니라 절차지향적인 모습도 많이 갖추고 있었다.
따라서 지켜야 할 것도 많고, 원칙도 많은데 그런 것을 알아보자.
객체 지향 프로그래밍 원칙
나무 위키 출처 :
1. 개요[편집]
객체지향 5원칙(SOLID).
객체지향에서 꼭 지켜야 할 5개의 원칙을 말한다. 일단 한번 보면 개념은 알아 듣긴 하지만 막상 실현하려면 생각보다 어려움이 따른다. 이 5개의 원칙의 앞글자를 따서 SOLID라고도 부른다.
2. 목록[편집]
2.1. SRP : 단일 책임 원칙[편집]
Single Responsibility Principle
모든 클래스는 단 하나의 책임을 가져야 한다는 원칙이다. 다르게 말하면 클래스를 수정할 이유가 오직 하나여야만 한다는 뜻이기도 하다.
예를 들자면, 계산을 하는 클래스 '계산기'가 있다고 치자. 근데 이 클래스는 GUI 환경에서 사용될 거라고 한다. 그래서 프로젝트에 GUI 코드를 추가해야하는 상황인데 만약 GUI 코드를 '계산기' 클래스에 넣었다면 SRP를 위반하게 된다. 이 클래스는 '계산을 한다'라는 책임과 'GUI로 나타낸다'라는 책임 두개를 지게 되는 것이다! 이렇게 되면 계산용 필드/메소드와 GUI의 필드/메소드가 뒤섞여 사용자도 개발자도 헷갈린다. 버그가 발생해서 한참 찾았더니 GUI용 코드가 아니라 비슷한 이름의 계산용 메소드를 호출하고 있다던지... 또 극단적으로, 갑자기 일정이 변환되어 CUI 환경으로 프로젝트가 전환되었다고 치자. 다른 프로그래머들은 불평하면서도 객체지향적 분리를 잘 해놔서 몇몇 클래스만 수정해 CUI용으로 바로 전환한다. 하지만 이 '계산기' 클래스를 맡은 프로그래머는 GUI 코드의 필드/메소드 뜯어내랴, 뜯어내다 생긴 부작용 치우랴, 클래스의 사용법 바꾸랴... 할일이 많아질 것이다.
좋은 예시를 들어준 것 같다.
2.2. OCP : 개방-폐쇄 원칙[편집]
Open Closed Principle
모든 소프트웨어 구성 요소는 확장에 대해서는 개방되어있지만, 수정에 대해서는 폐쇄되어있다는 원칙이다.
이번에도 예를 들자면, 스타크래프트의 유닛을 만든다고 치자. 당신은 이런저런 공통사항을 생각하며 메소드와 필드를 정의한다. 이중엔 이동 메소드도 있다. 이동 메소드는 대상 위치를 인수로 받아 속도에 따라 대상 위치까지 유닛을 길찾기 인공지능을 사용해 이동한다. 하지만 잠깐 곰곰히 생각해보니 이러면 브루들링같은 유닛의 기묘한 움직임을 구현할때 애로사항이 꽃필것 같다. 당신은 고민하다가 이동 함수에서 이동 패턴을 나타내는 함수(혹은 클래스)를 분리해서 구현을 하위 클래스에 맡긴다. 그러면 부르들링 클래스에선 이것만 재정의/설정하면 유닛 클래스의 변경 없이 색다른 움직임을 보여줄수 있다! 이 '유닛'클래스의 '이동' 메소드를 수정할 필요조차 없다(수정에 대해선 폐쇄). 그냥 브루들링 클래스의 이동 패턴만 재정의하면 그만인 것이다(확장에 대해선 개방).
예시를 읽어보면 인터페이스에 관련된 내용인 것 같다. ( 혹은 추상 메소드 )
2.3. LSP : 리스코프 치환 법칙[편집]
Liskov Substitusion Principle
자식 클래스는 언제나 자신의 부모 클래스를 교체할 수 있다는 원칙이다. 즉 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 작동해야 한다는 것. 상속의 본질인데, 이를 지키지 않으면 부모 클래스 본래의 의미가 변해서 is a 관계가 개발살나며, 그로 인해 다형성 불구자가 된다. 본질적인 구조를 바꾸지 않고 이를 해결하려 하면 높은 확률로 의존성 역전 법칙을 동시에 어기게 된다(...).
또다시 예를 들면, 컴퓨터용 '마우스' 클래스가 있다고 치자. 이 컴퓨터용 '마우스'는 어떤 마우스를 사오던 컴퓨터에 있는 ps/2 포트나 usb 포트에 연결할수 있을 것이다. 사용 면에서는 왼쪽과 오른쪽 버튼, 그리고 휠이 있어 사용자가 누르거나 굴릴수 있을 것이다. 또한 무언가 바닥에 대고 움직이면 컴퓨터가 이를 받아들인다는 것도 안다. 마우스가 볼마우스던 광마우스던, 아니면 GPS를 이용하건 외계인 테크놀러지로 무중력 상태에서도 쓸수 있건 간에 암튼 사용자는 바닥에 착 붙여 움직일것이고, 모든 마우스는 예상대로 신호를 보내 줄 것이다. 또한 만약 추가적인 특별한 버튼이 있는 마우스라도 그 버튼의 사용을 제외한 다른 부분은 보통의 마우스와 다를 바 없으므로 사용자는 그 마우스의 그 버튼이 뭔 역할을 하던간에 무심한듯 시크하게 아무 문제 없이 잘 사용한다. 하지만 오른쪽/왼쪽 버튼 대신 뒤쪽/앞쪽 버튼을 사용하는 기묘한 마우스가 나왔다고 치자. 사용자는 평소 보던 버튼을 누를수가 없으므로 "현기증 난단 말이에요 빨리 '제대로 된' 마우스 가져다 주세요"라고 이상을 호소할 것이다. 위의 볼마우스나 광마우스는 LSP를 휼륭하게 지키지만[1] 바로 위의 예제는 전혀 그렇지 못하다고 볼 수 있다.
2.4. ISP : 인터페이스 분리 원칙[편집]
Interface Segregation Principle
클라이언트에서 사용하지 않는 메서드는 사용해선 안된다. 그러므로 인터페이스를 다시 작게 나누어 만든다. OCP와 비슷한 느낌도 들지만 엄연히 다른 원칙이다. 하지만 ISP를 잘 지키면 OCP도 잘 지키게 될 확률이 비약적으로 증가한다.이젠 좀 지겹겠지만 또 예를 들어보자. 게임을 만드는데 충돌 처리와 이팩트 처리를 하는 서버를 각각 두고 이 처리 결과를 (당연히) 모두 클라이언트에게 보내야 한다고 가정하자. 그러면 아마 Client라는 인터페이스를 정의하고 그 안에 충돌전달()과 이펙트전달(이펙트)를 넣어놓을 것이다. 그리고 충돌 서버와 이펙트 서버에서 이 인터페이스를 구현하는 객체들을 모아두고 있으며, 때에 따라 적절히 신호를 보낸다. 하지만 이렇게 해두면 충돌 서버에겐 쓸모없는 이펙트전달 인터페이스가 제공되며, 이펙트 서버에겐 쓸모없는 충돌전달 이펙트가 제공된다. 이를 막기 위해선 Client인터페이스를 쪼개 이펙트전달가능 인터페이스와 충돌전달가능 인터페이스로 나눈 뒤, 충돌에는 충돌만, 이펙트에는 이펙트만 전달하면 될 것이다. 또한 Client 인터페이스는 남겨두되 이펙트전달가능과 충돌전달가능 이 둘을 상속하면 된다.
2.5. DIP : 의존성 역전 법칙[편집]
Dependency Inversion Principle
상위 클래스는 하위 클래스에 의존해서는 안된다는 법칙이다. 당연하다고 생각되면서도 잘 어겨지는것 1순위(...). 팩토리 패턴이 귀찮다고 별도 클래스 분리 없이 상위 클래스에 정적 메소드로 구현하는 경우가 대표적인 어기는 사례이다 [2].
getter, setter 사용하는 이유
이번 공부하는 김에 조사하면서 항상 궁금해했던 getter, setter 을 사용하는 이유를 정리해보았다.
항상 멤버변수에 직접접근을 하면 되는데 왜 getter setter 을 통해서 접근을 하는거지?
직접 접근을 하니 간접접근을 하나 접근을 해서 멤버변수를 변경시킨다면 똑같은거 아닌가? 하는 생각이 들었었다 항상.
찾아보니 다음과 같은 사실을 알게 되었습니다.
- getter setter 을 사용하는 경우에는 메소드이기 때문에, 들어오는 인자에 관하여 제한이나 작동을 조절 할 수 있다.
= > 가령
public People{
public int number ;
}
이 int 형 number 같은 경우에는 생성자로 처음에 대입하는 값이 아니라면, 인자를 제한할 방법이 없습니다.
생성자를 통해서 넣는 값은 아니지만 제한을 해 주거나 다른 값을 리턴 해야 할 경우가 생길 수 있을 것입니다.
=>
public People{
private int number ;
public int getNumber () {
return this.number;
//return 2;
//return this.number +1 ;
}
}
위와 같은 경우에는 getNumber 에서 원하는 대로 number 을 조절해 줄 수 있습니다.
void MyClass::SetAge(int pAge)
{
if ( pAge >= 1 || pAge <= 150 ) // 당연히 1살 이상이여야 하고, 150살 넘어까지 못산다고 하면...
{
m_Age = pAge;
}
else
{
// 유효한 범위를 넘었다는 예외를 발생시킨다!
}
}
또한 이런 예도 있을 수 있습니다.
클라이언트가 내 클래스에서 변수 몇번 읽어 갔나를 셀 수도 있죠.
int MyClass::GetAge()
{
GetAgeCount++;
return m_Age;
}
이를 멤버 변수에 접근하는
이게 핵심인 것 같습니다.
참고 : http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tip&no=390
======================================================================================
이론은 이런 식으로 정리 해 봤는데 사실 직접 해보면서 깨달아야 더 깊이 알수 있을 것 같다.
아직은 제대로 체감이 오지 않는다.
이 블로그 만들 때, 객체지향적으로 만들지 않아서, 귀찮게 계속 추가해야 하는 번거로움을 느끼긴 했는데
객체지향적 설계가 익숙하지 않아서 제대로 하지 못했다.
계속 공부하고 개발 해가면서, 익히고 수정하고 발전시켜야겠다.