OOP의 5가지 원칙 / 5 Principle of Object-Oriented Programming.

총 3개의 파트로 진행될 [OOP 5원칙] 세미나는

다른 언어에서의 소프트웨어개발자들이 갈고 닦은 수많은 방법론중에

가장 널리 활용되고 기본이 된다고 여겨지는 5가지 원칙을

ActionScript 3.0에 적용해보고 그에 대한 의미를 다시금 생각해보는 내용이 될 것입니다.

때문에 객체 지향, 디자인 패턴에 대해서 전혀 모르는 상태라면

본 세미나는 별로 도움이 안될지도 모르겠습니다.

디자인 패턴, 리팩터링, 객체지향이니 하는것들은

모두 경험과 필요성을 자각하고 있다는 전제를 바탕으로

기본적인 설명은 하지 않을 것입니다.



0. 서두, Prologue

Flash 는 특성상 개발 기간이 짧고 디자인과 언어로써의 역할을 모두 해왔다.

게다가 Flash가 부흥하게 된 계기 역시 Tween 을 이용한 애니메이션이 지대한 공을 하였기 때문이고

거기다가 그당시 지원되던 메소드들은

말그대로 몇몇 "기능"일 뿐이었다.

솔직히 말해서 디자인툴도 아닌것이;;

개발언어도 아닌것이;;

참 희한했던 툴로 보였을 것이다.

언어로써의 모습도 아니었거니와 돌이켜 생각해보면 참 많은 내용을 하나의 메소드로 제공했었다는 것을 알 수 있다.

그래서 Flash 의 역사는 적지 않게 흘렀지만

이제서야 ActionScript 3.0이 나타났고 비로소 객체지향이라는 언어로써의 자격이 생겼다.

물론 아직도 미흡한 부분이 많고 앞으로 나갈길이 많지만

OOP를 지향하기에는 부족함이 없다.

OOP를 향한다는 이야기는 핵심적인 기술, 특정한 알고리즘하나, 단순 노가다가 아니라

구조로써 변화에 대응할 수 있고

보다 효율적인 관계를 구현전에 미리 구상할 수 있기 때문에 비용이 줄어든다는 이야기며

프레임웍과 개발 방법론을 익힘으로써 큰 규모의 어플리케이션으로써의 면모를 갖출 수 있다는 이야기다.



그렇다면 이미 많은 시간이 지난 이 시점에서 우리가 가져야할 자세는 무엇일까?

다른 진영에서 수십년간 쌓아온 우리 선배들의 꿀물지식들을

낼름 가져와서 우리것으로 익혀 미래에 대응해야 할것이다.



1. 5원칙, 5 Principle

우리가 만드는 클래스들, 클래스들간의 관계들, 구조들...

과연 어떤게 좋은 구조라고 할 수 있을까?

어떻게하면 보다 효율적으로 관리하고, 나중에 손이 덜 가도록 만들 수 있을까?

이에 대한 질문은 다른 영역에서 수 없이 논의되었었다.

오랜 시간 논의된 결과 아래 5가지 원칙을 기준으로 하게 되었다.

 - OCP : Open-Closed Principle (개방-폐쇄의 원칙)
 - SRP : Single-Responsibility Principle (단일 책임의 원칙)
 - DIP : Dependency Inversion Principle (의존 역전의 원칙)
 - ISP : Interface Segregation Principle (인터페이스 분리의 원칙)
 - LSP : Liskov Substitution Principle (리스코프의 대체 원칙)

딱 보기에는 약자도 거기서 거기고 영어로 보자니 먼말인지 모르겠고

한글로 읽어도 글자만 한글이지 당췌 이해가 잘 가지 않을 것이다.

어디서 본거 같긴 한데 정확하게는 무슨 말인지 잘 모르겠고

무슨 내용인지 궁금해서 살살 입질이 오리라 믿는다.

위 5가지 원칙을 기준으로 앞으로 설명을 해 나갈텐데

