이전에 다룬 적 있던 객체지향의 특징에 대해 다시 한 번 자세히 키워드로 알아보려고 한다.
객체지향에는 4대특징이라 불리우는 것이 있다.
첫 째로는 '캡슐화' 이다.
캡슐화란 객체를 상태와 기능으로 묶는 것을 의미하며, 이렇게 묶인 객체는 객체 내부의 상태와 기능을 숨기고 허용한 상태와 기능에만 접근을 허용하는 것이다.
이렇게 캡슐화 된 객체들은 서로의 정보에 접근 및 수정하는 것이 제한적이게 되고, 이와 같은 특성은 각 객체들간의 의존도는 낮아고, 자율성은 높이는 결과를 불러온다.
서로 섞일 위험은 줄이면서, 각 객체들이 가진 고유한 기능들이 각각 철저히 분리되어있으면 있을 수록, 유지보수가 굉장히 용이해진다.
각 정보와 기능들의 세분화가 가능해지기 때문에, 어느 부분에서 문제를 봉착했는지 알기 쉽고, 어떤 부분을 고쳤는지 알기 쉽다.
둘째로는 '상속' 이다.
상속이란, 부모 클래스의 모든 기능을 가지는 자식 클래스를 설계할 수 있음을 말한다.
상속을 통해 코드의 재사용성을 높일 수 있다. 이것은 8 - 1 상속 편에서 다룬 적이 있다.
부모 클래스를 자식에게 상속시키면, 모든 자식 클래스들은 부모 클래스의 기능과 요소들을 사용할 수 있어, 클래스마다 일일히 코드를 작성할 필요가 없어진다.
셋째로는 '추상화'이다.
추상화란, 관련 특성 및 엔터티의 상호작용을, 클래스를 설계함을 통해 전체적인 시스템의 추상적 표현을 정의하는 것을 말한다.
클래스를 상속시킬 때, 간혹 자식 클래스들이 부모의 기능이나 상태를 똑같이 받기 어려운 경우가 생긴다.
혹은, 부모가 가져야 할 기능중, 부모의 것이라고 딱 잘라 말하기 힘든 경우가 있다.
이러한 경우, 부모 클래스에서 추상적인 개념만 정의하고, 자식 클래스에서 구체화를 하여 사용하는 것을 추상화라고 한다.
이에대한 내용은 10. 추상 클래스에서 자세히 다루었다.
마지막으로는 '다형성'이다.
다형성이란, 부모 클래스의 함수를 자식 클래스에서 재정의하여 자식 클래스의 다른 반응을 구현하는 것이다.
어려운 말을 조금 쉽게 바꾸어보자면, 부모 클래스의 성질을 자식 클래스가 주어진 상황에 따라 여러가지 형태로 바꿀 수 있게 됨을 말한다.
클래스를 상속하다보면, 당연히 하나의 경우를 상정하고 만들어진 부모 클래스의 메서드를 모든 자식이, 모든 상황에서 활용하기 어려운 경우가 생기기 마련이다.
이를 위해, 부모의 성질을 자식이 상황에 따라 확장 내지 변화 시킬 수 있는 성질이 바로 다형성이다.
이에 대한 자세한 내용 역시 9. 가상함수와 오버라이드를 통해 다루었다.
위의 내용들은, 각 특징에 해당하는 문법을 통해 한 번 알아 본 것이다.
캡슐화는 클래스를 다룰 때, 상속은 클래스 상속을 다룰 때 알아보았고, 추상화와 다형성은 아직 배우지 않은 문법을 사용하기 때문에, 해당 문법을 따로 다루면서 설명하겠다.
객체지향의 4대 특징이라 불리우는 특징 외에도 알아두어야 할 것이 있다.
클래스를 만들고, 이를 통한 상호작용으로 시스템을 구현할 때 지켜야 할, 객체지향의 5대 원리가 있다.
이는 SOLID 원칙이라도 불리우는데, 하나씩 살펴보면,
먼저, 단일 책임의 원칙이다. (SRP : Single Responsibility Principle)
단일 책임의 원칙이란, 하나의 객체에게는 하나의 책임만을 부여할 것을 의미한다.
하나의 객체를 통해 여러 기능들이 수행된다면, 앞서 캡슐화에 대해서 말한 것 처럼 객체 각각의 의존도가 높아져 서로 뒤섞일 위험이 크며, 자립성이 낮아져 객체지향 프로그래밍이라고 말하기 힘든 상황이 생긴다.
따라서 객체를 만들 때 마다, 해당 객체의 역할을 뚜렷하게 정하는 것이 중요하며, 그 객체가 역할을 수행할 때 주어진 역할 만을 수행할 수 있도록 설계하는 것이 중요하다.
다음으로, 개방 폐쇄의 원칙이다. (OCP : Open Close Principle)
개방 폐쇄의 원칙이란, 객체의 변경에 있어서는 폐쇄적이어야 하고, 확장에 있어서는 개방적일 것을 말한다.
우선 객체의 변경에 있어서는 폐쇄적이어야 하는 부분은, 우리가 클래스를 다루면서 멤버변수에 접근하는 것을 접근자와 설정자를 통해 철저히 정보를 보안했던 것을 떠올리면 될 것이다.
그렇다면 확장에 있어서 개방적인 것은 어떤 것일까?
여기서 예를 들어보자. 우리는 단팥 호빵이라는 객체를 설정했다.
단팥 호빵에는 재료도 있고, 가격도있고, 파는 곳에 대한 정보도 있다.
따라서 우리는 맨 첫번째 단일 책임의 원칙에 따라 호빵이라는 객체를 서로 철저히 분리시켜 아래의 표와 같은 형태로 정리했다.
| 단팥 호빵 | ||
| ┌─ ─ ─ ─ ─ ─ ─ ─ ─ | ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ─ ─ ─ ─ ─ ─ ─ ─ ── ┐ |
| 재료 | 가격 | 판매처 |
| 속재료 : 단팥, 빵재료 : 밀가루... | 1개에 1000원, 5개 3000원 ..... | 편의점, 슈퍼마켓, 인터넷 쇼핑몰.... |
이렇게 단팥 호빵을 만들었는데, 단팥 호빵 외에 여러 호빵들이 우후죽순처럼 생겨났다.
단팥 호빵, 통팥 호빵, 야채호빵, 피자호빵, 오이호빵까지...
그렇다면 우리는 이렇게 만든 호빵객체를 또 개별적으로 만들어야 할 것이다.
따라서 우리는 이전에 배웠던 클래스 상속을 통해, 호빵의 종류가 확장됨에 따라 그에 맞게 용이하게 확장될 수 있도록
클래스를 정의해야 하는 것이다.
클래스로 선언한 개념이 확장됨에 유연하게 대처할 수 있어야 하되, 내부의 정보는 철저한 보안을 유지시키는 것이라고 할 수 있다.
이를 위해 프로퍼티라는 개념과, 추상 클래스, 가상 함수 등, 클래스에 등장하는 개념들을 다루었다.
셋째로는 리스코프 치환원칙이다. (LSP : Liskov Substitution Principle)
리스코프 치환원칙이란, 부모에게 상속받은 자식 클래스는 언제든 상속받은 부모의 역할을 대신할 수 있어야 한다는 것이다.
기본적으로 자식 클래스는, 부모의 모든 특징을 물려받는다. 부모 클래스를 기본 틀로 삼고, 각자에게 부여된 책임이나 상황별 기능을 세분화 하여 파생되는 것이 자식 클래스이다.
이렇게 파생된 모든 자식클래스는 공통적으로 부모 클래스의 기능을 물려받았기 때문에, 부모의 기능을 모두 수행할 수 있어야 한다는 것이다.
이것이 불가능 하다면, 개별 객체로서의 의미가 더 커져 상속하고, 상속받는 의미가 감쇠되기 때문에, 이에 유의해야한다.
I에 해당하는 원칙은 인터페이스 분리원칙이다. (ISP : Interface Segregation Principle)
11. 인터페이스 에서 다루었던 그 인터페이스가 맞다.
인터페이스 분리원칙은, 인터페이스로 개념을 추상화할 때, 기능별로 충분한 세분화를 거쳐,
인터페이스를 상속받는 객체가 인터페이스 내에 있는 기능 중 사용하지 않는 기능이 없도록 하는 것이다.
인터페이스의 세분화가 제대로 이루어지지 않는다면, 필요 없는 기능이 있는데도 해당 인터페이스를 받는 경우가 생긴다.
따라서, 철저히 세분화하여 꼭 필요한 기능만을 상속받는 것이 중요하다.
마지막으로, 의존성 역전원칙이다.(DIP : Dependency Inversion Principle)
의존성 역전원칙이란, 하위 개념보단 상위 개념에 의존해야한다는 것이다.
객체 지향을 통해 어떠한 프로그램을 구현하는데에 있어, 다양한 객체들을 거쳐 프로세스가 진행된다.
예를들면, 은행에 돈을 찾으러 갈 때를 떠올려보자.
우리는 은행에 돈을 찾기 위해 은행을 가서, 창구에 접수하고, 창구에서 은행업무를 진행하는 은행원을 통해 돈을 받는다.
우리는 돈을 찾는 것이 느려져도, 돈을 찾는 기능을 온전히 수행하지 못하도록 설계한 은행이나, 접수가 서툰 은행원에게 빠른 업무진행을 요구해야하지, 금고 속에 꼭꼭 숨어 나오지 않는 돈에게 빨리 나오도록 요구해선 안된다는 것이다.
많은 프로세스를 거쳐 끝내 얻게되는 결과물에게 의존하기 보단, 결과물에 도달하기위해 거쳐가는 프로세스에게 의존하는 것이다.
이렇게 되지 않는다면, 객체간의 상호작용의 의존도가 굉장히 높아지게 된다.
앞서 들었던 은행의 예를 다시 들어보자.
우리가 은행의 금고에 돈을 넣었다고 생각해보자.
우리가 은행 금고의 비밀번호를 알고 있는 대신, 업무를 보조하는 은행원의 존재가 전부 사라졌다고 가정한다면,
우리는 금고의 비밀번호를 잊는 순간 영영 돈을 꺼내지 못하게 된다.
하지만 현실에서는 그렇지 않다.
우리가 설정한 계좌의 비밀번호를 잊어도, 해당 비밀번호를 설정하는 업무를 맡은 은행원도 있고, 비밀번호를 설정했을 당시 내가 입력한 정보를 대조하며 비밀번호를 새롭게 변경하도록 도와주는 은행원들도 있다.
우리가 너무 급한 나머지, '은행원들은 없고 그냥 나 혼자 금고 비밀번호를 알고 바로바로 뽑고싶다.' 라고 생각한다면
자칫 큰 사고로 이어질 수 있다.
매개를 통해 객체간의 상호작용을 이루며, 최대한 관계의 의존도를 낮추고 관계의 긴장을 느슨하게 유지하는 것이 의존성 역전원칙의 핵심이다.
객체지향의 4가지 특징도, SOLID원칙도 새롭게 배우는 개념이지만, 우리가 이미 클래스의 상속과 객체지향 프로그래밍을 위해 필요한 요소들을 공부했기 때문에 이해하는 것이 어렵지만은 않았다.
다만, 앞으로의 객체지향 프로그래밍에 있어서 알아두어야 할 것들, 지켜야할 것들을 알게 되었으니, 이를 지키지 않음으로써 발생하는 문제들을 스스로 예방할 수 있게 되었다.
'C# 일기' 카테고리의 다른 글
| 14. Array 클래스 (0) | 2024.03.08 |
|---|---|
| 13. String과 StringBuilder (2) | 2024.03.08 |
| 11. 인터페이스 (Interface) (2) | 2024.03.06 |
| 10. 추상 클래스(Abstract Class) (2) | 2024.03.06 |
| 9. 가상 함수와 오버라이드 (Virtual & Override) (0) | 2024.03.06 |