티스토리 뷰

IT/개발

객체지향 이란

K.Nero 2018. 6. 10. 13:36

객체지향은 개발을 하기 위한 강력한 개념이다. 하지만 객체지향을 익히고 사용하기 까지는 많은 노력과 시간이 필요하다. 나도 그랬지만 적당히 중복을 제거하고 공통을 묶어서 개발하는 것은 객체지향를 조금 흉내내려고 하는 것에 불과하다고 생각한다. 객체지향을 구현하기 위해서는 지식과 생각의 전환이 필요하고 이 글이 조금이나마 도움이 됐으면 한다.

객체지향의 중요한 개념

객체란 실세계를 반영한다. 어디서나 나오는 말이지만 객체지향의 핵심이 실세계의 사물을 사용하는 방법을 개발에도 똑같이 적용하여 사람에게 더 친숙하게 하기 위함이라고 생각한다면 정말 중요한 말이다. 사실 이것만으로도 대부분의 객체지향 이론들이 설명된다. 실세계에서는 사물간의 decoupling 이 당연하게 이루어지고 있고 encapsulation 또한 자연스럽게 사용되고 있다는 것을 알 수 있다. 예를 들어 A회사의 부품을 B회사가 사용하여 제품을 만들었다고 하자. 만약 A회사의 사용방법은 그대로지만 실제 행동이 변경되거나 새로운 버전의 사용 방법이 변경되는 순간 B회사에게는 재앙이 될 것이다. 재미있는 사실은 이 재앙이 코드에서도 동일하게 발생한다는 점이다.

객체지향이 추구하는 decoupling 을 실천하기 위한 핵심은 인터페이스에 있다. 실세계의 모든 사물(객체)은 서로에게 사용방법을 제공하고 제공되는 사용방법을 통해서만 서로를 사용하게 된다. 여기서의 사용방법이 곧 객체지향에서의 인터페이스이고 객체지향에서도 모든 객체들은 다른 객체들에게 명확한 사용방법을 제공해야 하는 이유가 여기에 있다. 인터페이스는 객체를 사용하기 위한 표준방법과 같다. 동일한 인터페이스로 제공되는 객체들은 인터페이스가 명시하는 기능을 제공해야하며 모든 객체는 구현이 다르더라도 똑같은 기능을 수행해야 한다.

라디오의 재생버튼, 리모콘의 버튼들을 떠올려 보면 시대가 변해서 새로운 리모콘이 나오더라도 우리는 배우는 과정없이 바로 사용할 수 있는 것은 정해진 인터페이스 덕분이다. 그리고 개발에서도 우리는 이런 리모콘을 쥐고있다. 어떤 DB 인지 상관없이 같은 방법으로 사용할 수 있도록 해주는 JDBC 가 바로 그것이다. 내부 구현은 모두 다르고 원격지의 DB 가 어느 회사에서 만든 DB 인지 모르더라도 JDBC 라는 리모콘을 통해 우리는 쉽게 DB 를 사용할 수 있다.

SOLID

객체지향의 중요한 기본원칙을 말하는 것으로 열심히 외웠던 것 중 하나이다. 처음 나도 이 5가지 원칙을 외울 때는 뜻이 와닿지 않았지만 객체지향의 개념을 이해하고 나서는 너무 당연하다는 생각이 들었다. 이 원칙들을 실세계의 객체라는 관점에서 하나씩 살펴보자.

Single responsibility principle : 단일 책임의 원칙이라고 하며 객체 하나는 오직 하나의 책임만 가져야 한다는 의미이다. 예를 들면 사람안의 심장이라는 객체가 있다. 심장은 피를 정화하고 온몸으로 피를 공급하기만 하면된다. 해독을 하거나 산소를 공급하려고 하면 안된다. 클래스가 하나의 책임이 아닌 여러 책임을 갖게 된다면 그만큼 변경사유가 늘어나고 견고함이 떨어지게 된다.

