Object Oriented Design principles


아래의 원칙들이 직접적으로 Safety design을 위한 것은 아니지만, 소프트웨어 설계시에 유용할 것으로 생각한다.

객체 지향 설계 원칙들

1. 단일 책임의 원칙(SRP, Single Responsibility Principle) 참고 링크

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐 이어야 한다.

어떤 객체가 너무 많은 다른 종류의 행동을 할 수 있다면, 그것은  다른 객체가 변경 될 때 함께 변경되어야 할 가능성이 많다는 것을 의미합니다.

가능한 하나 또는 명확하게 관련이 있는 책임을 부여 함으로써 객체는 보다 명확해(Explicit) 지고, 표현가능해 지며(Expressive), 느슨한 커플링(Loose Coupling) 과 높은 응집력(High Cohesion) 을 얻게 됩니다.

2. 의존 관계 역전의 원칙(DIP, Dependency Inversion Principle) 참고 링크

고차원의 모듈은 저차원의 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존한다.
추상화 된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.

자주 변경되는 구상 클래스(Concreate class)에 의존하지 마라. 만약 어떤 클래스에서 상속받아야 한다면, 기반 클래스를 추상 클래스로 만들어라. 만약 어떤 클래스의 참조(Reference)를 가져야 한다면, 참조 대상이 되는 클래스를 추상 클래스로 만들어라. 만약 어떤 함수를 호출해야 한다면, 호출 되는 함수를 추상 함수로 만들어야 합니다.

DIP는 그 용어 자체의 어려움 때문에 이해하기가 쉽지 않지만 내용은 단순합니다. 추상화된 것 (Absctract Class, Interface) 을 사용해야 커플링을 느슨하게 만들 수 있다는 말 입니다. 예를 들어

abstract class Car {} 로 부터 상속 받은 class Truck : Car {}, class Bus : Car {} 가 존재 할때,

Truck porter = new Truck() 과 같이 사용하지 말고,

Car myCar = new Truck();  과 같이 추상 클래스를 기반으로 작업하거나.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
abstract class Car
{
   public abstract void Run();
   public abstract void Stop();
}
class Truck : Car
{
    public override void Run()
    {
        throw new NotImplementedException();
    }
    public override void Stop()
    {
        throw new NotImplementedException();
    }
}
class Bus : Car
{
    public override void Run()
    {
        throw new NotImplementedException();
    }
    public override void Stop()
    {
        throw new NotImplementedException();
    }
}
static void main()
{
    Truck myCar = new Truck();
    myCar.Run();
    Car myCar = new Truck();
    myCar.Run();
}
1
 인터페이스를 사용하는 경우.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
abstract class Car
  {
  }
  interface IDrive
  {
      public void Run();
      public void Stop();
  }
  class Truck : Car, IDrive
  {
      #region IDrive 멤버
      public void Run()
      {
          throw new NotImplementedException();
      }
      public void Stop()
      {
          throw new NotImplementedException();
      }
      #endregion
  }
  class Bus : Car, IDrive
  {
      #region IDrive 멤버
      public void Run()
      {
          throw new NotImplementedException();
      }
      public void Stop()
      {
          throw new NotImplementedException();
      }
      #endregion
  }
  static void main()
  {
      Car myCar = new Truck();
      IDrive drive = (IDrive)myCar;
      drive.Run();
      drive.Stop();
  }

와 같이 추상적인 것에 구체적인 것을 대입해 사용함으로써 커플링을 느슨하도록 만드는 원칙 입니다.

3. 인터페이스 분리의 원칙(ISP, Interface Segregation Principle) 참고 링크

클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다.

비대한 클래스(Fat class) 란 메소드를 몇십 몇백개 가지는 클래스를 가르키는 말이다. 대개 시스템 안에 이런 클래스를 두고 싶어 하지 않지만, 가끔 피 할 수 없는 경우도 있습니다.

비대한 클래스가 거대하고 보기 흉하다는 사실 말고도, 한 사용자가 이 비대한 클래스의 메소드를 다 사용하는 일이 매우 적다는 것도 문제입니다. 즉. 메소드를 몇십개 선언한 클래스에서 사용자는 단지 두세 개만 호출 할 지도 모르죠. 불행하게도 이 사용자들은 호출하지도 않는 메소드에 생긴 변화에도 영향을 받습니다.