저 5가지 원칙은 서로 다른 뜻이나 관계가 아니라

서로 연관이 되어 있고 반대되는 원리 같은데 같이 쓰이기도 하는

서로 밀접한 관계가 있는 원칙들이기 때문에

세미나를 진행하면서 순서 없이 하나씩 등장할것이다.


2. OCP ( Open-Closed Principle ) : 개방-폐쇄의 원칙

사전적인 뜻풀이로는

"상속에는 열려있어야 하고 변경에는 닫혀있어야한다."

라고 되어 있다.

무슨 뜻인고 하니 우선 예를 들기전에 간단하게 개념부터 이야기 하고 가자면

상속을 자유롭게 하기 위해서 특정 하위 클래스가 할일을 미리 상위 클래스에서

구현해버리면 그 기능을 하지 않아야할 클래스까지도 그 기능을 강제로 물려받는다는 이야기다.

변경에 닫혀야한다는 이야기는

상위클래스는 모든 하위클래스들이 공통적으로 쓰는 기능들을 가지고 있기 때문에

특정 하위 클래스라고 해서 상위 클래스의 기본 기능을 바꿀수 있거나 간섭할 수 있어서는 안된다라는 이야기다.

결국은 OOP의 기본 개념인 자유로운 상속을 통한 확장과 재사용성을 추구하기위한

제 1법칙인 것이다.

자 그러면 널린 Java 예제 말고 ActionScript 로 그 예를 한번 살펴보자.

package painter1
{
     import flash.display.Sprite;
    
     public class Shape extends Sprite
     {
          protected var _type: String;
               
          public function drawCircle(): void
          {
          }
               
          public function drawOval(): void
          {
          }
               
          public function drawRect(): void
          {
          }
               
          public function drawTriangle(): void
          {
          }
               
          public function get type(): String
          {
               return this._type;
          }
         
     }}//end Shape

}

위와 같은 Shape 클래스가 있다.

이 클래스는 원, 사각형, 타원, 삼각형을 그릴 수 있는 모든 메소드를 가지고 있다.

그중에 하나로 Circle 클래스를 살펴보자.

package painter1
{
     public class Circle extends Shape
     {
          public function Circle()
          {
               this._type = "circle";
          }
     }}//end Circle

}

Circle 클래스는 자신의 타입을 "circle" 로 지정하고 Shape 클래스를 상속하였다.

그럼 사용할때는 어떻게 사용하는지 보자.

package painter1
{
     import flash.display.Sprite;
    
     public class Painter extends Sprite
     {
          public function Painter()
          {
               // set shape to Circle.
               this.shape = new Circle();
          }
         
          private var shape: Shape;
    
          public function draw(): void
          {
               if( this.shape.type == "circle" )
               {
                    this.shape.drawCircle();
               }
          }

     }}//end Painter

}

Painter 클래스에서는 사용될 shape 클래스중 하나를 인스턴스화해서

draw 라는 메소드에서 사용할때

type 이라는 변수를 가져다가 체크하는 수 밖에 없다.

클래스 다이어그램으로 그려보면 다음과 같다.

사용자 삽입 이미지


그러면 직사각형, 별모양, 오각형, 마름모, ................

늘어날때마다 타입은 하나씩 늘어나고

메소드도 하나씩 계속 늘어나야된다.

이런 문제를 어떻게 해결 할 수 있을까?

다른 언어에서는 Abstract 메소드를 이용해서 구현하지만 Flash 에서는 지원되지 않는 만큼

인터페이스를 통하여 추상화하는 방법을 사용한다.

또 슬슬 용어가 어려워지는데

아래 결과를 먼저 보자.

package painter2
{
     import flash.display.Sprite;
    
     public class Painter extends Sprite
     {
          private var shape1: IShape;
          private var shape2: IShape;
          private var shape3: IShape;
         
          public function Painter()
          {
               this.shape1 = new Circle();
               this.shape2 = new Rect();
               this.shape3 = new Triangle();
          }
         
          public function draw(): void
          {
               this.shape1.draw();
               this.shape2.draw();
               this.shape3.draw();
          }

     }}//end Painter

}

