본문 바로가기

객체지향

[POCU-OOP] 개체지향 프로그래밍 특성(OOP의 7대 특성)

 이 포스팅은 POCU 아카데미의 '개체지향 프로그래밍 및 설계(Java)' 동영상 강의를 학습하고 정리한 내용입니다.

제 의견이 추가되어 강의 내용과 포스팅 내용이 일치하지 않을 수 있다는 점 미리 말씀드립니다.

 

 


 

 

 

 

1. 캡슐화(encapsulation)

 

 캡슐화는 데이터( = 멤버 변수)와 그 데이터에 작용하는 동작( = 메서드)를 하나로 묶은 것을 의미한다. 정보를 개체 내부에 숨김으로서, 데이터를 외부로부터 보호한다.

 사용자는 클래스 속을 알 필요가 없으며, 이는 사용자가 함수 속을 알 필요가 없는 것과 마찬가지이다. 함수를 분리할 때 적용했던 원칙(중복된 코드가 있으면 private 메서드로 추출)을 클래스에 적용하는 것이다.

 

 

2. 상속(inheritence)

 상속은 이미 존재하는 개체를 기반으로 “확장된 개체”를 만드는 방법이다. 확장된 개체는 기존 개체의 데이터 및 동작을 물려 받으며, 다른 데이터나 동작을 추가할 수 있다.

 실용적인 용도의 관점에서는 코드 중복을 막는다는 장점을 제공한다. 여러 개체에 공통되는 데이터 및 동작을 부모 개체로 만들고, 개별 개체에서 부모 개체를 상속받아, 자신들의 특징적인 요소를 추가한다.

 상속구조는 어떤 프로세스로 만들까? 여러 개별 개체를 만든 뒤 공통 부분을 찾아 부모로 옮긴다. 사람은 구체적인 것(cf. 추상적인 것)을 더 잘 이해하기 때문이다.

 OOP와 관련한 가장 큰 결정사항 중 하나는 “상속 vs 컴포지션” 이다. 둘 다 재사용성이 목적이며, 가능/불가능의 측면에서 보면 많은 경우 둘 다 사용가능하다. 따라서 어떤 것을 사용할지 원칙이 필요하다.

상속 vs 컴포지션 원칙

1. 성능이 중요한 경우에는 상속을 선택한다.

  1. 상속은 개체 생성 시 메모리를 한 덩어리에 담는 반면, 컴포지션은 여러 덩어리의 메모리를 참조를 통해 사용한다. 즉, 참조를 위한 추가적인 메모리 할당이 필요하다.
  2. 캐시 메모리 사용 시, 상속 모델로 만든 개체는 한 번에 캐시 메모리에 들어갈 가능성이 높은 반면, 컴포지션 모델은 부품 수 만큼 캐시 메모리로 로딩할 가능성이 높다.
  3. 프로그래밍 언어에 따라 메모리 할당(new) & 해제(delete) 중에 특히 느린 것이 있다. 상속은 메모리 할당과 해제가 딱 한번 이뤄지지만, 컴포지션은 한 번 + 부품 수 만큼 이뤄진다.

2. 용도 떄문에 상속을 골라야 하는 경우 : 다른 형의 개체들을 한꺼번에 처리하고 싶을 때 상속을 사용한다.

ArrayList<Animal> animals = new ArrayList<>(new Animal[]{new Dog(), new Cat()});
for(Animal animal : animals){
		animal.eat(); 
}

 

3. 관리의 효율성을 고려할 때

깊은 상속일 경우, 부모 클래스를 변경하게 되면 자식 클래스에서도 문제가 없는지 모두 확인해야 한다. 물론 컴포지션도 비슷한 문제가 있지만 상속보다는 덜 하다.

 

4. 일반적인 경우 : is-a 관계면 상속 , has-a 관계면 컴포지션을 선택한다.

 

 

3. 다형성(polymorphism)

 다형성은 많은 사람들이 OOP의 핵심이라고 여기는 특징이다. 같은 지시를 내렸을 때 다른 종류의 개체가 동작을 달리하는 것을 말한다. 같은 지시는 동일한 함수 시그내처 호출을 의미하고, 다른 동작은 개체의 종류에 따라 실제 함수 구현 코드가 다름을 의미한다.

 다형성의 혜택을 받으려면 상속 관계를 활용해야 한다. 부모 개체에서는 함수 시그내처를 선언하고, 자식 개체에서는 해당 함수를 오버라이딩해야 한다.

 절차적 언어에서는 “같은 지시 다른 동작”을 수행하기 위해 if문을 사용해야 했다. 아래 코드는 컴파일 타임에 결정되며 이른 바인딩(early binding, cf. late binding)이라고 말한다.

