Posts [개발자 블로그] SOLID 원칙
Post
Cancel

[개발자 블로그] SOLID 원칙

SOLID 원칙 - 로버트 마틴이 주장한 다섯 가지 설계 원칙

1) SRP(단일 책임 원칙, Siingle Responsibility Principle)

  • 단일 책임 원칙이다. 즉, 클래스는 단 하나의 책임만을 가지도록 설계해야 한다는 의미
  • 책임은 ‘해야 하는 것’과 ‘변경 이유’ 두 가지로 해석 될 수 있음
  • 다음 그림은 SRP를 만족하지 않음
  • image
    • 하는 일의 관점: 하나는 임금을 계산하는 것이고 다른 하나는 영역의 합을 콘솔에 출력하는 일이다.
    • 변경 이유의 관점: 임금을 구하는 방식이 변경되면 클래스는 변경되어야 한다. 또한 출력 매체가 파일이나 HTML, XML 등으로 변경될 때에도 클래스는 변경되어져야 한다.

2) OCP(개방 폐쇄 원칙, Open Closed Principle)

  • 기존의 코드를 변경하지 않으면서 새로운 기능을 추가할 수 있도록 설계하는 원칙이다.
  • 무엇이 변화되는지를 생각해야함
  • 다음 그림은 OCP를 만족하지 않음
  • image
    • 임금을 구하는 방식이 변경되면 종업원 클래스의 calculatePay() 메소드를 변경해야 하기에 이는 OCP를 만족하지 않는다.
  • 다음 그림과 같이 임금을 계산하는 Logic을 인터페이스로 두어 수정해야 한다.
  • 스크린샷 2021-03-03 오후 1 34 57

3) LSP(리스코프의 대입 원칙, Liskov Substitution Principle)

  • 일반화 관계를 적절하게 사용했는지를 점검하는 원칙
  • LSP는 일반화 관계는 슈퍼 클래스가 제공하는 오퍼레이션과 파생클래스에서 제공하는 오퍼레이션 간에는 행위적으로 일관성이 있도록 설계가 되어야 한다는 원칙
  • 프로그램에서 슈퍼 클래스의 인스턴스 대신에 파생 클래스의 인스턴스로 대체하여도 프로그램의 의미는 변화되지 않도록 설계 (부모 대신 자신을 대입해도 문제가 없어야 한다)

행위 일관성

  • pre⇒pre’(만약 선조건 pre가 만족된다면 pre’가 만족되어야 한다.)
    • pre: 태풍이 분다(일반 로봇)
    • pre’: 태풍이 불거나 홍수가 난다(인명 구조 로봇)
  • post’⇒post(만약 후조건 post’가 만족된다면 post가 만족되어야 한다.)
    • post’: 사람의 생명 또는 가족의 생명을 구한다(인명 구조 로봇)
    • post: 사람의 생명을 구한다
  • 선조건(precondition): 기능A가 실행 되기 전에 만족되야 하는 조건
  • 후조건(postcondition): 기능시 수행된 후 만족된 조건
  • 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Ex1)
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;
	}
}

public class App {
	
	public static void testLSP(MinMax m) {
	
	ArrayList<Integer> a= new ArrayList<Integer>();
	ArrayList<Integer> b;
	a.add(100);
	a.add(500);
	a.add(50);
	a.add(505);
	a.add(30);
	
	b = m.mimax(a);
	System.out.println("smallest Value:: "+ b.get(0));
	System.out.println("largest Value:: "+ b.get(b.size()-1));
	
	}
	public static void main(String[] args) {
		testLSP(new MinMax());	
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Ex2)
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);
		a.set(0, minValue);
		a.set(a.size()-1, maxValue);
		b=a;
		return b;
	}
}

public class App {
	
