객체 지향 패러다임은 주로 객체, 클래스, 상속이라는 세 가지 주요 개념을 기반으로 한다.
객체(Object): 상태와 행동을 가진다. 상태는 속성(변수), 행동은 메서드(함수)로 표현, 서로에게 'messages'를 보내 소통한다.
클래스(Class): 객체를 생성하기 위한 템플릿이나 청사진
상속(Inhertance): 클래스 간의 관계를 표현하는 메커니즘
또 중요한 개념으로 "polymorphism(dynamic binding, 다형성)"이 있다. 동일한 이름의 메서드나 연산자가 다양한 방식으로 동작할 수 있다.
Object
객체는 명확한 경계와 의미를 가진 것 또는 개념이다. 이는 Tangible Things(물리적인 것), Concepts&Processes(개념 및 과정)이 있다.
Tangible Things는 프로그램 내에서 구체적인 객체로 표현될 수 있고, Concepts&Processes는 프로그램 내에서 특정한 동작이나 로직을 나타내는 객체로 표현될 수 있다.
객체는 프로그래밍 또는 디자인에서, 그들의 상태에 따라 어떻게 행동하거나 반응하는지 바뀔 수 있다.
사건 또는 시간에 따른 동적으로 달라지는 동작은 'state'의 존재를 나타낸다.
객체의 상태는 각 속성의 현재(보통 동적인) 값에 의해 결정된다. 엔지니어링에서는 객체의 상태를 드러내기보단 캡슐화해서 감추는 것이 좋은 관행이다.
객체들은 unique한 정체성(Identities)을 가진다.
Ientity는 객체를 모든 다른 객체들과 구별하는 속성이다.
객체들은 메시지라는 매커니즘을 통해 요청을 하여 행동을 수행한다.
객체들은 고립되어 존재하지 않는다. 객체들은 메시지를 교환하여 다른 객체와 협력해야 한다.
메시지는 행동에 대한 요청(request for action)이고, 객체는 메시지를 받아들일 수 있으며 대가로 행동을 수행하고 결과를 반환한다.
메시지 전달은 다른 객체에 함수를 호출하는 것과 동일하다.
메시지에서 메시지를 받아들이는 지정된 수신자(receiver, target)이 있다.
수신자가 수행하는 실제 행동은 클래스의 상태에 따라 다를 수 있다. (Polymorphism)
좋은 엔지니어링 습관은 캡슐화하는 것이다.
캡슐화(Encapsulation):
- 인터페이스와 구현의 분리
- 코드의 재사용(reuse), 신뢰성(reliability)를 도와준다. (strong cohesion(응집력), 느슨한 결합(loose coupling)
객체가 어떻게 일하는진 몰라도 되지만, 어떤 메시지를 이해할지는 알아야한다.
Class
클래스는 유사한 속성(attributes)과 행동(behavior)을 가진 객체들의 집합이다.
실제 세계에는 너무 많은 객체들이 있어서 다루기 비현실적이고, 객체를 분류함으로써 유사한 객체들 사이의 공통점을 파악할 수 있다.
- 한 번만 공통적인 것을 설명한다.
- 객체들을 분류함으로써 유사한 객체들 사이의 공통점을 파악할 수 있다.
클래스는 관련 객체의 내부 표현과 행동에 대한 repository이다.
객체들은 인스턴스화 과정을 통해 클래스에서 생성된다.
객체는 클래스의 실제 'instance'이다. (instance와 object는 같은 말이다.)
클래스는 factories, 객체는 products라고 이해하면 쉽다.
객체는 하나의 객체가 여러 유형을 가질 수 있다. 위 그림에서 ClassD는 ClassB의 하위 유형이고, Storable 및 Runnable 두 가지 유형의 인터페이스를 가지고 있다. 동시에, ClassX는 Runnable 인터페이스만을 가지고 있다. ClassD와 ClassX는 모두 Runnable 유형이다.
동일한 클래스에서 생성된 객체는 동일한 속성 값을 가질 수도 있고, 아닐 수도 있지만 각 객체는 항상 고유하다.
클래스 계층구조
- 클래스들은 상속 계층이라는 단일 루트 트리에 구성된다.
- 상속(Inheritance)은 자연스러운 카테고리 계층(hierarchies)을 반영하는 클래스 정의를 정리할 수 있게 한다.
- 클래스 계층은 특정 수준에서 찾을 수 있는 정보(데이터/동작)는 계층의 하위 수준에 자동으로 적용된다.
상속은 클래스 간의 "is a" 관계 (generalization/specialization - 일반화/특수화 관계)를 모델링한다.
예를 들어, Animal이란 상위 클래스와 Mammal이라는 하위 클래스(또는 파생 클래스)가 있을 때, Mammal is a(n) Animal이라는 관계가 성립된다.
Mammal이 Animal의 하위클래스라면, 모든 Mammal은 Animal이지만, 모든 Animal은 Mammal인 것은 아니다.
UML 표기법
Spparate Target Style에서는, Shape라는 상위 클래스가 있고, 이는 Polygon, Ellipse 등의 하위 클래스로 확장된다. 하위 클래스는 화살표로 상위 클래스를 가리킨다.
Shared Target Style에서는, 상속 관계를 더 간결하게 표현할 때 사용된다.
하위 클래스는 상위 클래스와 그 조상 클래스로부터 모든 속성과 연산을 상속받는다.
오버라이딩: 하위 클래스는 상위 클래스의 메서드를 재정의할 수 있다.
오버라이딩 시, 하위 클래스의 메서드는 상위 클래스의 메서드와 동일한 서명(signiture)를 가져야 한다. 이는 메서드의 이름, 타입, 순서, 반환 타입등을 포함한다.
다만, 모든 포유류가 알을 낳는다고 가정했을 때 알을 낳는 포유류인 오리너구리가 있는 예외가 있듯, 일반화가 도움이 될 수 있지만 예외 사항이 항상 존재한다는 것을 인지해야 한다.
LSP : Liskov Substitution Principle(리스코프 대체 원칙)
- 하위 클래스의 인스턴스는 상위 클래스의 인스턴스로 대체될 수 있어야 한다.
하위클래스의 디자이너는 인터페이스(함수서명)와 구현(함수 본문 또는 코드) 사이에서 어떤 것을 공유할지 정해야 한다.
subclass는 주로 구현을 공유하는 것을 의미한다. 이는 부모 클래스로부터 코드와 구조를 재사용하는 것을 의미한다. 이것을 in terms of 관계로 표현하고, 클래스 상속 또는 구현 상속이라고 불린다.
subtype은 다른 타입의 인터페이스를 포함하는 경우 그 타입의 하위 유형이다. - 인터페이스 상속
상속은 인터페이스 공유를 위한 메커니즘으로, is-a 관계를 강제한다.
슈퍼클래스 디자이너는 각 작업에 대해 서브클래스에게 무엇을 전달할지 결정해야 한다.
함수 수준에서는 상속이 두 가지 유형으로 나타낼 수 있다.
- 함수 인터페이스만 상속 : 서브클래스가 함수 서명(인터페이스)만 상속하고 자체 구현을 제공
- 구현까지 전부 상속 : 서브클래스가 슈퍼클래스로부터 함수 서명과 코드 모두를 상속
순수 가상 함수 - 파생 클래스에서 함수 인터페이스만 상속할 때 사용, 파생 클래스에서 구현해야함.
가상 함수 - 파생 클래스에서 함수 인터페이스와 기본 구현을 모두 상속, 파생 클래스에서 오버라이딩 가능
비가상 함수 - 파생 클래스에서 함수 인터페이스와 반드시 따라야 할 구현을 상속, 파생 클래스에서 오버라이딩 불가
참조 변수의 두 가지 타입
Static Type : 프로그램에서 선언된 타입을 나타냄, 고정돼있고 변경 불가
Dynamic Type : 실제 참조하는 객체의 타입을 나타냄, 객체의 생명 주기동안 변경 가능
Movie m = new Movie();
m = new JamesBondMovie();
JamesBondMovie jm;
jm = new JamesBondMovie();
m = jm;
jm = m; // error
JamesBondMovie가 Movie의 파생 클래스라고 할 때,
슈퍼클래스에 파생클래스를 할당할 수 있지만, 파생클래스에 슈퍼클래스를 할당할 수는 없다.
즉, 큰 그릇엔 작은 그릇을 넣을 수 있지만, 작은 그릇에는 큰 그릇을 넣을 수 없다.
컴파일러는 메시지가 수신 객체에 전송될 때, 정적 타입을 사용하여 연산 검색을 수행해 그 합법성을 확인한다.
부모클래스를 따라 올라가며 어떤 연산을 수행할지 결정한다.
위 그림에서, C클래스로 생성된 객체가 B의 g()를 호출하면 에러가 발생한다. 상속받지 않았기 때문이다.
서브클래스는 슈퍼클래스가 할 수 있는 모든 것을 할 수 있지만, 반대는 그렇지 않다.
객체의 사용자는 참조 변수를 통해서만 그 객체의 연산에 접근할 수 있고, 따라서 객체가 알 수 있는 것은 변수의 정적 타입에서 정의된 연산 집합뿐이다.
서브클래스는 모든 조상들로부터 연산을 상속받지만, 슈퍼클래스는 서브클래스로부터 무언가를 받지 않는다.
런타임은 메시지가 수신자에게 전송될 때 동적 타입을 사용해서 연산을 선택한다.
정리하자면, 메서드의 호출 결정은 컴파일 시간 또는 런타임 중 이루어질 수 있다.
컴파일 시간때는 정적 바인딩, 조기 바인딩(early binding)이라고 하며 함수 호출이 컴파일 시간에 결정된다. java, C++의 static 함수 또는 C++의 non-virtual 함수가 이에 해당된다.
런타임때는 동적 바인딩, 늦은 바인딩(late binding)으로, 함수 호출이 런타임이 결정된다. java와 C++에서 virtual 함수가 이에 해당한다.
정리
다형성(polymorphism)은 한 객체가 여러 형태를 가질 수 있는 특성을 말한다. 다형성은 객체 지향 프로그래밍의 핵심 개념 중 하나로, 동일한 메시지나 명령을 여러 다른 타입 객체에 보내더라도 각 객체에 따라 다른 동작을 수행할 수 있다.
하나의 메서드가 상위 클래스의 인스턴스를 매개변수로 받을 경우, 그 메서드는 하위 클래스의 인스턴스도 받을 수 있다. 반대로 메서드가 상위 클래스의 인스턴스를 반환해야 할 때, 그 메서드는 하위 클래스의 인스턴스도 반환할 수 있다.
객체 지향 패러다임: 소프트웨어 복잡성을 극복하는 진화적 접근법으로, 실제 세계의 문제 영역을 컴퓨터의 솔루션 영역으로 추상화하여 매핑하는 방식으로 작동한다.
'학교강의필기장 > OOP' 카테고리의 다른 글
Domain Model (0) | 2023.12.10 |
---|---|
System Sequence Diagram (0) | 2023.12.10 |
Use Case Diagram (0) | 2023.12.10 |
2. UML (1) | 2023.10.29 |
0. 객체지향개발론 개요 (1) | 2023.10.28 |