ISP 인터페이스 분리의 원칙은 가족관계가 아닐때 같은 행동을 하는 객체들에게 인터페이스를 할당 하고 사용 함으로써 DIP 를 가능하도록 만들고 , 그 결과로 느슨한 커플링과 명시성과 표현성을 얻습니다.

4. 리스코프 대체 원칙(LSP, Liskov Substitution Principle) 참고 링크

서브타입(Sub Type)은 언제나 자신의 기반 타입(Base Type)으로 교체할 수 있어야 한다.

LSP에 따르면, 기반 클래스의 사용자는 그 기반 클래스에서 유도된 클래스를 기반 클래스로써 사용할때, 특별한 것을 할 필요 없이 마치 원래 기반 클래스를 사용하는 양 그대로 사용할 수 있어야 한다. 더 자세히 말하자면, instanceof나 다운캐스트를 할 필요가 없어야 합니다. 강조하건데, 사용자는 파생클래스에 대해서 아무것도 알 필요가 없어야 합니다. 파생 클래스가 있다는 사실 조차도.

LSP는 상속(Inheritance), 다형성(Polymorphism) 과 관련된 원칙이죠. 상속은 코드 재사용 이라는 이유로 과용 될수 있는 기능입니다. 과잉 사용된 상속은 복잡한 계층구조와 커플링을 타이트하게 함으로써 객체지향으로 얻기 위한 유지관리비용 감소에 악영향을 미치는 요소중 하나 입니다. LSP란 상속을 사용할때 지켜야 하는 원칙을 말하는데요. 상속은 코드 재사용을 위해서가 아니라 명확한 가족 관계가 있을때 사용해야 하며, 그 행위(Behavior) 와 연관이 있습니다. 부모와 파생된 객체들이 존재 할때 가능한 모두 동일한 메소드 이름과 갯수를 가지게 함으로써  언제든지 서브 타입이 기반타입으로 교체될 수 있어야 함을 말합니다.

다시 말하면, 상속의 오용은 가족이 아닌 객체를 비슷한 일을 하는 것을 보고 코드 재사용 욕심에 가족 관계로 묶는 것. 다층 상속으로 계층 구조가 깊고 복잡하게 하는 것. 파생 타입에 부모, 형제들과는 전혀 동떨어진 능력을 부여 함으로써 돌연변이를 만들어 버리는 것을 의미하며 LSP는 돌연변이(부모, 형제들의 형질과 관련이 없는 능력을 가진 파생타입 이며 이렇게 될때는 이미 가족이 아님을 의미)가 발생하지 않도록 관리해서 상속 관계의 타입들이 유연하게 대체 될 수 있게 하는 원칙 입니다.

5. 개발 폐쇄 원칙(OCP, Open-Closed Principle) 참고 링크

소프트웨어 엔터티(클래스, 모듈, 함수 등등)는 Interface에 대해서는 개방되어야 하지만, 변경에 대해서는 폐쇄되어야 한다.

단위 테스트를 할 때는 종종 환경에 생기는 변화를 제어하고 싶은 경우가 발생합니다. 예를 들어 Emploee를 어떻게 테스트 할지 생각해 보시죠. Emploee객체는 데이터베이스를 변경합니다. 하지만 테스트 환경에서 실제 데이터베이스를 바꾸고 싶지 않습니다. 그렇다고 해서 단위 테스트를 하기 위해 테스트용 더미 데이터베이스를 만들고 싶지도 않습니다. 그렇다면, 테스트 환경으로 환경을 변경해서 테스트 할 때 Emploee가 데이터베이스에 하는 모든 호출을 가로챈 다음 이 호출들이 올바른지 검증하면 좋을 것입니다.

엔터티가 외부로 부터 영향을 받지 않고, 내부의 필요에 의해서만 변경하게 하려면 보호막으로 감싸고 노출되지 않아야 합니다. 그러나 다른 객체와 상호 작용하기 위해서는 선택적인 노출이 필요 합니다. 다른 객체와 대화하기 위한 인터페이스와 확장을 위해서는 Open 하고, 이외의 경우에는 Closed 해야 정보가 감추어지고 (Information Hiding) , 객체들끼리 연결하게 되는 끈이 줄어듦으로써 커플링이 타이트 해질 가능성이 줄어 듭니다.

기타 참고자료

인용 자료: OOD – 객체지향 설계의 가장 큰 원칙은 무엇인가? S.O.L.I.D

객체지향 개발 5대 원리: SOLID (링크)

Advertisements

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중