💻 자바와 객체지향

자바 객체지향의 원리 책을 읽고 내용을 정리하는 글입니다.

객체 지향은 인간 지향이다

기계 중심의 기계어에서 조금 더 인간을 편하게 하는 어셈블리어, 기계에 맞추어 기계어를 변화할 필요 없이 컴파일러의 변화로 하나의 언어로 코드를 작성할 수 있었던 C 언어 그리고 OS 플랫폼에 따라 다르게 해석 되었던 문제를 해결한 Java 까지의 발전해 온 과정은 모두 로우 레벨의 기계가 아닌 하이 레벨의 인간을 배려하기 위한 과정이다.

특히 포인터의 개념은 기계 수준으로 눈높이를 낮추지 않으면 이해하기 매우 힘든 부분이고 “왜 우리가 기계 종속적인 개발을 해야 하는가?” 의 의문에서 시작하여 우리가 눈으로 보고, 느끼고, 생활하는 현실 세계처럼 프로그래밍을 하는 방법을 생각해낸 것이 바로 객체 지향의 개념이다.

왜 우리가 느끼는 것과 같은 현실 세계처럼 프로그래밍 하는 것이 객체 지향의 개념일까?

객체 지향을 이해하기 위해 큰 그림을 생각해보면

  • 세상에 존재하는 모든 것은 사물, 즉 객체이다.
  • 각각의 사물은 고유하다.
  • 사물은 속성을 갖는다.
  • 사물은 행위를 한다.

또한, 사물을 하나하나 이해하기보다는 사물을 분류해서 이해하는 것이 인간의 인지법이다.

예를 들어 김종민, 한효주, 김연아라고 하는 존재는 사람이라는 분류에 속한다. 그리고 사람이라는 분류 안의 객체들은 나이, 몸무게, 키 등의 속성(property)과 먹다, 자다, 울다 등의 행위(method)를 갖고있다.

객체 지향 이전에는 속성과 메서드를 객체라는 단위로 묶지 않고 속성과 메서드 들을 따로 분리된 형태로 프로그램을 작성했었는데, 객체 지향에서는 우리가 주변에서 실제 사물을 인지 및 사고하는 방식대로 객체 단위의 프로그래밍이 가능하다.

따라서 객체 지향은 인간의 인지 및 사고 방식까지 프로그래밍에 접목하는 인간(개발자) 지향을 실천하고 있다.

객체 지향의 4대 특성 - 캡! 상추다

객체 지향은 다음과 같은 4대 특성인 캡상추다를 갖고있다.

  • 캡 - 캡슐화(Encapsulation): 정보 은닉
  • 상 - 상속: 재사용
  • 추 - 추상화(Abstraction): 모델링
  • 다 - 다형성(Polymorphism): 사용 편의

클래스 VS 객체 == 붕어빵틀 VS 붕어빵 ???

흔히들 클래스와 객체의 관계를 붕어빵틀과 붕어빵의 관계로 가리킨다. 하지만 이것은 잘못되었다. 자바 코드로 직접 살펴보자

자바 코드를 한글로 다음과 같이 나타내보겠다.

클래스 객체명 = new 클래스();

이것이 붕어빵틀과 붕어빵의 관계라고 한다면 다음과 같다고 해야한다.

붕어빵틀 붕어빵 = new 붕어빵틀()

말이 안되지 않는 가?

진도를 더 나아가 다음 문제들의 답을 생각해보자

  • 사람은 클래스인가? 객체인가?
  • 김연아는 클래스인가? 객체인가?
  • 뽀로로는 클래스인가? 객체인가?
  • 펭귄는 클래스인가? 객체인가?

위에 문제에서 클래스와 객체를 구분하는 간단한 방법은 나이를 물어 보는 것이라고 할 수 있다.

  • 사람의 나이는 몇 살인가?
  • 김연아의 나이는 몇 살인가?
  • 뽀로로의 나이는 몇 살인가?
  • 펭귄의 나이는 몇 살인가?

