Inor

[SOLID] 객체지향 개발의 5대 원칙 본문

Computer Engineering

[SOLID] 객체지향 개발의 5대 원칙

Inor 2017. 8. 22. 19:42

- 객체지향 개발의 원칙


 저와 비슷한 초보 개발자들이 객체지향 프로그래밍을 하는 중간에 겪는 많은 문제들은 클래스들의 구조를 설계할때 너무 복잡한 구조로 설계했기 때문에 발생 합니다. 디자인 패턴들은 이런 구조적 문제 때문에 발생하는 문제점을 줄여주고 사전에 예방 합니다. 그러나 디자인 패턴을 공부하기 전에 디자인 패턴의 근간이 되는 객체지향 개발의 5대 원칙인 SOLID에대해서 알아보도록 하겠습니다. SOLID는 개발자가 객체지향개발을 할 때 기본적으로 갖고 있어야하는 생각들을 5가지로 정리한 것 입니다. 하나씩 알아보도록 하겠습니다.




- Single Responsibility Principle (단일 책임 원칙)


 단일 책임 원칙이란 하나의 클래스가 한 가지의 기능만 갖고 있으며 클래스가 제공하는 서비스는 하나의 책임을 수행하는데 집중해야 한다는 원칙 입니다. 즉, 클래스는 하나의 책임만을 캡슐화 하고 있어야 한다는 것을 의미 합니다. 예를 들어서 책이라는 클래스가 책에대한 정보를 갖고 있으며 그 정보는 데이터베이스에서 갖고 오도록 설계 됐다면 이것은 SRP 원칙에 위배 되는 클래스 입니다. 왜냐하면 책이라는 클래스가 책에대한 정보를 저장하고 있는 책임을 갖고 있으며 데이터베이스에 접근하는 책임도 갖고 있기 때문 입니다. 이런 경우에 책 이라는 클래스는 두 가지의 이유(책에대한 정보 저장, 데이터베이스 접근)로 수정이 발생 합니다. SRP를 따르면 하나의 클래스는 하나의 책임을 갖고 있으며 해당 클래스를 수정하는 이유는 오직 하나여야만 합니다.




- Open / Closed Principle (개방 / 폐쇄 원칙)


 개방 / 폐쇄 원칙은 확장에는 열려있고 수정에는 닫혀 있어야 한다는 원칙 입니다. 확장에 열려 있어야 한다는 뜻은 유지보수 단계에서 사용자의 요구 사항이 추가 됐을때 수정에 용이해야 한다는 뜻 입니다. 수정에는 닫혀 있어야 한다는 뜻은 마찬가지로 유지보수 단계에서 사용자의 요구 사항을 반영할때 기존의 모듈을 수정 해서는 안된다는 뜻 입니다. 즉, 요구사항이 추가 됐을때 기존 모듈을 수정하지 않으면서 새로운 기능의 추가가 자유로워야 한다는 뜻 입니다. OCP는 상속과 조립(인터페이스)을 이용해서 구현이 가능 합니다. 예를 들어, 사람이라는 클래스와 자동차라는 인터페이스가 있고, 자동차 인터페이스를 구현한 스틱 자동차, 오토 자동차 클래스가 있는 상황이 있다고 합시다. 사람 클래스는 자동차 인터페이스를 통해서 스틱 자동차, 오토 자동차 클래스를 사용하고 있습니다. 그런데 갑자기 고객의 요구사항으로 전기 자동차를 추가해야하는 상황이라면 전기 자동차 클래스를 설계하고 자동차 인터페이스를 구현하면 됩니다. 이렇게 되면 자동차 인터페이스를 구현하는 클래스들은 확장에 열려있고 사람 클래스는 변경에 닫혀있는 상황이 됩니다.