Open/closed principle : 확장에는 열려있고 수정에는 닫혀있어야 한다는 것으로 새로운 기능을 넣기 쉬워야 하며 수정이 되더라도 다른 곳에 영향을 주어서는 안된다는 의미한다. 이는 실세계의 사물의 구성도 동일하게 이루어 져있다는 것을 알 수 있다. 확장에 용의하기 위해서는 부품이 제공하기로 한 약속한 방법(인터페이스)을 유지하면서 새로운 기능을 추가해야 한다. 이렇게 되면 사용하는 쪽에서는 기존 기능에는 변경이 없기 때문에 변경되는 부품을 신경쓸 필요가 없게 된다.

Liskov substitution principle : 상위 타입의 인스턴스를 하위 타입으로 변경가능해야 한다는 의미이다. 이를 지키기 위해서는 상위 타임의 행동을 하위 타입이 동일하게 수행해야 한다는 것을 의미한다. 이것은 실세계에서 많은 자동차들이 정해진 같은 행동을 하는 것을 본다면 이해하기 쉬울 것이다. 조금 거리는 있지만 우리가 새로운 자동차를 타더라도 메뉴얼을 보지 않아고 운전을 할 수 있는 것과 비슷하다. 상속을 받은 하위 객체는 상위 객체가 하는 행동을 똑같이 제공하여 사용하는 쪽에서 수정없이 필요에 따라 변경할 수 있어야 한다.

Interface segregation principle : 인터페이스를 작게 분리해야 한다는 것으로 클라이언트가 사용하지 않는 인터페이스에 의존해서는 안된다는 의미이다. 자신이 제공해야 할 기능인지 명확하게 판단하여 자신과 거리가 멀거나 기능이 너무 다양해 진다면 별도의 인터페이스로 제공하는 것을 고려해야 한다. 잘 분리가 된다면 기능을 제공하는 객체와 사용하는 객체 모두 변경되어야 할 이유가 줄어 들고 자신의 역할이 명확해 지는 것을 알 수 있다.

Dependency inversion principle : 구체화에 의존해야 하는 것이 아니라 추상화에 의존해야 한다는 의미이다. 추상화라는 단어는 어렵게 들릴 수 있지만 사람이 더 인지하기 쉽게 표현하는 것을 추상화라고 생각해 볼 수 있다. 개발을 할 때 추상화를 한다는 것은 세부적인 기능보다 이 기능들이 수행하는 요점을 노출시켜 주는 것이라고 하겠다. (추상화를 쉽게 설명하기가 쉽지 않다. 더 좋은 설명이 있거나 제가 잘못 생각하고 있으면 댓글달아주세요 :) )  추상화된 인터페이스는 작은 행동들을 포함할 수 있다. 구현에 의존하게 되면 하나의 동작을 위해 필요한 과정들을 모두 알고 실행해야 하지만 그 과정들을 하나의 추상화된 동작으로 표현한다면 구현보다는 기능에 의존적으로 사용할 수 있다. 그리고 내부 구현의 변경에 영향을 받지 않는다.

SOLID 만 봤을 때도 객체지향에서의 인터페이스가 얼마나 중요한 역할인지 알 수 있다. 물론 여기서 인터페이스는 java의 interface 가 아닌 사용 방법이라는 것이 중요하다. 물론 java 의 interface 가 이 용도를 위해서 있는 것은 맞다. 그래서 이를 어떻게 활용하는지 디자인 패턴을 통해서 확인해 보자.

Design Pattern

Strategy Pattern

행동패턴에 속하는 이 패턴은 런타임에서 입력되는 데이터에 따라 행동을 정의할 수 있게 해준다. 상황에 맞는 알고리즘을 선택할 수 있게 해주며 알고리즘 로직을 하나의 객체에 고립시켜준다.(encapsulation) 보통 동물들의 fly, cry 와 같은 행동들을 인터페이스로 분리하고 각 동물에 맞게 나누는 예제들이 많이 나온다.
좀 더 친숙한 예로 드릴을 들자면 드릴은 몸체는 하나이고 여러 개의 비트(드릴의 탈장착이 가능한 앞부분)를 갖고있다. 비트를 회전시켜  사용하는 것은 동일하지만 상황에 따라서 언제든지 비트를 교체할 수 있다. 돌리는 행위 자체를 알고리즘을 실행시키는 메소드(예 execute( ))로 생각한다면 어떤 비트를 연결하는 가는 어떤 알고리즘을 사용하는 것인가와 같다. 그리고 비트의 생김새가 실제 구현 클래스라면 비트를 돌려서 사용해야 한다는 것은 외부에서 비트를 사용하는 방법(인터페이스)이라고 할 수 있다.
비트와 드릴의 관계처럼 중요하고 자주 변경돼야 하는 행동을 전략패턴을 통해 알고리즘을 사용하는 객체와 decoupling 할 수 있다.