사람의 나이? 펭귄의 나이? 말이 안되는 것을 알 수 있을 것이다. 그래서 사람과 펭귄은 클래스이다.

김연아의 나이? 뽀로로의 나이? 관심이 없을지라도 검색 엔진을 찾아보면 바로 답이 나온다. 그래서 이 둘은 객체이다.

클래스와 객체의 관계를 알았으니 이제 객체 지향의 4대 특성을 하나하나 상세히 살펴보자.

추상화: 모델링

추상화의 일반인이 생각하는 의미와 전산 용어로서의 의미는 다르지 않다.

추상화의 대가라고 한다면 피카소를 떠올릴 수 있을 것이다. 피카소 작품의 특징은 사실적인 것을 그린게 아니라 마음 속에 느껴지는 그 사람의 특징을 극대화해서 추상화로 그렸다는 것이다. 여기서 추상의 사전적 의미를 살펴볼 필요가 있다.

` 추상: 여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용`

객체 지향의 추상화와 그림으로서의 추상화, 그리고 사전적 의미로서의 추상화가 같은 의미인 것이다. 우리는 사전적 의미에서 공통 특성 / 공통 속성 추출에 주목할 필요가 있다.

추상화는 모델링이다.

추상화란 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것이라고 정리할 수 있다. 이 개념을 머릿속에 간직해 두고 객체 지향의 추상화를 생각해보자.

객체 지향의 4대 특성은 클래스를 통해 구현된다. 또는 객체라고 할 수도 있다.

객체와 클래스의 정의를 알아보면 다음과 같다.

객체: 세상에 존재하는 유일무이한 사물

클래스: 분류, 집합, 같은 속성과 기능을 가진 객체를 총칭하는 개념

즉, 세상에 존재하는 유일무이한 객체를 특성(속성 + 기능)에 따라 분류해 보니 객체를 통칭할 수 있는 집합적 개념, 즉 클래스(분류)가 나오게된다.

이게 객체 지향의 추상화와 무슨 관련이 있을까?

사람이라는 클래스를 설계한다고 해볼때, 사람 클래스를 만들기 위해 주변에서 보이는 실체들, 즉 사람 객체들을 관찰해서 사람 객체들이 가진 공통된 특성을 찾게 된다. 시력, 몸무게, 혈액형, 키, 나이 등등 명사로 표현되는 특성을 속성이라고 한다. 속성은 값을 가질 수 있다.

먹다, 자다, 일하다, 침 뱉다, 운전하다 등등 동사로 표현되는 특성을 기능/행위라고 한다. 이러한 기능/행위는 수행 절차 또는 로직이라고 하는 것을 갖게된다. 이를 객체 지향에서 메서드라고 한다.

사람의 모든 특성을 나열하게되면 수십만 개를 넘길 것이다. 사람 클래스가 사람 객체들의 모든 특성을 나열 할 수 있을 까? 또는 그럴 필요가 있을 까?

여기서 또 하나의 개념이 나온다. 바로 애플리케이션의 경계이다. 때로는 애플리케이션 경계를 컨텍스트(Context)라고 부르기도 한다.

애플리케이션 경계를 알기 위해서는 단순한 질문 하나만 던져 보면 된다.

내가 창조하려는 세상은 어떤 세상인가?

조금 더 프로그래밍적으로 질문을 바꾼다면 다음과 같이 바꿀 수 있을 것이다.

내가 만들고자 하는 애플리케이션은 어디에서 사용될 것인가?

만약 병원 애플리케이션을 만들고 있다면 사람은 환자를 의미하는 좀 더 구체적인 이름으로 바꿀 수 있을 것이고 클래스 설계도 달라질 것이다. 결국 애플리케이션 경계에 따라서 필요한 특성들이 보일 것이며 이는 추상화에 뜻에 따라 구체적인 내용을 관심 영역에 대한 특성만을 가지고 재조합하는 과정을 거치는 것이고 추상화는 모델링이다. 라는 말에 동의할 수 있을 것이다.

