[Java] 객체지향에 대한 이해

지난 포스트에서는 객체지향을 이해하는 데에 필요한 메모리 영역에 대해 알아보았다. 이번에는, 이를 바탕으로 객체지향에 대한 이해의 첫걸음인 객체객체지향의 4대 특성에 대해 정리해보았다.

객체

객체라는 것을 정의를 통해 이해하기는 어려운 것 같다. Wikipedia의 정의를 참고하면,

컴퓨터 과학에서 객체 또는 오브젝트(object)는 저장공간에서 할당되어 값을 가지거나 식별자에 의해 참조되는 공간을 의미하며, 변수, 자료 구조, 함수 또는 메소드가 될 수 있다. 프로그래밍 언어는 변수를 이용해 객체에 접근하므로 객체와 변수라는 용어는 종종 함께 사용된다. 그러나 메모리가 할당되기 전까지 객체는 존재하지 않는다. 객체지향 프로그래밍에서 객체는 클래스의 인스턴스이다. 클래스 객체는 자료와 그 자료를 다루는 명령의 조합을 포함하여 객체가 메시지를 받고 자료를 처리하며 메시지를 다른 객체로 보낼 수 있도록 한다.

즉, 클래스를 바탕으로 누군가 인스턴스를 생성했을 때 그 인스턴스를 객체라고 한다.

객체지향의 4대 특성

클래스와 객체(인스턴스)의 개념은 ‘붕어빵틀 : 붕어빵’ 이라기 보다는, ‘강아지 : 초롱이’같은 느낌이다. 클래스는 분류에 대한 개념이고, 그 분류에 속하는 것 중 실체하는 것 하나가 객체이다. 이를 바탕으로 객체지향의 4대 특성을 이해해보자.

1. 캡슐화, Encapsulation

객체지향에서 캡슐은 ‘1) 객체의 속성과 행위를 하나(클래스)로 묶고, 2)구현 내용의 일부는 외부로부터 노출되지 않도록 한다’는 측면을 뜻한다. ‘1)’번 속성은 밑에서 설명할 상속, 추상화, 다형성 전체와도 관련이 있는 특성이고 여기서는 ‘2)’번 측면에 대해서 집중하겠다. 2)에 다르면 캡슐화는 클래스 내부에 구현된 부분를 외부로부터 감추는=은닉하는 것이다. 이렇게 객체의 내부로직을 감추면 접근할 수 있는 부분이 제한되고 다른 클래스와의 결합도가 낮아지는 장점이 있다. 결합도가 낮다는 것은, 코드의 변경에 유연하게 대처할 수 있다는 것을 뜻한다. 감추는 정도는 ‘접근제어자’public, protected, private을 통해 구현한다. 간단히 요약하면 각각은 아래와 같은 경우를 의미하는 접근제어자이다.

public: 모두 접근 가능
protected: 상속된 클래스나 같은 패키지 내의 클래스 접근 가능
[default]: 같은 패키지 내의 클래스에서 접근 가능
private: 자기 자신에서만 접근 가능

(위와 같다고는 하는데, 캡슐화에 대한 부분은 직접 코드를 구현할 때에 많이 고민이 되는 부분이고 아직 이해가 잘 가지 않은 것 같다. 언젠가 다시 정리가 필요해 보인다.)

2. 상속, Inheritance Extension

객체지향에서 상속은 하위 클래스가 상위 클래스의 속성을 갖는 것이다. ‘부모-자식’ 이라기 보다는 ‘동물-인간’과 같이 ‘상위분류-하위분류’와 같은 개념에 더 가깝다. 인간은 동물이라는 클래스를 상속한 것이라고 할 수 있다. 즉, 상속은 상위 클래스의 특성을 재사용/확장한 것이다.
한면, Java에서는 다중상속을 지원하지 않는 대신 Interface라는 것을 도입하였다. 특정 Interface를 implements했다는 것은, 그 Interface가 갖는 속성이 가능하도록 구현했다는 것이다. 예를들면 Serializable, Cloneable, Comparable, Runnable과 같은 인터페이스가 있다. 인터페이스는 be able to로 새로운 특성을 강제하도록 구현한다. 예를들어 public class A extends B implements C라는 것은 ‘A 클래스는 상위클래스인 B의 특성을 모두 가지고 있으면서, C가 갖고있는 특성을 추가적으로 갖고있다’ 정도의 개념으로 이해할 수 있다.
한편, 하위클래스의 인스턴스가 생길 때 T메모리의 힙영역에는 하위클래스 객체만 생기는 것이 아니라 상위클래스 인스턴스도 생긴다. 이것이 중요한 경우는 아래와 같은 경우이다.

Animal pingu = new Penguin();
Penguin pororo = new Penguin();

위와 같이 pingu와 pororo를 생성했을 때, pingu, pororo 인스턴스를 만들 때에 각각 Penguin과 그 상위 클래스인 Animal의 인스턴스 모두 생성이 된다. 그런데, pingu의 자료형이 Penguin이 아닌 Animal이기 때문에 pingu는 Penguin 인스턴스가 갖고 있는 변수나 메소드는 사용하지 못하게 된다.

3. 추상화, Abstraction

추상화는 공통적인 부분을 추출해내는 것이다. 사람1, 사람2, … 가 있을 때 이들의 공통점을 찾아 ‘사람’이라는 클래스를 만드는 것이다. 즉, Class를 생성할 때는 이것의 실체(클래스의 인스턴스=객체)를 생각하고, 그 실체들의 공통점을 잘 추출(=모델링)하여 Class로 만들어야한다. 추상화 = 모델링이다. 그리고 자바에서는 객체지향 중 추상화를 ‘class’로 지원하고 있다.

4. 다형성, Polymorphism

다형성은 오버라이딩, 오버로딩을 통해 하위 클래스가 상위 클래스의 함수를 변형할 수 있도록 한다. 오버라이딩은 상위클래스의 메서드를 완전히 새로 정의하는 것이고, 오버로딩은 상위클래스의 메서드를 인자만 다르게 하여 여러 개 정의하는 것이다. 예를 들어, 메서드 명은 하나인데 인자 값을 받는 경우도 있고 안 받는 경우도 있는 경우에 오버로딩을 통해 동일함수를 여러 개 구현할 수 있다.
한편, 하위클래스가 상위클래스의 메서드를 오버라이딩 했을 경우, T메모리에서 상위 클래스 인스턴스의 메소드는 하위 클래스가 오버라이딩한 메소드로 덮어 씌워진다. 그래서 아래 코드와 같이 상위클래스로 정의한 객체가 하위클래스의 인스턴스를 참조할 때에 오버라이딩된 메서드를 사용할 수 있게된다.

Animal[] animals = new Animal[2];
animals[0] = new Cat();
animals[1] = new Rabbit();
for (Animal animal: animals) animal.cry();

다형성을 통해 구현의 유연함, 사용 편의성을 제공해주는 것이라고 이해하자.

여기까지 객체지향의 기본이 되는 4대 특성에 대하여 정리해보았다. 다음 SOLID 설계 원칙을 정리하며, 객체지향에 대한 어렴풋한 이해를 마무리할 예정이다.