if(animalType == AnimalType.Dog){
	System.out.println("왈왈");
}else if(animalType == AnimalType.Cat){
	System.out.println("야옹");
}else if(animalType == AnimalType.Bird){
	System.out.println("삐약");
}

 

 다형성은 각 자료형의 코드를 클래스 안에 넣음으로써 캡슐화를 상승시킨다. 새로운 클래스를 추가할 때 클래스 코드만 추가하고 호출자(클라이언트)의 코드는 수정하지 않아도 된다. 이를 통해 유지보수성을 높이는 장점을 제공한다.

 위에서 언급한 다형성은 엄밀히 말하면 세 가지 다형성 중 하나인 “서브타입(subtype) 다형성”이라고 한다. 물론, 보통 다형성이라 하면 대부분 “서브타입 다형성”을 의미한다. 그 외에는 애드혹(ad-hoc) 다형성(ex. 함수 오버로딩) , 매개변수 다형성(ex. Java Generic)이 있다.

 

 

4. (데이터) 추상화(abstraction)

 추상화를 언급하는 문헌에 따라, ‘데이터 추상화’ 또는 ‘추상화’라고 한다. 추상화란 무엇일까? 수학적으로는 일반화(반의어 : 구체화)의 의미를 갖는다. OOP에서는 개체 속에 있는 실제 데이터나 함수 구현 방법에 종속되지 않겠다는 뜻을 갖는다.

 데이터 추상화는 개체 사용 시 그 안에 정확히 어떤 데이터가 있는지 알 필요가 없음을 의미한다. 즉, 개체 안에 있는 데이터에 직접 접근을 불가하도록 하는 것이다. 대신, getter & setter와 같은 함수를 통해 접근 제어가능하다. 캡슐화는 데이터 추상화를 이루는 방법 중 하나이다.

 다형성을 통한 추상화도 존재한다. 추상 클래스(abstract class)나 인터페이스(interface)를 활용한다.

 

추상화는 추상 자료형(ADT, abstract data type) 관점과 절차적 데이터 추상화(PDA, procedural data abstraction) 관점으로 나눠진다. ADT관점에서, 사용자는 클래스를 자료형으로 사용할 수 있음을 강조한다. 그 클래스 안에 들어있는 멤버 변수가 정확히 무엇인지 몰라도 되며, 클래스로부터 개체 생성이 가능하다는 것이 주요 포인트이다.

PDA관점에서, 데이터를 직접 조작하는 대신 메서드를 호출함을 강조한다. OOP라는 용어를 처음 주창했다는 소수설의 관점과 유사하다고 한다. 

 

추상화는 모델링이다 (참고자료 : 스프링 입문을 위한 자바 객체지향의 원리와 이해(책))

 객체 지향의 추상화는 곧 모델링이다. 추상화란 구체적인 것을 분해해서 관찰자가 관심있는 특성만 가지고 재조합하는 것이라고 정리할 수 있다. 세상에 존재하는 유일무이한 객체를 특성(속성 + 동작)에 따라 분류하면 객체를 총칭할 수 있는 집합적 개념, 즉 클래스(분류)가 나오게 된다. 이때 객체는 유일무이한 사물이며, 클래스는 같은 특성을 지닌 여러 객체를 총칭하는 집합의 개념이다.

 추상화 할 때, 애플리케이션 경계에 따라 클래스의 설계가 달라져야 한다. 추상화의 일반적인 의미는 구체적인 것을 분해해서 관심영역에 대한 특성만을 가지고 재조합하는 것이다. 이를 IT적인 용어로 바꿔보면 다음과 같다. 추상화란 구체적인 것을 분해해서 관심영역(애플리케이션 경계)에 있는 특성만 가지고 재조합하는 것이다( == 모델링).

 

 

5. 연관(association)

 연관은 어떤 개체가 제공하는 기능을 다른 개체가 이용하는 관계를 말한다. 종종 상속과 비교해서 설명된다. 상속은 자식 개체가 부모 개체의 모든 것을 내포하는 관계이지만, 연관은 한 개체가 다른개체를 참조하는 관계이다. 상속과 연결지어, 서로 장단점이 반대된 행태를 갖고 있다.

 세부적으로 다시 집합과 컴포지션으로 나뉘기도 하며, 이 셋을 구분하지 않고 다 합쳐서 컴포지션이라 하기도 한다.

 

 

6. 컴포지션(composition) & 집합(aggregation)

 컴포지션은 합성, 조합, 조립, 구성 등 다양한 번역어가 존재한다. 의미적으로는 여러 개의 부품(그 자체로 개체)을 조립해서 새 개체를 만드는 방법을 뜻한다(ex. 자동차 부품). 집합과의 차이는 부품 그 자체로는 존재 의의가 없다는 것이다. 조립품이 소멸할 때 부품도 같이 소멸한다. 즉, 부품은 조립품의 수명을 따른다.

 반대로 집합에서는 전체 그룹단위가 사라져도 단위요소가 사라지지 않는다. 즉, 여러 개체를 모아 다른 개체를 만든다는 것은 컴포지션과 동일하지만, 단위요소가 별도로 존재할 수 있다는 점에서 차이를 갖는다.

갖는다.