2023. 1. 26. 10:07ㆍ개발일지
단일 책임 원칙
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다" - 로버트 C. 마틴
- '하나의 객체는 단 하나의 책임을 가져야 한다'
- 왜? 객체는 꼭 하나의 책임을 가져야 하는 걸까요?
단 하나의 책임을 갖는 객체
- 예를 들어서 회사-개발자, 국방부-예비군, 여자친구-남자친구 이런식으로 개발자 클래스, 예비군 클래스, 남자친구 클래스와 같이 역할과 책임을 클래스별로 분리하여 연관이 있는 클래스와 각각 따로 관계를 맺는 것이 서로 영향을 미치지 않아야 합니다.
- Calculator에게 주어진 책임과 역할이 너무 큽니다.
- 각각의 연산자의 연산 과정에 변화가 생겼을 때 Calculate 메서드의 크기가 비대해 지거나 다른 연산 과정에 영향을 미칠 수 있습니다.
개방-폐쇄 원칙
"소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다." -로버트 C. 마틴
('자신의 확장에는 열려 있고, 주변의 변화에 대해서는 닫혀 있어야 한다.')
Java의 Database 연결
- Java에는 데이터베이스(Database)에 접속 할 수 있도록 도와주는 JDBC라는 API가 존재하며, 이 JDBC를 개방-폐쇄 원칙의 예로 들 수 있습니다.
- 개발을 할 때 Oracle, MySQL 등 다양한 데이터베이스를 사용하게 되며, 이때 만약 개발이 진행되는 과정에서 데이터베이스(Database)를 변경하게 된다고 하더라도 우리가 그동안 개발하던 코드를 전부 수정할 필요는 없습니다.
- 연결할 때 필요한 ID, PWD, URL 등의 설정을 담당하는 부분만 변경하면 쉽게 데이터베이스를 변경할 수 있습니다.
- 연산자를 추가하려고 할 때 코드의 변화가 너무 많이 일어납니다.
- if else 조건문 블록이 존재하며 연산자 추가 시 코드변화가 빈번하게 일어납니다.
원칙 적용 방법
- 변화되는 부분을 추상화해서 변화를 고정 시키며 기능이 추가될 때 클래스의 상속을 통해서 하위 클래스에서 기능을 구현 하도록 합니다.
- 기존코드를 수정하지 않아도 객체 상속의 다향성 원리에 의해 기능이 확장 되도록 합니다.
- 연산 클래스는 추상화된 부모 클래스를 상속받아 기능별로 구현합니다.
AbstractOperation : 추상 클래스
- operate : 추상 메서드
리스코프 치환 원칙
"서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다." - 로버트 C. 마틴
- '하위 클래스의 인스턴스는 상위형 객체 참조변수에 대입해 상위 클래스의 인스턴스 역할을 하는 데 문제가 없어야 한다'
- 리스코프 치환 원칙 : 치환성(substitutability)은 객체 지향 프로그래밍 원칙이며, 컴퓨터 프로그램에서 자료형 S가 자료형 T의 서브타입이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 한다는 원칙
원칙 위반 증상
- 객체의 타입을 확인합니다.(instanceof)
- 자식 클래스명이 연관되거나 의존성이 있는 클래스에서 자주 발생합니다.
- 추가적인 기능이 필요한 자식 클래스가 존재하기 때문에 타입 확인을 진행하고 있습니다.
- 따라서 자식 클래스의 인스턴스가 부모클래스의 인스턴스 역할을 수행하는데 문제가 없다고 할 수 없습니다.
원칙 적용 방법
- 부모와 자식 클래스 사이의 행위가 일관성이 있도록 추상화를 좀 더 정교하게 구현합니다.
- 연산 기능을 추상화한 부모 클래스에 피연산자 값의 유효성 검사를 진행하는 메서드를 추가해 줍니다.
- 계산기 클래스에서는 이 메서드를 사용하는 유효성 검사를 진행하고 이 유효성 검사가 필요한 자식 클래스에서는 이 추가된 유효성 검사 조건을 구체화합니다.
인터페이스 분리 원칙
"클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안 된다." - 로버트 C. 마틴
- '특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다'
- 클라이언트가 필요하지 않는 기능을 가진 인터페이스에 의존해서는 안되고 최대한 인터페이스를 작게 유지해야 합니다.
원칙 위반 증상
- 필요하지 않은 기능을 강제로 구현해야하는 상황이 발생합니다.
- 필요하지 않은 혹은 사용 못 하는 기능이 강제로 구현되어 사용하지 못하도록 예외처리를 해야하는 상황이 발생 할 수 있습니다.
- 필요하지 않는 기능을 강제로 구현하고 있습니다.
- 사용하지 못하도록 예외처리를 구현하고 있습니다.
원칙 적용 방법
- 필요하지 않은 기능을 강제로 구현하지 않도록 인터페이스를 분리합니다
- 연산 결과를 보여주는 방법마다 인터페이스를 구현합니다.
DisplayResult : 인터페이스, 연산결과만 출력
- displayResult : 추상 메서드
DisplayWithOperator : 인터페이스, 연산과정 포함 출력
- displayResultWithOperator : 추상 메서드
의존성 역전 원칙
"고차원 모듈은 저차원 모듈에 의존하면 안 된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다."
"추상화된 거은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다."
"자주 변경되는 구체(Concrete) 클래스에 의존하지 마라" - 로버트 C. 마틴
- 세부적인 사항은 추상화에 의존해야 합니다.
- 의존성 역전 원칙을 적용한 대표적 사례로는 이전 OCP 에서 설명했던 JDBC에서 찾아볼 수 있습니다.
- 또한 OCP에서 본 계산기 예제도 마찬가지입니다.
원칙 위반 증상
- 저수준 모듈에서 변경이 발생되면 고수준 모듈에 수정사항이 발생합니다.
- 계산기(고수준 모듈)가 개별 연산 클래스(저수준 모듈)을 포함하며 계산 기능을 구현할 때 연산 클래스에 의존하고 있습니다.
원칙 적용 방법
- 고수준 모듈의 변화되는 부분을 추상화합니다.
- 저수준 모듈을 추상화에 의존시킵니다.
- 계산기 클래스에 추상화된 부모 클래스를 포함시킵니다.
- 연산 클래스는 추상화된 부모 클래스를 상속받아 기능별로 구현합니다.
AbstractOperation : 추상 클래스
- operate : 추상 메서드