이전 포스팅에서 객체에 대한 내용과 객체의 크기에 대한 결정을 하는 방법, 그리고 인터페이스에 대해 간단하게 다뤄 보았다. 이번 포스팅에서는 실제로 객체를 어떻게 정의하는지에 대해서 이야기 해보고자 한다.
객체 구현 명세하기
객체의 구현은 클래스에 정의한다. 클래스는 객체 내부의 데이터, 데이터의 표현방법, 그리고 오퍼레이션을 정의하여 객체를 구현한다.
OMT(Obejct Modeling Technique) 기반의 표기법에서는 클래스를 표현하는 사각형에 볼드체로 클래스의 이름을 표현한다. 오퍼레이션의 이름은 클래스 아래 칸에 나열하고, 클레스가 정의하는 데이터는 오퍼레이션 아래 칸에 표시한다. 즉, 클래스의 이름, 오퍼레이션, 데이터 순으로 구분하여 표시한다.
** operation의 type은 반환 type을 명시한 것이며, variable의 type은 객체의 타입이다. (string, int, List 등..)
클래스로부터 인스턴스 생성을 통해 객체가 만들어진다. 즉, 객체는 클래스의 인스턴스라고 말할 수 있다. 클래스의 인스턴스화 과정은 객체 내부 데이터(variable)에 대한 공간을 할당하고, 이들 데이터를 오퍼레이션과 연관짓는 것이다. 클래스의 인스턴스화 과정을 통해 객체의 인스턴스를 얻게 된다.
인스턴스화
점선 화살표는 한 클래스(A)가 다른 클래스(B)의 객체를 인스턴스화 함을 의미한다. 화살표의 방향은 생성할 객체의 클래스(B)로 향한다.
클래스 상속
클래스 상속을 통해 기존의 클래스를 기반으로 새로운 클래스를 정의할 수 있다. 서브 클래스가 부모 클래스를 상속하면, 부모 클래스가 갖는 모든 데이터와 오퍼레이션을 서브클래스가 갖게 된다. 서브 클래서의 인스턴스는 부모 클래스가 정의한 모든 데이터를 가지며, 부모 클래스가 정의한 오퍼레이션을 모두 수행할 수 있다. 서브클래스 관계는 아래 이미지와 같이 수직선에 삼각형을 연결한다. 삼각형의 밑변 쪽이 서브클래스이며, 꼭지점이 부모 클래스이다.
class Parent
{
public string sample;
public Parent()
{
Console.WriteLine("Parent Called");
}
}
class Child : Parent
{
public Child(string sample)
{
this.sample = sample;
Console.WriteLine("Child Called");
}
public void Test()
{
Console.WriteLine("sample: {0}", sample);
}
}
class Program
{
static void Main(string[] args)
{
Child c = new Child("soopiri");
c.Test();
}
}
// result
// Parent Called
// Child Called
// sample: soopiri
추상클래스 (Abstract Class)
추상클래스는 모든 서브클래스들 사이의 공통되는 인터페이스를 정의한다. 추상 클래스는 정의한 모든 오퍼레이션이나 일부 오퍼레이션의 구현을 서브 클래스에게 넘긴다. 정의한 오퍼레이션들 모두가 추상 클래스에 의해 구현된 것이 아니므로, 추상 클래스는 인스턴스를 생성할 수 없다. 정의만하고 구현하지 않는 오퍼레이션을 추상 오퍼레이션이라고 하고, 추상 클래스가 아닌 클래스를 구체적 클래스(concrete class) 라고 한다.
서브 클래스는 부모 클래스가 정의한 행위를 재정의하거나 정제할 수 있다. 서브 클래스는 부모 클래스에 정의한 오퍼레이션의 구현을 바꿀 수 있다. 즉, 오버라이딩(overriding)을 통해 서브클래스는 부모 클래스에 정의된 처리 방식을 변경할 수 있다. 클래스의 상속은 다른 클래스를 확장하여 새로운 클래스를 정의할 수 있게 한다. 이로써 비슷한 기능성을 갖는 객체군을 정의할 수 있게 된다.
추상 클래스의 이름은 이텔릭체로 작성하여 다른 클래스와 구분한다. 추상 클래스의 오퍼레이션도 마찬가지다. 다이어그램에 오퍼레이션 구현에 대한 사항을 언급할 수 있는데 이는 모서리가 접힌 노트 기호에 작성하고 오퍼레이션과 실선으로 연결하면 된다.
abstract class AbstractSample
{
public abstract void TestMethod();
}
class SubClassSample : AbstractSample
{
public override void TestMethod()
{
// ...
}
}
클래스 상속 vs 인터페이스 상속
클래와 타입 간의 차이를 이해하는 것은 중요한 일이다. 객체의 클래스는 객체의 구현을 정의한다. 클래스는 객체의 내부 상태와 오퍼레이션 구현 방법을 정의하는 것이고 객체의 타입은 인터페이스만을 정의하는 것으로 객체가 반응할 수 있는 오퍼레이션의 집합을 정의한다. 하나의 객체가 여러 타입을 가질 수 있고 서로 다른 클래스의 객체들이 동일한 타입을 가질 수 있다. 즉, 객체의 구현은 다를지라도 인터페이스는 같을 수 있다는 의미이다.
클래스와 타입 간에는 밀접한 관련이 있다. 클래스도 객체가 만족할 수 있는 오퍼레이션을 정의하고 있으므로 객체의 타입을 정의하는 것이기도 하다. 그래서 객체가 클래스의 인터페이스라고 말할 때 객체는 클래스가 정의하고 있는 인터페이스를 지원한다는 뜻을 내포한다.
정리하자면, 클래스 상속은 객체의 구현을 정의할 때 이미 정의된 객체의 구현을 바탕으로 한다. 즉, 코드 공유의 방법이다. 이에 비해 인터페이스 상속은 객체가 다른 곳에서 사용될 수 있음을 의미한다.
구현에 따라서 프로그래밍 X 인터페이스에 따라서 프로그래밍 O
클래스 상속은 기본적으로 부모 클래스에 정의한 구현의 재사용을 통해 프로그램의 기능성을 확장하려는 메커니즘이다. 이미 존재하는 것을 이용해서 새로운 객체를 빨리 정의해 보려는 것이다. 기존의 클래스를 그대로 상속할 수 있다면 새로운 구현에 드는 비용은 무료인 셈이다.
그러나 구현의 재사용이 상속하는 목적의 전부는 아니다. 상속의 기능 중에는 하나는 동일한 인터페이스를 갖는 객체군을 정의한는 것으로 매우 중요한 상속의 특징이다. 객체군을 정의하는 것이 중요한 이유는 그것을 통해 다형성을 얻어 낼 수 있기 때문이다.
추상 클래스를 정의하고 인터페이스 개념으로 객체를 다룰 때 얻을 수 있는 두 가지 장점은 다음과 같다.
- 클라이언트가 원하는 인터페이스를 객체가 만족하고 있는 한 클라이언트는 그들이 사용하는 특정 객체 타입에 대해 알아야 할 필요가 없다.
- 클라이언트는 이들 객체를 구현하는 클래스에 대해 알아야 할 필요가 없고 단지 인터페이스를 정의하는 추상 클래스가 무엇인지만 알고 있을 뿐이다.
이렇게 하면 서브 시스템 간의 구현 종석성을 없앰으로써 재사용 가능한 객체지향의 원칙을 만족한다.
구현이 아닌 인터페이스에 따라서 프로그래밍 하자. 변수를 구체적 클래스의 인스턴스로 선언하지 말고, 대신 추상 클래스의 인터페이스를 만족하도록 인스턴스 변수를 정의하는 것이 디자인 패턴의 일반적인 형태일 것이다.
GoF의 디자인패턴 참고
'Development > 디자인패턴' 카테고리의 다른 글
디자인 패턴을 이용하는 방법 (4) (0) | 2022.04.20 |
---|---|
디자인 패턴을 이용하는 방법 (3) (0) | 2022.04.19 |
디자인 패턴을 이용하는 방법 (1) (0) | 2022.04.14 |
디자인패턴의 조직화, 관계도 (0) | 2022.04.13 |
디자인 패턴의 종류 (0) | 2022.04.13 |