Composite Pattern

이 패턴은 하나 이거나 그룹 이거나  같은 사용방법으로 접근할 수 있도록 해준다. 이는 곧 트리 구조에 많이 사용될 수 있다. 프로그램을 사용하다 보면 트리구조인 메뉴를 볼 수 있고 상위 메뉴는 하위 메뉴를 갖고 있다. 메뉴는 모두 같은 클릭이라는 인터페이스를 통해 제공이 되고 상위메뉴는 클릭하면 하위 메뉴를 보여주며 하위메뉴가 없는 경우 특정 기능을 실행해 준다. 
나는 프레임워크를 개발할 때 이 패턴을 리소스를 로딩하는 부분에 적용했던 적이 있다. 프레임워크의 필요한 리소스를 분류해 나가며 각 부분을 로딩하는 객체를 트리구조로 구성했다. 이럴 경우 프레임워크는 하나의 리소스를 로딩하듯이 호출할 수 있고 그 안의 여러 객체들은 각 리소스를 로딩하는 하위 객체들을 연쇄적으로 호출하게 된다. 이때 중요한 점은 각 로딩하는 객체들이 다른 리소스에 의존하지 않도록 해야 한다.

Adapter Pattern

실생활에서는 연관은 있지만 인터페이스가 완전히 다른 문건들이 굉장히 많다. 그리고 이를 연결하기 위한 adapter 들도 많이 있다. 인터넷에서는 카드 리더기 예도 굉장히 많이나온다. 
이 패턴은 개발을 할 때 어떤 불편한 라이브러리를 쓰기 편하게 wrapping 해서 사용하는 것과는 다르다. 이 패턴은 어떤 인터페이스로 제공하는 기능을 사용하고 있는 객체가 다른 인터페이스의 새로운 것을 사용하려고 할 때 인터페이스가 변경되면 사용하는 객체의 많은 부분이 변경되기 때문에 이를 막고자할 때 사용한다. 새로운 기능을 기존의 인터페이스로 변경하여 제공하게 되면 사용하는 객체는 변경없이 새로운 객체를 사용할 수 있다.


디자인 패턴들은 모두 interface 를 중요하게 다르고 이를 중심으로 설명한다. interface 를 통해 기능을 제공하고 이를 사용하게 되면 각 객체들은 실생활에서와 같이 내부의 구조를 모르고 사용할 수 있기 때문이다. 

여기 까지는 어렵지 않은 부분일 수 있고 쉽다고 생각할 수도 있을 것이다. 하지만 나는 여기까지 알게 되는 시간도 길었다. 그렇다면 이 정도를 알면 모두가 OOP를 잘 할수 있게 되는 것일까? 전혀 그렇지 않다. 이제부터 시작이다. OOP 에서 개발을 할 때 중요한 것은 어디까지를 객체로 볼 것이냐 이다. 객체를 너무 작게 생각한다면 오히려 구현이 복잡해 질 수 있고 커진다면 여러 기능으로 인해 변경이 잦아 질 것이다. 단일책임원칙이 말하는 하나의 책임은 정의하기 나름이고 현실에서도 같다. 자동차도 객체이고 엔진도 객체이다. 바퀴도 객체이고 작은 부품도 객체로 볼 수 있다. 객체를 가장 효율적인 객체인 적당한 책임을 부여하고 사용이 쉽고 변경이 적게하기 위해서는 역시 많은 경험이 필요하다.


'IT > 개발' 카테고리의 다른 글

Spring @Transactional 사용하기  (0) 2018.10.18
Spring + JWT  (0) 2018.08.31
레거시에 Solr 적용하기  (0) 2018.05.22
Redis HA  (0) 2018.04.05
Github 에 개인 maven repository 생성하기  (0) 2018.02.14
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함