- The Liskov Substitution Principle (리스코프 치환 원칙)

 리스코프 치환 원칙은 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙 입니다. 즉, 부모 클래스가 사용 되는 곳에 자식 클래스로 치환 하더라고 문제가 없어야 한다는 의미 입니다. LSP의 궁극적은 목표는 다형성을 만족시키기 위한 원칙 입니다. 일반적으로 다형성을 만족 시키기 위해서 선언은 부모 클래스로 하고 생성은 자식 클래스로 대입하는 방법을 사용 합니다. 예를 들어서, Storage라는 저장을 담당하는 클래스를 선언하고 해당 클래스를 상속 받는 FileStorage, DBStorage 클래스들을 선언 했다고 하겠습니다. 그리고 FileStorage 클래스에서는 저장을 담당하는 메서드로 fileSave()를 사용하고 DBStorage 클래스에서는 insert()로 선언 했다고 하겠습니다. 이런 경우에 Storage 클래스로 선언하고 다른 파생 클래스로 생성할 경우에 각각의 실제 인스턴스를 비교(instanceof)해서 해당 클래스에 맞는 저장 메서드를 실행 시켜줘야 합니다. 만약 Storage 클래스에 save라는 메서드를 생성하고 파생 클래스들이 저장에 관한 작업을 할 경우에 save 메서드를 오버라이드해서 사용 했다면 위와 같은 불필요한 작업은 발생하지 않았을 것 입니다. 이것이 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙을 따라서 개발을 했을 경우의 장점 입니다.



- Interface Segregation Principle (인터페이스 분리 원칙)

 인터페이스 분리 원칙이란 자신이 사용하지않는 인터페이스는 구현하지 말아야 한다는 원리 입니다. 하나의 통합된 인터페이스보다 다양하게 정의된 인터페이스들을 사용하는 것이 좋다는 것을 의미 합니다. 어떻게 보면 인터페이스의 책임을 분리해야 된다는 말이 되기 때문에 ISP는 인터페이스에대한 SRP라고 할 수 있습니다. 예를 들어서 Animal 이라는 인터페이스를 구현하는 Human이라는 클래스와 Dog라는 클래스가 있다고 하겠습니다. 그리고 Animal 이라는 인터페이스에는 shakeTail() 이라는 메서드가 정의 된 상태라고 하겠습니다. 이런 경우에 Human 클래스는 Animal 이라는 인터페이스를 상속 받고 있기 때문에 Human 클래스에서도 꼬리를 흔드는 shakeTail() 메서드를 구현 해야 합니다. 이런 경우에 Animal 이라는 인터페이스를 여러개로 쪼개서 Dog와 Human 클래스가 서로 다른 인터페이스를 구현 하도록 하는 것이 ISP를 적용한 설계라고 할 수 있습니다. 즉, 인터페이스는 구현을 강제하는 메서드가 적을 수록 좋다는 뜻 입니다. 그리고 추가적으로 상위 클래스는 풍성할 수록 좋고(LSP) 인터페이스는 빈약할 수록 좋습니다.



- Dependency Inversion Principle (의존성 역전 원칙)


 의존성 역전 원칙이란 자주 변경되는 구체 클래스에 의존하면 안된다는 원리입니다. 즉, 추상화된 것은 구체적인 것에 의존하면 안되고 구체적인 것이 추상화된 것에 의존해야한다는 뜻입니다. 왼쪽의 UML을 보면 Car 클래스가 SnowTire 클래스를 의존하고 있는 상황입니다. SnowTire 클래스는 변경이 자주 일어나는 클래스이고 계절이 바뀌면 다시 바꿔줘야 합니다. 이런 경우 Car 클래스가 추상적인 것에 의존하게 변경해줘야 합니다. SnowTire보다 추상적인 Tire 인터페이스를 선언하고 SnowTire가 Tire 인터페이스를 구현합니다. Car 클래스는 *Tire들의 추상적 개념인 Tire 인터페이스에 의존하여 구체화된 클래스가 아닌 추상화된 상위 클래스나 인터페이스에 의존하도록 해야합니다. Car 클래스는 변경에 닫혀있고 Tire의 확장에 열려 있는 상태이기 때문에 OCP와 비슷하게 보일 수 있습니다.

Comments