	public static void testLSP(MinMax m) {
	
	ArrayList<Integer> a= new ArrayList<Integer>();
	ArrayList<Integer> b;
	a.add(100);
	a.add(500);
	a.add(50);
	a.add(505);
	a.add(30);
	
	b = m.mimax(a);
	System.out.println("smallest Value:: "+ b.get(0));
	System.out.println("largest Value:: "+ b.get(b.size()-1));
	
	}
	public static void main(String[] args) {
		testLSP(new MinMax1());	
	}
}
  • MinMax클래스와 MinMax클래스를 상속받은 MinMax1클래스가 있다.
  • MinMax클래스의 mimax()메소드를 MinMax1클래스에서 오버라이딩하고 있다.
  • MinMax클래스의 mimax()메소드는 최소값과 최대값을 출력할 수 있지만 MinMax1클래스의 mimax()메소드는 최소값만을 출력하기에 행위적으로 일관성이 없음을 의미한다. pre⇒pre1가 만족되지만 ~(post1⇒post)
  • 즉 MinMax클래스의 인스턴스 대신 MinMax1클래스의 인스턴스로 대치 할 수 없다는 것을 의미한다.
  • 행위적으로 일관성을 유지하려면 다음과 같이 수정해야 한다.
1
2
3
4
5
6
7
8
public class MinMax2 extends MinMax{
	public ArrayList<Integer> mimax(ArrayList<Integer> a) {
		ArrayList<Integer> b;
		b=a;
		Collections.sort(b);
		return b;
	}
}
  • MinMax클래스의 인스턴스 대신 MinMax2의 인스터스로 대치 할 수 있다. 즉, 행위적 일관성이 유지 된다. pre⇒pre1가 만족되고 (post2⇒post)

4) ISP(인터페이스 분리 원칙, Interface Segregation Principle)

  • 인터페이스를 클라이언트에 특화되도록 분리시키라는 설계 원칙
  • 클라이언트의 관점에서 클라이언트 자신이 이용하지 않는 기능에는 영향을 받지 않아야 한다는 내용이 담겨 있다.
    image

5) DIP(의존성 역전 원칙, Dependency Inversion Principle)

  • 의존 관계를 맺을 때 변화하기 쉬운 것 또는 변화가 자주 되는 것보다는 변화하기가 어려운 것, 변화가 거의 되지 않는 것에 의존하라는 원칙이다.
  • 그렇다면 변하기 쉬운 것과 변하기 어려운 것은 무엇인가? 가령 정책, 전략과 같은 어떤 큰 흐름이나 개념 같은 추상적인 것은 변하기 어려운 것에 해당하고 구체적인 방식, 사물 등과 같은 것은 변하기 쉬운 것에 해당한다.
  • 객체지향 관점에서 변하기 어려운 추상적인 것들을 표현하는 수단으로 추상 클래스와 인터페이스가 있다.
  • DIP를 만족하기 위해서는 어떤 클래스가 도움을 받을 때는 구체적인 클래스 보다는 인터페이스나 추상 클래스에 의존관계를 맺도록 설계해야 한다.
  • image
  • 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
	private Pet pet;
	public void setPet(Pet pet) {this.pet=pet;}
	public void loves() {
		System.out.println("I love "+pet.toString());
	}
}
public abstract class Pet {}
public class Cat extends Pet {
	public String toString() {
		return "Cat"
	}	
}

public class Main {
	public static void main(String[] args) {
		Pet t = new Cat();
		Person p = new Person();
		p.setPet(t);
		p.loves();
	}
}
  • DIP 따른 설계는 의존성 주입(dependency injection)의 기반
  • 예제에서 사람이 마음이 바뀌어 좋아하는 애완동물이 바뀌어도 전혀 Person, Pet, Cat 등의 기존의 코드에 영향 없이 의존성 주입을 통해 애완동물을 바꿀 수 있다.

출처

  • Java객체지향 디자인패턴(한빛미디어)
This post is licensed under CC BY 4.0 by the author.

[개발자 블로그] 객체지향 원리

[개발자 블로그] 디자인 패턴