취업 준비를 하며 복습한다는 마음으로, 이것을 보는 누군가에게 도움이 되었으면 하는 마음으로
객체지향 개발의 5원칙을 작성해 봅니다. 제가 아는 정보가 틀렸을 수 있습니다.
이 글에 대한 잘못된 정보나, 오탈자 등 수정해야 할 항목 혹은 추가해야 할 항목은 댓글로 알려주시면 감사하겠습니다.
이 자료가 올라가는 저장소 :
https://github.com/hwk0911/Java-tutorial
SOLID란 로버트 마틴이 2000년대 초반 명명한 객체지향 프로그래밍 및 설계의
다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개한 것이다.
객체지향은 하나 이상의 클래스들이 모여 하나의 프로그램을 이룬다.
프로그램이 그 시점에서 필요한, 이번만 쓰고 버릴 프로그램을 만드는 경우는 극히 드물다.
이런 이유로 대부분의 프로그램은 유지보수 및 확장이 필연적으로 일어날 수 밖에 없다.
이 작업을 비교적 쉽게 진행할 수 있게하는 일종의 약속이 객체지향 개발의 5원칙 "SOLID"다.
Java에서만 해당되는것이 아닌 객체지향 프로그래밍 전체를 관통하는 주제다.
Single Responsiblity Principle (SRP)
Open-Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Depedency Inversion Priciple (DIP)
한 클래스, 함수는 하나의 책임만 가져야 한다.
그 책임은 완전히 캡슐화되어야 한다.
이때 '책임'은 하나의 기능으로 생각하면 이해가 쉽다. 즉 클래스를 수정할 이유는 오직 하나여야 한다는 의미이다.
우리가 쉽게 접하는 게임으로 예를 들자면, 무기의 성능을 구현하는 클래스와 무기의 외형을 구현하는 클래스는
분리되어야 한다. 아래의 다이어그램을 살펴보자.
다이어그램을 보면, 데미지를 계산하는 어플리케이션과, 그래픽에 관련된 어플리케이션이 Weapon 클래스를 참조한다.
위와 같은 설계는 SRP를 위반하는 설계인데, Weapon 클래스가 weaponStatus와 draw 두개의 책임을 갖기 때문이다.
이런 설계는 Weapon 클래스를 수정하였을때 참조하는 어플리케이션에 의도하지 않은 영향을 준다.
SRP를 지켜 설계한 다이어그램은 다음과 같다.
위와 같은설계는 완전히 독립된 클래스를 선언해 각각 하나의 책임을 갖도록 분리하여 설계한 다이어그램이다.
데미지를 계산하는 클래스를 새로 선언해 더이상 총의 외형을 구현하는 기능에 영향을 받지 않는다.
처음 책임을 설명할 때, 기능으로 생각하면 이해가 쉽다고 얘기했고, "클래스를 수정할 이유는 오직 하나여야 한다"
고 설명했다. 이 내용을 생각해보면 SRP가 지켜진 설계인지 쉽게 확인할 수 있다.
클래스를 수정할 때, 두개 이상의 영향이 생긴다면, SRP가 지켜지지 못한 설계라는 의미이다.
줄줄이 영향이가는 설계는 좋지 못한 설계이다.
소프트웨어 요소는 확장에는 열려 있으나, 변경에는 닫혀 있어야 한다.
OCP위반을 방지는 인터페이스(Interface)나 추상 클래스(abstract class)의 선언과 이것을 상속하여 클래스를
선언 하는것을 통해 비교적 쉽게 가능하다.
UML다이어그램 그리기 연습을 덜 해서, 화살표 모양을 헷갈렸다...
의존 관계는 아래의 화살표처럼 표현해야 맞다.
새로운 무기 타입을 추가할 때 마다 기존의 클래스는 영향을 받지 않는다.
이런 방식으로 설계를 하면, 확장에는 열려있고, 수정에는 닫혀있는 설계가 된다.
이것은 단위테스트를 진행할 때 매우 중요하며, 유용하게 작용 한다.
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
서브타입은 언제나 자신이 기반타입(base type)으로 교체할 수 있어야 한다. 유도된 클래스의 메소드를 퇴화시키거나 불법으로 만드는 일을 피하라. 기반 클래스의 |
출처 : java 프로그래머를 위한 UML 실전에서는 이것만 쓴다!
위 내용들을 종합하여 쉽게 풀면, 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 한다.
로 볼 수 있다. 즉 일반화 관계에 대한 원칙이다.
예를 들어, 창과 근접무기의 관계를 보자. 누가봐도 근접무기 = 부모 클래스, 둔기 = 자식 클래스 로 볼 수 있다.
이때 자식 클래스인 창이 부모 클래스인 근접무기의 기능을 수행 가능한지 확인해보자.
위와 같이 근접 무기의 특징이 둔기의 특징에 적용이 가능하다.
예시와 마찬가지로, 부모 클래스가 수행하는 일을 자식 클래스가 가능하다면 LSP에 맞게 설계되었다 볼 수 있다.
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
클라이언트는 자신이 사용하는 메소드에만 의존해야 한다는 원칙이다.
예를 들어, 게임 클라이언트를 구성하는 데, 관련된 인터페이스가 아닌 공부와 관련된 메소드를 implement 하면 안된다.
우리는 게임을 할 땐 공부를 하지 않는 것 처럼 각 각 독립된 인터페이스로 설계해야 한다.
언젠간 쓰겠지 하는 마음으로 하나의 거대한 인터페이스를 선언하는 것은 바보같은 행동이다.
의미없는 코드들은 늘어나고, 구조도 깔끔하지 못하다.
프로그래머는 "추상화"에 의존해야지, 구체화에 의존하면 안 된다.
상위 클래스는 하위 클래스에 의존해서는 안된다는 아주아주 당연한 원칙이다.
언터페이스는 변하지 않는 것 으로 선언하고, 구체 클래스는 변하기 쉬운 것 으로 설계해야 한다.
DIP를 위반한 다이어그램은 다음과 같다.
Weapon 클래스가 WeaponType 인터페이스에 의존하고, WeaponType 인터페이스는 근접, 원거리, 마법 무기로 부터
상속 받는 내용이다. 이 경우 인터페이스는 변하기 쉬운 것으로 선언했고, 구체 클래스는 고정된 것으로 선언했다.
DIP를 완벽하게 위반한 다이어그램이다.
더 나은 개발자를 위해 (0) | 2020.02.11 |
---|