객체지향 코드 설계 5가지 원칙(SOLID)
코드 설계 원칙(SOLID)
SOLID은 SRP(단일책임원칙), OCP(개방 폐쇄 원칙), LSP(리스코프 치환원칙), DIP(의존역전 원칙), ISP(인터페이스 분리 원칙)의 앞글자를 따서 만들어졌다. SOLID를 지키고 설계를 한다면 시간이 지나고 유지보수와 확장이 쉬운 소프트웨어로 개발할 수있고 프로그램 설계에서는 어떠한 원칙을 정하고 그것을 기반으로 프로그램을 작성한다면 원칙 없이 작성한 코드보다 좋은 결과를 볼 수 있다.
단일 책임 원칙 (Single Responsibility Principle)
객체 지향 프로그래밍에서 단일 책임 원칙(Single Responsibility Principle)은 모든 클래스는 하나의 책임만 가지며 클래스는 그 책임을 완전히 캡슐화 해야한다. 클래스가 제공하는 모든 기능은 이 책임과 주의 깊게 부합해야한다는 말이다. 단일책임 하나의 책임 즉 하나의 기능만 가지는 것인데, 이것은 코드가 변경이 되더라도 변경사항에 대한 책임이 있는 부분만 수정하면 된다.
💡 특정 데이터를 분석하여 서버로 전송하는 모듈을 생각해보자
- 데이터를 분석하고 알고리즘때문에 변경이 될 수있다.
- 서버로 전송하는 형식 때문에 변경될 수 있다.
단일 책임에 의하면 이 문제의 두 측면이 실제로 분리된 두 책임 때문에 따라서 분리된 두 책임 때문이며 따라서 분리된 클래스나 모듈로 나눠야한다. 다른 시기에 다른 이유로 변경되어야 하는 두가지 묶는 것은 나쁜 설계일 수 있다.
한 클래스에서 한가지의 관심사에 집중하도록 유지하는 것이 중요한 이유는 이것이 클래스를 더욱 튼튼하게 하기 때문이다. 앞서 든 예를 계속 살펴보면, 편집과정에 변경이 일어나면 같은 클래스의 일부로 있는 출력 코드가 망가질 수 있기 때문이다.
개방 폐쇠 원칙 (Open Closed Principle)
개방 폐쇠 원칙 (Open Closed Principle)원칙은 소프트웨어가 확장에 대해서 열려 있어야하고 수정에 대하서 닫혀있어야 한다는 원칙이다. 어떠한 내용을 수정하기 위해 연관된 다른 코드나 모듈까지 수정하는 것은 어렵고 난감하다. 개방 폐쇄 원칙은 시스템의 구조를 올바르게 구성하여 변경사항이 발생하더라도 다른 코드나 모듈에 영향이 없도록 하는 것이다.
개방-폐쇄 원칙이 잘 적용된 경우 새로운 기능을 추가하거나 기존 기능을 변경하기에 용이해지고, 객체지향의 핵심 원칙이라 할 수 있다. 원칙을 따르지 않는다해서 구현이 불가능한것은 아니지만 이원칙을 무시한다면 객체지향의 큰 장점 유연성 재사용성, 유지 보수성등 장점을 가져갈 수 없다.
리스코프 치환 원칙(Liskov Substitution Principle)
💡 리스코프의 원칙은 표준적인 요구사항을 강제로 한다.
- 하위 클래스에서 메서드 파라미터의 반공변성
- 하위 클래스에서 반환형의 공변성
- 하위 클래스에서 메서드는 상위 클래스 메서드에서 던져진 예외 사항을 제외하고 새로운 예외 사항을 던지면 안된다.
- 하위 클래스에서 선행 조건은 강화될 수 없음
- 하위 클래스에서 후행 조건을 약화할 수 없음
- 하위형에서 상위의 불변 조건은 반드시 유지되어야한다.
치환성은 객체 지향 프로그램밍 원칙이다. 만약 클래스 A가 클래스 B의 자식 클래스라면 별다른 변경없이 부모 클래스 B를 자식 클래스 A로 치환할 수 있어야 한다는 원칙이다. 즉 다운 캐스팅된 인스턴스가 논리적으로 그 역할이 문제 없어야한다는 것이다.
Public class A{}
Public class B extends A{}
Public class C extends B{}
인터페이스 분리 원칙(Interface Segregation Principle)
인터페이스 분리 원칙은 어떠한 클래스가 자신이 이용하지 않는 메서드에 의존하지 않아한다는 원칙이다. 인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고 작은 단위로 분리함으로써 클래스들이 꼭 필요한 메서드들만 이용할 수 있게 한다. 이와같이 작은 단위들을 역할 인터페이스라고도 부른다. 인터페이스분링 ㅝㄴ칙을 통해 시스템의 내부 의존성을 약화해 리팩토링, 수정, 재배포를 쉽게할 수있다.
public abstract class Bird{
abstract void fly()
abstract void cry()
}
public class Eagle extends Bird{
@Override
public void fly(){...}
@Override
public void cry(){...}
}
위 코드와 같이 Bird라는 추상 클래스를 만들어 새의 울음소리를 내고 날수있는 기능을 가진 메서드를 만든뒤 Bird를 상속받은 Eagle을 만든다. 이때 Penguin 클래스를 만든다면 펭귄은 새지만 날지는 못하므로 fly()메서드를 가진다면 ISP 인터페이스 분리 원칙(Interface Segregation Principle)원칙에 어긋날 수 있다는 것이다.
public abstract class Bird{
abstract void cry()
}
public interface Flyable{
abstract void fly()
}
public abstract class FlyableBird extends Bird implements Flyable{...}
public class Penguin extends Bird{
@Override
public void cry(){...}
}
이러한 형식으로 구현한다면 코드의 문제점을 해결할 수 있다. fly()메서드를 인터페이스로 분리하고 날 수 있는 새에만 구현함으로써 펭귄은 사용하지않은 fly() 메서드를 가지기 않을 수 있어 IPS원칙을 지킬 수 있다. 만약 펭귄이 수영이란 메서드를 추가하고싶다면 Swimmable()인터페이스에 swim()메서드를 만들어 볼 수 있다.
의존 역전 원칙 (Dependency inversion principle)
객체 지향프로그래밍에서 의존 연적 우너친ㄱ은 모듈들을 분리하는 특정 형식을 지칭한다. 이 원칙을 따르면 사우이 계층이 하위 계층에 의존하는 전통적인 의존 관계를 역전시킴으로써 상위 계층이 하위계층의 구현으로 부터 독립되게 할 수 있다. 이 원칙은 다음과 같은 내용을 담고 있다.
- 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야한다.
- 추사화는 세부 사항에 의존해서는 안된다. 세부사항이 추상황에 의존해야한다.
이 원칙은 상위와 하위 객체 모두 동일한 추상화에 의존해야한다는 객체지향적 대원칙을 제공한다.
옛날 핸드폰으로 예시로 할 수 있다. 옛날 핸드폰은 대부분 제조사마다 전용 충천지가 있었다. 전용 충전기, 전용 크레이들이 있어서 다른 기종의 충전기로는 충전하지 못하는 경우가 흔했다. 어떤 기기가 단 한가지 종류의 전용 충전기에만 충전이되고 다른 충전기로는 호환이 되지 않는다면 전용 충전기에 강한 의존을 가진다 말할 수 있다.
하지만 요즘 안드로이드 스마트폰을 보면 C타입으로 되어있다. C타입 선을 어느제조사가 만들어도 호환이 된다. 제조사 구분 없이 C타입 규격만 맞다면 충전할 수 있기 때문이다.
개인으로 공부하다보니 부정확한 정보가 있을 수 있습니다. 잘못된 부분은 댓글로 지적해주시면 감사하겠습니다.