- S = Single Responsibility Principle = 단일 책임 원칙
- O = Open-Closed Principle = 개방 폐쇄 원칙
- L = Liskov Substitution Principle = 리스코프 치환 원칙
- I = Interface Segregation Principle = 인터페이스 분리의 원칙
- D = Dependency Inversion Principle = 의존관계 역전의 원칙
SRP(Single Responsibility Principle) 단일 책임 원칙 - 클래스는 오직 책임이 하나여야 한다. 요구사항이 변경 되었을때는 변경 요인이 하나여야 한다.
하나의 클래스, 하나의 함수는 하나의 기능만을 수행하도록 개발되어야 한다.
하나의 클래스가 여러가지 동작을 수행한다면 코드를 수정하기 어렵다.
OCP(Open-Closed Principle) 개방 폐쇄의 원칙 - 클래스는 확장에는 열려 있지만, 수정에는 닫혀 있어야 한다.
기능을 변경, 확장할 수는 있지만 그 기능을 쓰는 코드는 수정하지 말아야 한다.
코드를 통해서 살펴보겠다. 아래와 같이 동작하는 코드가 있다고 해보자.
public class Main {
public static void main(String[] args) {
}
}
public class UseJava {
public void hello(){
System.out.Println("java");
}
}
여기서 만약에 파이썬을 사용하려고 한다면 코드를 아마도 이렇게 바꿔야 할 것이다.
public class Main {
public static void main(String[] args) {
}
}
public class UsePython {
public void hello(){
System.out.Println("python");
}
}
다시 자바를 쓰도록 바꾼다면 코드를 아래와 같이 다시 바꿔야 할 것이다.
public class Main {
public static void main(String[] args) {
}
}
public class UseJava {
public void hello(){
System.out.Println("java");
}
}
바뀔때마다 이렇게 계속 코드를 고치는 것은 불편하다.
인터페이스를 사용해서 개방 폐쇄 원칙을 지킬 수 있다.
아래 코드처럼 말이다.
public class Main {
UseLanguage useLanguage = new UseJava();
public static void main(String[] args) {
}
}
public interface UseLanguage {
public void hello();
}
public class UseJava implements UseLanguage {
public void hello(){
System.out.Println("java");
}
}
public class UsePython implements UseLanguage {
public void hello(){
System.out.Println("java");
}
}
필요에 따라서 interface에 객체를 다르게 생성해서 사용 할 수 있다.
클래스의 개방 폐쇄 원칙
인터페이스에서만 이렇게 사용 하는 것이 아니고 클래스에서도 쓸 수 있다.
상위 클래스에 메소드를 정의하고 하위 클래스에서 상세 구현을 하면서 확장 할 수 있다.
리스코프 치환 원칙 - 서브 클래스는 수퍼클래스가 사용 되는 곳에 대체 될 수 있어야 한다.
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.
코드를 통해서 살펴본다.
public class Main {
public static void main(String[] args) {
IpsMonitor monitor = new IpsMonitor();
monitor.setTouch(false);
monitor.isTouch(); // return false
IpsMonitor ipad = new Ipad();
ipad.setTouch(false);
ipad.isTouch(); // return true -> false 기능을 의도했지만 ipad 와 모니터는 다르기 때문에
// 상속으로 구현 할 수 없다. -> 하위 클래스인 아이패드가 상위 클래스인 모니터를 치환 할 수 없다.
}
}
public class Ipad extends IpsMonitor{
public boolean touch;
public void setTouch(boolean touch) {
this.touch = touch;
}
@Override
public boolean isTouch(){
return true;
}
}
public class IpsMonitor {
public boolean touch;
public void setTouch(boolean touch) {
this.touch = touch;
}
public boolean isTouch() {
return this.touch;
}
}
코드를 보면 ips monitor 클래스는 touch 속성을 false 로 가진다. 이때 ipad 클래스를 정의한다.
ipad 클래스도 모니터라고 생각해서 ips monitor 로부터 상속 받아서 구현했다고 해봤다.
이렇게 되면 상위 클래스에서 의도한 touch 속성에 대한 값이 ipad 클래스에서는 다르게 된다.
처음부터 ips monitor 와 ipad 는 상위, 하위 클래스로 구현 되면 안됐다.
리스코프 치환 원칙을 어기고 구현을 했기 때문에 하위 클래스인 ipad 를 사용했을때 상위 클래스인 ips monitor 에서 의도한 것과 다르게 동작한다.
상위 클래스에서 정의한 명세대로 구현되고 하위 타입의 객체로 치환해도 정상적으로 동작해야 한다.
이게 리스코프 치환 원칙이다.
인터페이스 분리의 원칙 - 인터페이스는 구현 스펙을 정의한다. 이를 분리하라는 것은 거대한 클래스가 있다면 그것을 쪼개라는 것이다.
사용하지 않는 기능을 오버라이드 해서 구현 하면 안 된다.
코드를 통해서 살펴본다.
public class Main {
public static void main(String[] args) {
}
}
public interface Utility {
public void keyValue();
public void minusIndex();
}
public class UseJava implements Utility{
@Override
public void keyValue() {
System.out.Println("Map");
}
@Override
public void minusIndex() {
System.out.Println("last item");
}
}
public class UsePython implements Utility {
@Override
public void keyValue() {
System.out.Println("dict");
}
@Override
public void minusIndex() {
System.out.Println("last item");
}
}
코드는 프로그래밍 언어를 쓰는 상황을 가정했다.
코드에서는 utility 인터페이스를 사용해서 minusIndex 함수를 정의하고 있다.
UseJava 와 UsePython 클래스에서는 Utility 인터페이스를 구현 하고 있기때문에 minusIndex 함수를 구현해야 한다.
그런데 자바에서는 배열에 -1 인덱스를 쓸 수 없다.
코드를 맞게 고쳐보겠다.
public class Main {
public static void main(String[] args) {
}
}
public interface Utility {
public void keyValue();
}
public interface MinusIndex {
public void minusIndex();
}
public class UseJava implements Utility{
@Override
public void keyValue() {
System.out.Println("Map");
}
}
public class UsePython implements Utility, MinusIndex {
@Override
public void keyValue() {
System.out.Println("dict");
}
@Override
public void minusIndex() {
System.out.Println("last item");
}
}
인터페이스를 2개로 나누었다. 클래스도 너무 크다면 나눠서 구현한다.
DIP(Dependency Inversion Policy) 의존 역전 원칙 - 구현에 의존하기 보다는 인터페이스에 의존하도록 코딩한다.
고 수준 모듈은 저 수준 모듈의 구현에 의존해서는 안된다
저 수준 모듈이 고 수준 모듈에서 정의한 추상 타입에 의존해야 한다.
코드로 예를 들면 아래와 같은 코드는 DIP 에 위배된다.
public class HighLevel {
LowLevel low = new LowLevel();
public static void use() {
low.hello();
}
}
public class LowLevel {
public static void hello() {
System.out.Println("hello");
}
}
HighLevel 클래스에서 LowLevel 클래스의 구현을 그대로 사용하고 있다.
이것을 인터페이스에 의존하도록 바꿔야 한다.
public class HighLevel {
LowInterface low = new LowLevel();
public static void use() {
low.hello();
}
}
public interface LowInterface {
public void hello();
}
public class LowLevel implements LowInterface {
public static void hello() {
System.out.Println("hello");
}
}
LowInterface 인터페이스를 선언했다.
그리고 LowLevel 클래스에서 이것을 구현하도록 했다.
고수준인 HighLevel에서는 LowInterface 인터페이스에 LowLevel 클래스의 객체를 대입했다.
이렇게 하면 인터페이스를 사용하도록 구현 할 수 있다.
'Java' 카테고리의 다른 글
java로 외부 파일 실행하기 (0) | 2019.06.06 |
---|---|
Mac OS gradle 설치 , 환경 변수 설정 (4) | 2019.05.29 |
Java stream().map().collect() 사용해보기 (0) | 2019.01.11 |
UML 통합 모델링언어 / 클래스 다이어그램 (0) | 2017.10.11 |
자바 죽음의 다이아몬드 문제 / java Deadly Diamond of Death (0) | 2017.09.28 |
댓글