SRP (단일책임의 원칙: Single Responsibility Principle)
작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임(변화의 축: axis of change)을 수행하는 데 집중되어 있어야 한다는 원칙입니다. 이는 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 함을 의미합니다. SRP원리를 적용하면 무엇보다도 책임 영역이 확실해지기 때문에 한 책임의 변경에서 다른 책임의 변경으로의 연쇄작용에서 자유로울 수 있습니다. 뿐만 아니라 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이라는 이점까지 누릴 수 있으며 객체지향 원리의 대전제 격인 OCP원리뿐 아니라 다른 원리들을 적용하는 기초가 됩니다. 이 원리는 다른 원리들에 비해서 개념이 비교적 단순하지만, 이 원리를 적용해서 직접 클래스를 설계하기가 그리 쉽지만은 않습니다. 왜냐하면, 실무의 프로세스는 매우 복잡 다양하고 변경 또한 빈번하기 때문에 경험이 많지 않거나 도메인에 대한 업무 이해가 부족하면 나도 모르게 SRP원리에서 멀어져 버리게 됩니다. 따라서 평소에 많은 연습(‘책임’이란 단어를 상기하는)과 경험이 필요한 원칙입니다.
정리
Class
와Method
는 하나의 '역할'만 하도록 해야 한다. 만약 상태와 행위가 변화의 시기와 이유가 달라진다면 클래스나 메소드를 분리해야 한다.
OCP (개방폐쇄의 원칙: Open Close Principle)
버틀란트 메이어(Bertrand Meyer)박사가 1998년 객체지향 소프트웨어 설계 라는 책에서 정의한 내용으로 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원리입니다. 이것은 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야 한다는 의미로, 요구사항의 변경이나 추가사항이 발생하더라도, 기존 구성요소는 수정이 일어나지 말아야 하며, 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻입니다. 로버트 C. 마틴은 OCP는 관리가능하고 재사용 가능한 코드를 만드는 기반이며, OCP를 가능케 하는 중요 메커니즘은 추상화와 다형성이라고 설명하고 있습니다. OCP는 객체지향의 장점을 극대화하는 아주 중요한 원리라 할 수 있습니다.
정리
확장에는 열려있고, 변경에는 닫혀있어야 한다. 변경이나 추가사항이 발생해도 기존 코드의 수정이 최소한으로 발생해야한다.
LSP (리스코브 치환의 원칙: The Liskov Substitution Principle)
이 원칙은 5가지 원칙 중에서 좀처럼 쉽게 이해 되지 않는 원칙의 하나로 LSP라는 이름에서는 도저히 원칙에 대한 내용을 도출 할 수 없는 원칙입니다. LSP를 한마디로 한다면, “서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다.”라고 할 수 있습니다. 즉, 서브 타입은 언제나 기반 타입과 호환될 수 있어야 합니다. 달리 말하면 서브 타입은 기반 타입이 약속한 규약(public 인터페이스, 물론 메소드가 던지는 예외까지 포함됩니다.)을 지켜야 합니다. 상속은 구현상속(extends 관계)이든 인터페이스 상속(implements 관계)이든 궁극적으로는 다형성을 통한 확장성 획득을 목표로 합니다. LSP원리도 역시 서브 클래스가 확장에 대한 인터페이스를 준수해야 함을 의미합니다. 다형성과 확장성을 극대화 하려면 하위 클래스를 사용하는 것보다는 상위의 클래스(인터페이스)를 사용하는 것이 더 좋습니다. 일반적으로 선언은 기반 클래스로 생성은 구체 클래스로 대입하는 방법을 사용합니다. 생성 시점에서 구체 클래스를 노출시키기 꺼려질 경우 생성 부분을 Abstract Factory 등의 패턴을 사용하여 유연성을 높일 수 있습니다. 상속을 통한 재사용은 기반 클래스와 서브 클래스 사이에 IS-A관계가 있을 경우로만 제한 되어야 합니다. 그 외의 경우에는 합성(composition)을 이용한 재사용을 해야 합니다. 상속은 다형성과 따로 생각할 수 없습니다. 그리고 다형성으로 인한 확장 효과를 얻기 위해서는 서브 클래스가 기반 클래스와 클라이언트 간의 규약(인터페이스)를 어겨서는 안 됩니다. 결국 이 구조는 다형성을 통한 확장의 원리인 OCP를 제공 하게 됩니다. 따라서 LSP는 OCP를 구성하는 구조가 됩니다. 객체지향 설계 원리는 이렇게 서로가 서로를 이용하기도 하고 포함하기도 하는 특징이 있습니다. LSP는 규약을 준수하는 상속구조를 제공 합니다. LSP를 바탕으로 OCP는 확장하는 부분에 다형성을 제공해 변화에 열려있는 프로그램을 만들 수 있도록 합니다.
정리
서브 타입은 기반 타입과 호환이 되어야 한다. 즉 서브 클래스는 슈퍼 클래스의 형태로 사용 가능해야한다. 이를 바탕으로 확장에 용이해 진다.
List list1 = new ArrayList();
List list2 = new LinkedList();
ISP (인터페이스 분리의 원칙: Interface Segregation Principle)
ISP원리는 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리입니다. 즉 어떤 클래스가 다른 클래스에 종속될 때에는 가능한 최소한의 인터페이스만을 사용해야 합니다. ISP를 ‘하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다’라고 정의할 수 도 있습니다. 만약 어떤 클래스를 이용하는 클라이언트가 여러 개고 이들이 해당 클래스의 특정 부분집합만을 이용한다면, 이들을 따로 인터페이스로 빼내어 클라이언트가 기대하는 메시지만을 전달할 수 있도록 합니다. SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조합니다. 하지만 ISP는 어떤 클래스 혹은 인터페이스가 여러 책임 혹은 역할을 갖는 것을 인정합니다. 이러한 경우 ISP가 사용되는데 SRP가 클래스 분리를 통해 변화에의 적응성을 획득하는 반면, ISP에서는 인터페이스 분리를 통해 같은 목표에 도달 합니다.
정리
어떠한 인터페이스를 구현할 때 꼭 사용하는 것만 구현해야한다. 다른 클레스에서 사용하지 않는다면 인터페이스를 분리해 추가 구현하자.
public interface Animal {
void eat();
void cry();
}
public interface Bird {
void fly();
}
public interface Mammal {
void run();
}
public class Cat implements Animal,Mammal {
@Override
public void eat() {
System.out.println("냠냠");
}
@Override
public void cry() {
System.out.println("냐옹");
}
@Override
public void run() {
System.out.println("사뿐사뿐");
}
}
public class Parrot implements Animal,Bird {
@Override
public void eat() {
System.out.println("쩝쩝");
}
@Override
public void cry() {
System.out.println("안녕");
}
@Override
public void fly() {
System.out.println("푸더더덕");
}
}
DIP (의존성역전의 원칙: Dependency Inversion Principle)
의존 관계의 역전 Dependency Inversion 이란 구조적 디자인에서 발생하던 하위 레벨 모듈의 변경이 상위 레벨 모듈의 변경을 요구하는 위계관계를 끊는 의미의역전입니다. 실제 사용 관계는 바뀌지 않으며, 추상을 매개로 메시지를 주고 받음으로써 관계를 최대한 느슨하게 만드는 원칙입니다.
DIP는 복잡하고 지난한 컴포넌트간의 커뮤니케이션 관계를 단순화하기 위한 원칙입니다. 실 세계에서도 헐리우드 원칙에서와 같이 귀찮도록 자주 질문과 요청을 하는 동료에게도 써먹어 볼만한 원칙입니다.
정리
자주 변화하는 것보다 변화하기 어려운 것, 변화가 거의 없는 것에 의존하라는 것. 구체적인 클래스로 의존관계를 맺지말고 추상화 된 인터페이스를 구현해 의존관계를 맺어 확장에 용이하게 한다.
public class Farm {
private Animal animal;
public void init(Animal animal) {
animal = animal;
}
public void check() {
animal.cry();
}
public void giveFood() {
animal.eat();
}
}
public interface Animal {
void eat();
void cry();
}
public class Bard implements Animal {
@Override
public void eat() {
System.out.println("콕콕콕");
}
@Override
public void cry() {
System.out.println("안녕");
}
}
public class Chicken implements Animal {
@Override
public void eat() {
System.out.println("콕콕콕");
}
@Override
public void cry() {
System.out.println("꼬꼬");
}
}
객체지향 개발 5대 원리를 지켜야 하는 이유
5원칙을 만족하면 유지 보수가 용이 해지고 가독성도 상승한다. 그리고 코드의 재사용이 가능하며 추가 요구사항 구현할 때 다른 코드들의 변경사항을 최소화 할 수 있다.또한 의존관계를 추상화된 인터페이스로 받게 되면서 다형성도 보장 할 수 있다.
5대 원리를 따르면 확장성, 다형성, 가독성, 재사용성을 보장할 수 있다.
참고
http://www.nextree.co.kr/p6960/
https://wkdtjsgur100.github.io/solid-principle/