Solid를 쓰는 이유?
객체지향 설계가 더 쉬워지고, 유지보수와 확장도 쉬워짐
1. SRP(Single Responsibility Principle) : 단일 책임 원칙
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다."
- 하나의 객체가 하나의 책임을 갖는 것
- 즉, 하나의 객체가 자신이 할 수 있는 것과 해야 하는 것만 수행할 수 있도록 설계되어야 한다!
SRP를 지켜야 하는 이유
응집도와 결합도 관점에서 접근해야 한다.
응집도
- 한 프로그램 요소가 얼마나 뭉쳐있는가를 나타내는 척도.
- 응집도가 높은 클래스는 하나의 책임에 집중하고, 독립성이 높아져 재사용이나 기능의 수정, 유지보수가 용이하다.
결합도
- 클래스 간의 상호 의존 정도.
- 결합도가 낮으면 클래스간의 상호 의존성이 줄어들어 객체의 재사용이나 수정, 유지보수가 용이하다.
Example)
- SRP가 지켜지지 않은 경우
public class Person {
public String job;
public Person(String job)
{
this.job = job;
}
public void Work()
{
if(job.equals("Programmer"))
System.out.println("코딩하다");
else if(job.equals("Teacher"))
System.out.println("수업을 하다.");
}
}
- SRP가 지켜진 경우
public abstract class Person {
abstract public void Work();
}
public class Programmer extends Person {
public void Work()
{
System.out.println("개발을 하다");
}
}
public class Teacher extends Person{
public void Work()
{
System.out.println("학생을 가르치다");
}
}
위 두 코드 다 타 블로그를 참고했지만 직관적으로 차이를 볼 수 있다.
첫 번째 코드는 Programmer와 Teacher 두 직업을 책임지고 있다.
두 번째 코드는 클래스별로 각각의 직업을 책임지고 있다. -> SRP를 지키고 있다!
2. OCP(Open Closed Principle) : 개방 폐쇄 원칙
"기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다!"
- 요구사항이 변경되었을 때, 변경해야 되는 부분과 않아야 하는 부분을 구분해 변경해야 하는 부분을 유연하게 작성하는 것
- 이 부분을 위해 인터페이스를 주로 사용한다.
Example)
- OCP 미적용
public class CarKey {
CarA myCar;
CarKey(CarA car) {
myCar = car;
}
void open() { /* 문 열림 */ }
void lock() { /* 문 닫힘 */ }
void turnOn() { /* 시동 검 */}
}
- OCP 적용
public interface CarKey {
void open();
void lock();
void turnOn();
}
첫 번째 코드에서 만약 새로운 차가 추가되면 똑같은 기능을 또 추가해야 한다.
두 번째 코드에서는 CarKey 인터페이스를 참조한다면 아무리 많은 차를 추가하더라도 똑같은 기능을 추가할 필요 없다.
3. LSP(Liskov Substitution Principle) : 리스 코프 치환 원칙
"자식 클래스는 부모 클래스에게 가능한 행위를 수행할 수 있어야 한다!"
- 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
- 하위 클래스의 인스턴스는 상위 객체 참조 변수에 대입해 상위 클래스의 인스턴스 역할을 하는데 문제가 없어야 한다.
Example)
SRP의 예제를 가져오자.
현재 Person 클래스를 상속받는 Programmer와 Teacher는 Person 역할이 가능하다.
public class Main {
public static void main(String argsp[])
{
Person person1 = new Programmer();
person1.Work();
Person person2 = new Teacher();
person2.Work();
}
}
4. ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
"한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다. 하나의 일반적인 인터페이스보다 여러 개의 구체적인 인터페이스가 낫다!"
- 자신이 사용하지 않는 기능에 영향을 받지 말아야 한다.
- 상위 클래스는 많을수록 좋고, 인터페이스는 적을수록 좋다.
Example)
- ISP 미적용
public class Programmer extends Person {
public void Work()
{
System.out.println("개발을 하다");
}
public void Eating()
{
System.out.println("먹다");
}
public void Sleeping()
{
System.out.println("자다");
}
}
public class Teacher extends Person{
public void Work()
{
System.out.println("학생을 가르치다");
}
public void Eating()
{
System.out.println("먹다");
}
public void Sleeping()
{
System.out.println("자다");
}
}
위 코드를 보면, 각 직업별로 중복되는 기능이 있다.
이 2개의 기능은 공통적으로 가지기 때문에 상위 클래스에서 구현해 하위 클래스에서 재사용하면 더 좋은 설계가 된다.
- ISP 적용
public abstract class Person {
abstract public void Work();
public void Eating()
{
System.out.println("먹다");
}
public void Sleeping()
{
System.out.println("자다");
}
}
이처럼 상위 클래스에서 공통 부분을 구현하고(풍부할수록 좋다), 하위클래스가 따로 구현할 수밖에 없는 메서드만 구현을 강요하도록 한다.(인터페이스가 작을수록 좋다.)
5. DIP(Dependency Inversion Principle) : 의존 역전 원칙
"의존 관계를 맺을 때, 변화하기 쉬운 것보다 변화하기 어려운 것에 의존해야 한다!"
- 자주 변경이 되는 클래스에 대해서 의존하지 말아라.
- 의존관계를 맺을 때, 구체적인 클래스보다 인터페이스나 추상 클래스와 관계를 맺어라.
* 변화하기 쉬운 것 : 구체적인 것
* 변화하기 어려운 것 : 추상적인 것
Example)
만약 사람이 운동화를 신는다고 가정하면, 샌들을 신어야 할 때 변경되어야 한다.
하지만, 위처럼 추상화된 '신발' 인터페이스에 의존한다면 샌들을 신든 운동화를 신든 사람은 영향을 받지 않는다.
출처
'Java' 카테고리의 다른 글
[Java] Object(toString(), equals(), hashCode()) (0) | 2021.08.03 |
---|---|
[Java] StringBuilder vs StringBuffer (0) | 2021.08.03 |
[Java] 다형성, 캐스팅, 추상 클래스, 추상 메서드 (0) | 2021.06.22 |
[Java] 상속, final, 자동 형변환 (0) | 2021.06.22 |
[Java] 객체지향 프로그래밍(OOP)이란? (0) | 2021.06.22 |