어떤 쉐입이 오던지 IShape 을 구현한 클래스이면

draw() 메소드만 실행해주면 된다.

아래는 IShape 인터페이스의 모습이다.

package painter2
{
     public interface IShape
     {
          function draw(): void;

     }}//end IShape

}

draw() 메소드 밖에 없다.

즉, IShape 을 구현한 클래스는 모두 draw 라는 공통적인 메소드를 가지지만

어떤 동작을 할지는 구현된 클래스가 알아서 처리하는것이다.

interface 는 OCP 를 위해서 태어난게 아닐까 싶다.

즉 Oval은 public 으로 draw 메소드를 구현해놓고

그 안에서 원을 그릴지 어떨지 판단하면 된다.

Oval클래스는 아래와 같다.

package painter2
{
     import flash.display.Sprite;
    
     public class Oval extends Sprite implements IShape
     {
          public function draw(): void
          {
               // draw oval
          }

     }}//end Oval

}

훨씬 간결해졌으면서도 훨씬 사용하기 편해졌다.

이와 같이 상위 클래스가 하위 클래스의 역할을 벗어던짐으로써

상위 클래스 대신 인터페이스가 그자리를 차지하면서 생긴 효과는

재사용성을 극대화 시키고 IShape 을 사용하는 곳이면

그 후에 어떤 도형을 추가하더라도 사용되는 곳의 소스는 수정할 필요없이

얼마든지 추가할 수 있게 되었다.

위 구조를 클래스 다이어그램으로 나타내면 아래와 같다.

사용자 삽입 이미지


이처럼 확장이 열려있도록 하기 위해서는

인터페이스를 통하여 어떤 동작을 할것이라는 것만 정의를 해주고

실제 동작하는 것은 하위클래스가 담당하도록 확장을 자유롭게 해줄 수 있다.

이런 구조가 바로 확장에 있어서 열린 (Open) 된 구조다.

이렇게 됨으로써 상속을 하면서 상위 클래스를 변경하지 않아도 되게 되었다.

이것이 변경으로 부터 닫혀 (Closed) 된 구조다.

ActionScript 기본 클래스중에서

예를 볼 수 있는 부분은 바로 ByteArray 와 URLStream, Socket, FileStream 클래스들이다.

기본적으로 생각할 수 있기로는

어차피 바이트를 읽고 쓰는 클래스들인데

ByteArray 를 상위 클래스로 놓고 쓰지 않을까?

라는 생각이 들수도 있지만

바이트를 읽고 쓰는 동작이 ByteArray 에 의해서 고정되어 버렸다면

바이트 정보를 메모리가 아닌 로컬 파일을 쓰는 FileStream,

바이트 정보를 메모리나 로컬 파일이 아닌 패킷으로 보내는 Socket 등의 클래스들은

탄생하지 못했을 것이다.

탄생했더라도 ByteArray 와는 상관없는 상위 클래스를 가지고 있을것이기 때문에

바이너리 데이타를 소켓으로 보내거나

바이너리 데이타를 파일로 저장하는등의 방법은 아마 매우 어려워졌을 것이다.

하지만 IDataInput, IDataOutput 이라는 인터페이스 둘로 분리시킴으로써

바이트 정보를 읽고 쓰는 행위를 한다는

동작만 정의해두고

실제로 동작하는 부분은 이 인터페이스를 구현하는 클래스들이

따로따로 구현할 수 있게 된것이다.

'Architecture > OOP 5대 원칙' 카테고리의 다른 글

[AS3.0 강좌] OOP의 5대 원칙 Part.3  (20) 2007.12.11
[AS3.0 강좌] OOP의 5대 원칙 Part.2  (4) 2007.12.11

+ Recent posts