상속: 재사용 + 확장

상속은 언어의 오해가 있는데 영어단어를 그대로 옮기면서 문제가 생겼다. 객체 지향의 상속은 할아버지와 아버지, 삼촌과의 관계가 아니라 동물과 포유류, 조류와의 관계이다. 동물이라고 하는 것 중 분류를 조금 더 세분화해서 포유류와 조류가 나오고 포유류를 조금 더 분류해서 고래, 박쥐가 나오는 것이다.

객체 지향에서의 상속은 상위 클래스의 특성을 하위 클래스에서 상속(특성 상속)하고 거기에 더해 필요한 특성을 추가, 즉 확장해서 사용할 수 있다는 의미이다.

앞으로는 부모 - 자식 클래스 보다는 상위 - 하위 클래스 또는 슈퍼 - 서브 클래스라고 표현하자.

상위 클래스 쪽으로 갈 수록 추상화, 일반화 되었다고 말하며 하위 클래스 쪽으로 갈수록 구체화, 특수화됐다고 말한다.

삭송 관계에서 반드시 만족해야 할 문장이 있다.

  • 하위 클래스는 상위 클래스다.

할아버지와 아버지의 개념으로 아직도 이해하고 있다면 문제가 생긴다.

  • 아버지는 할아버지다?

위와 같은 문제가 생기기 때문이다. 하지만 상위 - 하위 클래스의 관계로 추상화와 구체화의 내용으로 생각을 갖고 있다면 아래와 같이 자연스러운 예로 생각이 들 것이다.

  • 포유류는 동물이다.

자바 언어에서도 상위 - 하위 클래스의 개념으로 본 것을 볼 수 있다. inheritance를 사용하지 않고 extends를 했단 점인데, 자바 언어의 아버지라고 불리는 제임스 고슬링은 객체 지향의 상속을 정확히 이해한 것이다.

상속은 is a 관계를 만족해야 한다 ?

상속에 대한 또 하나의 오해가 있다. 상속은 is a 관계를 만족해야 한다는 말을 들어 본 적이 있을 것이다. 아래의 문장을 보자

펭귄 is a 동물

"펭귄은 한 마리 동물이다." 번역도 되고 논리도 맞는 것 같다. 하지만 천천히 다시 보자. 펭귄은 클래스고 동물도 클래스다. 그러나 "한 마리 동물"은 클래스 일까? 아니다. 객체다. 그렇다면 상위 - 하위 클래스의 관계를 직접 대입해보자. "하위 클래스는 하나의 상위 클래스이다"가 된다. 상위와 하위 클래스는 모두 분류/집단을 의미하는데 하나의 상위 클래스는 하나의 객체이다. 삼단 논법에 의거하여 "하위 클래스는 하나의 객체다" 라는 결론을 도달하면 인간의 논리가 무너져 버린다.

따라서 is a 표현은 맞지않고 더 명확한 표현이 있는데 바로 is a kind of 이다.

  • 펭귄 is a kind of 동물

머리가 개운해지고 논리가 맑아지는 느낌이 들 것이다.

다중 상속과 자바

자바는 다중 상속을 지원하지 않았는데, 왜 뺏는지 다중 상속의 문제점을 살펴보자.

인어는 사람과 물고기를 상속받는다고 해보자. 그렇다면 인어가 수영을 한다면 사람처럼 팔과 다리를 저어 수영해야할까? 아니면 물고기처럼 가슴, 등, 꼬리 지느러미로 헤엄쳐야 할까? 이와 같은 문제를 다중 상속의 다이어몬드 문제라고한다.

결국 다중 상속의 득실 관계에서 실이 더 많았기에 자바와 C#은 과감히 다중 상속을 포기했다. 대신 자바에서는 C++에는 없는 인터페이스를 도입해 다중 상속의 득은 취하고 실은 과감히 버렸다.

상속과 인터페이스

