Home [Design Pattern] SOLID 원칙
Post
Cancel

[Design Pattern] SOLID 원칙

로버트 마틴이 주장한 5가지의 설계원칙

SRP(Single Responsbility Principle)

객체지향 설계 관점에서는 SRP에서 말하는 책임의 기본 단위는 객체를 말한다. 즉, 객체는 단 하나의 책일만 가져야 한다는 의미다.

책임이란?

→ 해야 하는 것, 할 수 있는것으로 객체에 책임을 할당할 때는 어떤 객체보다도 작업을 잘할 수 있는 객체에 할당해야 한다.

예를 들어 Student 클래스가 수강 과목을 추가하거나 조회하고, 데이터베이스에 객체 정보를 저장하거나 데이터베이스에서 객체 정보를 읽는 작업도 처리하고, 성적표와 출석부에 출력하는 일도 실행한다고 가정하자. 그럼 코드는 다음과 같다.

1
2
3
4
5
6
7
8
public class Stduent{
		public void getCourses() {...}
		public void addCourse(Course c) {...}
		public void save() {...}
		public Student load() {...}
		public void printOnReportCard() {...}
		public void printOnAttenddanceBook() {...}
}

지금 이대로라면 Student 클래스에는 너무나 많은 책임을 수행해야 한다. 즉, Student 클래스에는 단한가지 일만 할 수 있도록 단 나의 책임만 가져야 한다. 책임은 또한 변경이유라고 해석할 수 있으며 만약 학생 정보를 성적표와 출석부 이외에 형식으로 출력한다면 Student 클래스도 변경 되어야 하기 때문에 한 책임은 변화의 이유로도 볼 수 있다.

따라서 Student 클래스는 책임을 분리해서 SRP를 적용해야 하며 Student 클래스의 경우 학생 고유의 역할을 수행하게끔 Student 클래스를 수정해야 한다.

1
2
3
public class Stduent{
		public Student load() {...}
}

OCP(Open Closed Principle)

기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙

Student 클래스로 예를 들어보면

Untitled1.png

만약 이 구조에서 출력형식을 json으로 바꾸게된다면?

그러면 Studnet 클래스에서 코드를 수정해야 하며 OCP원칙에 어긋나게 된다. 따라서 OCP를 만족하는 설계가 되기 위해서는 다음과 같은 단계를 거치면 된다.

  1. 변화되는것을 식별
  2. 변화되는것을 클래스로 분리
  3. 변하는것을 포용하는 개념인 추상클래스나 인터페이스로 추상화한다.
  4. 추상클래스나 인터페이스를 이용하여 자식클래스로 모델링한다.

위의 Student 클래스에서 변화되는것은 출력하는 방식으로 print()가 변경 되므로 이것을 클래스로 분리시킨다. 그런후 변화되는것을 포용하는 개념인 Printer라는 인터페이스를 만든 후 적용하면 다음과 같다.

Untitled1.png

위와 같이 변화되는것을 식별하고 인터페이스를 만든 후 자식클래스에서 구현하도록 만들면 OCP 원칙에 만족하는 설계가 된다.

LSP(Liskov Substitution Principle)

일반화 관계를 적절하게 사용했는지를 점검하는 원칙

다음 정렬하는 예제를 살펴보면

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MinMax {
		public ArrayList<Integer> mimax(ArrayList<Integer> a) {
			int minValue;
			int maxValue;
			ArrayList<Integer> b;
			b=a;
			minValue = Collections.min(a);
			maxValue = Collections.max(a);
			b.set(0, minValue);
			b.set(a.size()-1, maxValue);
			return b;
		}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class MinMax1 extends MinMax{
		public ArrayList<Integer> mimax(ArrayList<Integer> a){
				int minValue;
				int maxValue;
				ArrayList<Integer> b;
				minValue = Collections.min(a);
				maxValue = Collections.min(a);
				b.set(0, minValue);
				b.set(a.size()-1, maxValue);
				return b;
		}
}

MinMax 클래스와 MinMax1 클래스를 보면 MinMax 클래스는 가장 큰값과 가장 작은값을 출력하는 반면 MinMax1은 가장 작은값만 출력한다. 즉, MinMax1은 MinMax 클래스를 상속하여 구현하였지만 부모 클래스와 결과가 달라 이둘은 행위적으로 일관성이 없게 된다. 따라서 LSP를 만족하려면 자식클래스도 부모클래스와 같이 가장 큰값과 가장 작은 값을 출력하도록 설계를 해야 한다.

ISP(Interface Seperate Principle)

인터페이스를 클라이언트에 특화시키도록 분리시키는 설계 원칙

복합기 기능을 제공하는 클래스로 예를 들어보자

1
2
3
4
5
public interface PrintingService {
    public void print();
    public void fax();
    public void copy();
}

PrintingServie를 이용해 구현한 복합기 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HansungPrinter implements PrintingService {
    @Override
    public void print() {
        System.out.println(" 한성프린터 출력");
    }

    @Override
    public void fax() {
        throw new UnsupportedOperationException("Not Supported");
    }

    @Override
    public void copy() {
        throw new UnsupportedOperationException("Not Supported");
    }
}

위 클래스를 보면 HansungPrinter는 fax기능과 copy기능을 사용하지 않고 상속되는 문제가 발생하게 된다. 그래서 만약 HansungPrinter가 제공하지 않는 fax()나 copy() 인터페이스에 변화가 생긴다면 변경으로 인해 발생하는 문제의 영향을 받지 않도록 해야한다. (예를 들어 copy(int pages) 처럼 copy() 연산에 복사할 페이지 수를 인자로 요구하도록 변경)

즉, ISP는 인터페이스를 클라이언트에 특화되도록 분리시키는 원칙이므로 위의 예제를 ISP를 만족하게 설계하면 다음과 같은 구조로 이루질 수 있다.

Untitled2.png

DIP(Dependency Inversion Principle)

상위 모듈은 하위 모듈에 의존하면 안되며 이 두 모듈 모두 다른 추상화된 것에 의존해야 하고 구체적인 것이 추상화된 것에 의존해야 한다는 원칙

의존 관계를 설정할 때에는 구체적인 클래스 보다는 이를 추상화한 개념과 인터페이스 같은 수단을 이용하여 관계를 맺도록 설계해야 한다.

DIP가 적용되기 전의 클래스 다이어그램을 보면

Untitled3.png

위와 같은 설계는 상위모듈이 하위모듈에 의존관계가 생기게 되어 만약 하위모듈이 변화된다면 자연스럽게 상위 모듈도 변경해야 되어 DIP를 만족하지 못하는 설계가 되어버린다. 따라서 상위모듈과 하위모듈 모두 인터페이스 즉 추상화된것에 의존관계를 맺도록 의존관계를 역전 시키게 설계해야 DIP를 만족하게 된다.

DIP를 만족한 설계

Untitled4.png

위와 같이 상위모듈과 하위모듈 모두 추상화된것에 의존하게 되며 DIP를 만족하게 된다.

[프로젝트] marvel_website

[Design Pattern] 싱글톤 패턴