OOP (Object Oriented Programming) - 객체 지향 프로그래밍은 기존의 절차적/구조적 프로그래밍에서 보다 현실세계를 반영하자는 취지에서부터 시작되었다. 우리가 현실세계에서 인지하는 사물, 곧 객체를 인지하는 방식대로 프로그램을 만들자는 것이다. 그것이 객체 지향이고 Java는 객체 지향을 가장 잘 나타내는 언어들 중 하나로 대표되고 있다.
이러한 객체 지향에는 다음과 같은 4가지 특성이 있다.
상속(Inheritance) : 재사용
추상화(Abstraction) : 모델링
캡슐화(Encapsulation) : 정보 은닉
다형성(Polymorphism) : 사용 편의
클래스 : 객체
클래스와 객체에 대한 이야기를 할 때 가장 많이 볼 수 있는 잘못된 예시가 있다.
바로 클래스를 공장, 객체를 공장에서 생산되는 물건 정도로 설명하는 것이다.
Ex) '자동차 공장'(클래스)에서 '자동차'(객체)를 찍어낸다는 설명
하지만, 이러한 공장 - 사물 과 같은 비유는 잘못되었다.
클래스는 class 라는 영어 의미대로 분류, 종류 정도의 추상적인 개념을 가지고 있어야 한다. 즉, 구체적인 대상이 아닌, 구체적인 대상을 통칭하는 하나의 분류에 대한 개념이라는 것이다. 반면, 객체는 클래스와 반대로 추상적인 개념이 아닌, 유일무이한 하나의 대상, 실체이다.
Ex) '자동차'(클래스)라는 분류의 '[0448]이라는 번호판을 가진 김현수의 차'(객체)
이처럼 클래스는 여러 객체들을 통칭하는 하나의 개념, 분류이고 객체는 고유한 사물, 생물 등으로 생각하면 된다.
- +) 코드상에서 클래스에서 객체를 만들었을 때, 객체라는 표현보다는 클래스의 인스턴스(instance)라는 표현을 쓴다.
상속
상속은 클래스의 속성과 기능을 재사용하고 새로운 것들을 덧붙여 확장하는 것이다.
객체 지향의 가장 중요하고, 강력한 특성은 상속이다.
한국에서 '상속'이라는 단어는 친족간의 어떠한 권리나 의무를 계승, 물려주는 것을 말한다.
물론 상속을 통해 상위 클래스에서 하위 클래스에게 변수와 메소드를 넘겨줄 수 있는 것은 사실이다.
따라서, 여러 책, 강의에서는 상속에 대한 이해를 돕기위해 부모 - 자식의 관계로 설명하는 경우가 많다.
하지만, 상속은 하위 클래스에게 자신의 것들을 물려준다는 개념보다는,
재사용과 확장의 개념으로 접근해야한다.
class Father{
public void fixSomething(){
System.out.println("fixing...");
}
/*
.
.
.
*/
}
class Daughter extends Father{
}
위의 예시 코드에서 Daughter 클래스는 Father 클래스를 상속받고 있다. 따라서, Daughter은 Father에서 정의된 메소드들을 사용할 수 있다. 즉, 상속이 만약 부모 - 자식간의 관계만으로 설명될 수 있다면, 부모를 상속받는 자식은 부모의 모든 행동과 속성을 상속받아 사용할 수 있어야한다. 즉, 할아버지 클래스부터 손주 클래스까지 상속관계가 이어진다면,
손주는 할아버지가 하는 일부터, 아버지가 하는 일까지 모두 할 수 있는 존재가 된다. 이는 논리적으로 맞지 않다.
따라서, 상속은 부모 - 자식간의 관계보다는 재사용과 확장의 개념으로 이해해야 한다.
class Animal{
public void eat(){ }
}
class Birds extends Animal{
public void fly(){ }
}
class Eagle extends Birds{
public void huntingFood(){ }
}
게시글 위쪽에서 클래스와 객체에 대한 이야기에서, 클래스는 '추상적인 분류'라고 표현했었다.
상위 클래스에서 하위 클래스로 갈수록 추상적인 개념에서 보다 구체적으로 개념이 변화하게 되는 것이 상속이다.
위의 예시 코드에서도 최상위 클래스인 Animal에서 Birds, Eagle로 개념이 점차 구체화 되는 것을 볼 수 있다.
이렇게 추상적인 분류를 구체화 시키는 방향으로 상속을 이용하면 자연스럽게 IS-A 관계, 더 정확히는 is a kind of 관계를 만족하게 된다. [ Bird is a kind of Animal / Eagle is a kind of Birds ]
결과적으로. 상속은 하위 클래스에 상위 클래스의 메소드와 변수들을 넘겨주면서 상위 클래스의 코드들을 '재사용'하게 되고 상위 클래스의 코드들에 하위 클래스만의 속성과 기능을 추가하면서 '확장' 하게 된다. 자바에서 상속하는 키워드가 'Inheritance'가 아닌, 'extends' 인 것도 이러한 이유 때문이다.
다음 세 가지를 기억하자.
- 객체 지향의 상속은 상위 클래스의 특성을 '재사용'하는 것이다.
- 객체 지향의 상속은 상위 클래스의 특성을 '확장'하는 것이다.
- 객체 지향의 상속은 is a kind of 의 관계를 만족한다.
추상화
추상화는 구체적인 것을 분해해서 관심영역에 있는 특성만을 가지고 재조합 하는 것이다.
객체는 고유한 사물이고 클래스는 여러 객체들을 총칭하는 개념이다.
클래스는 여러 객체들을 총칭하기 때문에 객체들의 공통된 특성을 지니고 있어야 한다.
이처럼 객체들의 특성을 추출하고 조합하여 클래스를 설계, 모델링 하는 것이 추상화이다.
이 때, 어떠한 특성들을 재조합 할 것인지는 관심영역(애플리케이션 경계)에 따라 다르다.
만약, 은행 어플리케이션을 만들때 사람이라는 클래스를 설계한다면, 시력 / 혈액형 / 키 와 같은 특성들보다는 직업, 연봉, 신용과 같은 특성들이 필요할 것이다. 반면, 병원 어플리케이션을 만들고 있다면 오히려, 시력 / 혈액형 / 키 와 같은 특성들이 필요하다. 이처럼 애플리케이션 경계(서비스 도메인)에 따라 클래스의 설계가 달라진다는 것이다.
캡슐화
객체 지향에서 캡슐화는 정보 은닉의 기능을 가진다.
대표적인 객체 지향 언어인 자바에서는 '접근 제어자'를 통해 캡슐화의 특성을 지니게 된다.
접근 제어자의 종류는 public, private, (package-private OR [default]), protected로 4가지가 있다.
특징은 다음과 같다.
- public - 누구나 접근 가능
- protected - 상속받거나 같은 패키지에 있을때만 접근 가능
- [default] (접근제어자로 아무것도 적지 않았을 때) - 같은 패키지 내에 있을 때만 접근 가능
- private - 선언된 클래스에서만 접근 가능
이와 같은 접근 제어자를 통해, 외부의 객체로부터 자신의 정보를 제한적으로 노출시킬 수 있다.
추가적으로, 접근 제어자가 private이라도 같은 클래스의 객체끼리는 서로 접근이 가능하다.
또한, 서로 다른 파일이더라도 패키지가 같다면 같은 패키지 내 클래스나 객체의 public, [default], protected 멤버에 자유롭게 접근할 수 있다.
다형성
객체 지향에서 다형성은 프로그램을 작성할 때 사용 편의성을 제공한다.
객체 지향에서 다형성은 오버라이딩(Overriding)과 오버로딩(Overloading)이 있다. 추가적으로, 클래스를 상속받거나 인터페이스를 구현하는 클래스에서 인터페이스나 상위 클래스의 자료형으로 객체를 생성하는 것도 다형성이라고 볼 수 있다.
Overriding은 상위 클래스의 같은 시그니처(반환형, 매개변수, 메소드 명)의 메소드를 하위 클래스에서 재정의하는 것이다.
class Birds{
public void fly(){
System.out.println("flying");
}
}
class Eagle extends Birds{
@Override
public void fly(){
System.out.println("flying faster");
}
}
Overriding 한 메소드는 Birds "클래스명" = new Eagle() 형태로 객체를 생성하여 fly 메소드를 사용해도 하위 클래스에서 재정의된 fly 메소드가 실행된다는 특성이 있다.
Overloading은 같은 이름으로 매개변수를 다르게 하여 메소드 정의하는 것을 말한다.
class Eagle{
public void fly(){
System.out.println("flying faster");
}
public void fly(String name){
System.out.println(name + " flying faster");
}
}
참고
ㆍ"스프링 입문을 위한 자바 객체 지향의 원리와 이해", 김종민 저자