다중 상속을 포기하고 대신 인터페이스를 도입한 자바에서 인터페이스는 어떤 관계를 나타내는 것일까?

다중 상속 대신이니 상속과 같이 is a kind of일까? 그렇게 생각해도 사실 무관하지만 객체 지향이 긴 세월을 거쳐서 정체되고 발전하다 보니 인터페이스는 상속과 다르게 쓰는 것이 유용하다는 결론에 도달하게 됐다.

  • 인터페이스: 구현 클래스 is able to 인터페이스
  • 해석: 구현 클래스는 인터페이스할 수 있다.
  • 예제: 고래는 헤엄칠 수 있다.

인터페이스는 be able to. 즉, "무엇을 할 수 있는" 이라는 표현 형태로 만드는 것이 좋다. 자바 API에서도 이러한 be able to 형식의 인터페이스를 많이 볼 수 있다.

  • Serializable 인터페이스: 직렬화할 수 있는
  • Cloneable 인터페이스: 복제할 수 있는
  • Comparable 인터페이스: 비교할 수 있는
  • Runnable 인터페이스: 실행할 수 있는

여기서 한 가지 퀴즈를 풀어보자.

  • 상위 클래스는 하위 클래스에게 물려줄 특성이 많을수록 좋을까? 적을수록 좋을까?
  • 인터페이스는 구현을 강제할 메서드가 많을수록 좋을까? 적을수록 좋을까?

조금 깊이 생각해 보면 상위 클래스는 물려줄 특성이 풍성할수록 좋고, 인터페이스는 구현을 강제할 메서드의 개수가 적을수록 좋다는 결론에 도달할 수 있다.

자세한 것은 이후에 다루겠지만 객체지향 5대 원칙중 리스코프 치환원칙(LSP) 에서 물려줄 특성이 풍성한 것이 좋다는 것을 알 수 있으며 인터페이스 분할 원칙(ISP)에서 강제할 메서드의 개수가 적을 수록 좋다는 것을 알 수 있다.

다형성: 사용편의성

객체 지향에서 다형성이라고 하면 오버라이딩(overriding)과 오버로딩(overloading)이라고 할 수 있다. 물론 상위 클래스와 하위 클래스 사이에서도 다형성을 이야기할 수 있고, 인터페이스와 그것의 구현 클래스 사이에서도 다형성을 이야기할 수 있지만 가장 기본은 오버라이딩과 오버로딩이라고 할 수 있다.

두 정수를 더해서 반환하는 함수를 생각해보면 add(int, int) 형식일 것이다. 만약 다형성이 지원되지 않는 언어라면 정수와 실수를 더하는 함수를 만들 때 addIntDouble(int, double)이라는 새로운 함수를 만들어야 할 것이다. 왜냐하면 오버로딩이 지원되지 않기때문이다.

오버라이딩의 경우에도 하위 클래스가 재정의한 메서드를 알아서 호출해 줌으로써 형변환이나 instanceof 연산자를 써서 하위 클래스가 무엇인지 신경 쓰지 않아도 된다. 상위 클래스 타입의 객체 참조 변수에서 하위 클래스가 오버라이딩한 메서드를 자동으로 호출해 줌으로써 깔끔한 코드를 유지할 수 있게 된다.

캡슐화: 정보은닉

자바에서 정보 은닉 이라고 하면 접근 제어자인 private, [default], protected, public이 생각날 것이다. 그리고 접근자 및 설정자 메서드도 머릿속을 스쳐 지나갈 것이다.

이것들을 통해서 자바에서는 클래스에 다양한 접근 제한의 경우를 만들 수 있다. 4가지의 접근 제어자와 static의 경우를 조합하면 총 8가지로 프로퍼티의 경우를 나눌 수 있으며 일반 메소드와 정적 메소드 그리고 다른 패키지에서의 클래스 등에서 겪을 수 있는 접근 제한이 다르다.

⤧  Next post equals(), hashCode() ⤧  Previous post 42서울 오픈스튜디오 프로젝트 개발기 - 4