728x90
Chapter5 객체지향 설계 5원칙
5.1 SRP - 단일 책임 원칙
- 어떤 클래스를 변경해야 하는 이유는 오직 하나 뿐이다.
- 하나의 클래스에 역할가 책임이 너무 많으면 안된다. 그래서 책임에 따라 여러개의 클래스로 쪼개야한다.
- 단일 책임의 원칙은 속성, 메서드, 패키지, 모듈, 컴포넌트, 그리고 프레임워크 등에도 적용될 수 있는 개념이다.
<단일 책임의 원칙이 적용이 되지 않은 코드>
package srp.bad;
public class 강아지 {
final static Boolean 숫컷 = true;
final static Boolean 암컷 = false;
Boolean 성별;
void 소변보다() {
if (this.성별 == 숫컷) {
// 한쪽 다리를 들고 소변을 본다.
} else {
// 뒤다리 두 개로 앉은 자세로 소변을 본다.
}
}
- 수컷 강아지, 암컷 강아지를 책임지게 되면서 if 분기문을 씀 ( 분기문은 단일 책임 원칙이 지켜지지 않은 경우 나타나는 현상)
<단일 책임의 원칙이 적용 된 코드>
강아지.class
package srp.good;
public abstract class 강아지 {
abstract void 소변보다();
}
숫컷강아지.class
package srp.good;
public class 숫컷강아지 extends 강아지 {
void 소변보다() {
// 한쪽 다리를 들고 소변을 본다.
}
}
암컷강아지.class
package srp.good;
public class 암컷강아지 extends 강아지 {
void 소변보다() {
// 뒤다리 두 개로 앉은 자세로 소변을 본다.
}
}
- 공통점은 강아지 클래스에 두고 강아지 클래스를 상속해서 숫컷강아지와 암컷강아지가 차이점만 각자 구현을 하면 된다.
5.2 OCP - 개방 폐쇄 원칙
- 자신의 확장에는 열려 있고 주변의 변화에 대해서는 닫혀있어야한다.
<개방폐쇄 원칙이 위배되는 경우>
- 운전자가 마티즈를 탔을 때는 인스턴스의 기어수동조작() 메서드를 사용했지만 쏘나타로 바꾸자마자 기어자동조작() 메서드를 사용하게 된다.
<개방폐쇄 원칙을 적용한 경우>
- 상위 클래스 혹은 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다고 해도 객체 지향세계의 운전자는 영향을 받지 않음
- 자신의 확장에는 개방돼 있는 것이고, 운전자 입장에서는 주변의 변화에 폐쇄돼 있는 것이다.
- 개방폐쇄 원칙이 적용되는 또다른 예시 :
JDBC
-오라클에서 mysql로 바뀌더라도 connection을 설정하는 부분 외에는 따로 수정할 필요가 없음
JVM
-개발자가 작성한 소스코드는 운영체제의 변화에 닫혀 있고, 각 운영체제별 JVM 은 확장에 열려 있는 구조가 되는 것이다.
판매 인터페이스
-손님의 구매라는 행위는 직원이 세부적으로 구매 담당자든, 보안 담당자든 심지어 남자에서 여자로 학생에서 노인으로 교대 한다 해도
전혀 영향 받지 않음.
5.3 LCP - 리스코프 치환 원칙
- 서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
- 계층도/조직도일 경우 논리에 맞지 않는다 (리스코프 치환 원칙 위반)
- 분류도일 경우 논리에 잘 맞는다. (리스코프 치환 원칙 성립)
5.4 ISP - 인터페이스 분리 원칙
- 남자의 역할 별로 인터페이스를 분리한다.
- 인터페이스를 통해 메서드를 외부에 제공할 때는 최소한의 메서드만 제공한다. (인터페이스 최소주의 원칙)
- 상위클래스는 풍성할 수록 좋고 인터페이스는 작을 수록 좋다.
<빈약한 상위 클래스를 사용하는 경우>
package poorSuperClass;
import java.util.Date;
public class Driver {
public static void main(String[] args) {
사람 김학생 = new 학생("김학생", new Date(2000, 01, 01), "20000101-1234567",
"20190001");
사람 이군인 = new 군인("이군인", new Date(1998, 12, 31), "19981231-1234567",
"19-12345678");
System.out.println(김학생.이름);
System.out.println(이군인.이름);
// System.out.println(김학생.생일); // 사용불가
// System.out.println(이군인.생일); // 사용불가
System.out.println(((학생) 김학생).생일); // 캐스팅 필요
System.out.println(((군인) 이군인).생일); // 캐스팅 필요
// System.out.println(김학생.주민등록번호); // 사용불가
// System.out.println(이군인.주민등록번호); // 사용불가
System.out.println(((학생) 김학생).주민등록번호);
// 캐스팅 필요
System.out.println(((군인) 이군인).주민등록번호);
// 캐스팅 필요
김학생.먹다();
이군인.먹다();
// 김학생.자다(); // 사용불가
// 이군인.자다(); // 사용불가
((학생) 김학생).자다(); // 캐스팅 필요
((군인) 이군인).자다(); // 캐스팅 필요
// 김학생.소개하다(); // 사용불가
// 이군인.소개하다(); // 사용불가
((학생) 김학생).소개하다(); // 캐스팅 필요
((군인) 이군인).소개하다(); // 캐스팅 필요
((학생) 김학생).공부하다(); // 캐스팅 필요
((군인) 이군인).훈련하다(); // 캐스팅 필요
}
}
- 형변환이 여기저기 발생하면서 상속의 혜택을 보지 못하고 있다.
<풍성한 상위 클래스를 이용>
package richSuperClass;
import java.util.Date;
public class Driver {
public static void main(String[] args) {
사람 김학생 = new 학생("김학생", new Date(2000, 01, 01), "20000101-1234567",
"20190001");
사람 이군인 = new 군인("이군인", new Date(1998, 12, 31), "19981231-1234567",
"19-12345678");
System.out.println(김학생.이름);
System.out.println(이군인.이름);
System.out.println(김학생.생일);
System.out.println(이군인.생일);
System.out.println(김학생.주민등록번호);
System.out.println(이군인.주민등록번호);
// System.out.println(김학생.학번); // 사용불가
// System.out.println(이군인.군번); // 사용불가
System.out.println(((학생) 김학생).학번);
// 캐스팅 필요
System.out.println(((군인) 이군인).군번);
// 캐스팅 필요
김학생.먹다();
이군인.먹다();
김학생.자다();
이군인.자다();
김학생.소개하다();
이군인.소개하다();
// 김학생.공부하다(); // 사용불가
// 이군인.훈련하다(); // 사용불가
((학생) 김학생).공부하다(); // 캐스팅 필요
((군인) 이군인).훈련하다(); // 캐스팅 필요
}
}
- 캐스팅이 자주 일어나지 않는다.
- 소개하다()는 사람 클래스에 공통적으로 있지만 이는 군인과 학생의 경우 다르게 실행이 되야한다. -> 이런 경우 추상메서드를 쓴다.
<좋은 코드>
package richSuperClass;
import java.util.Date;
public abstract class 사람 {
String 이름; // 최대한 공통적인 변수들은 여기에 담는다.
Date 생일;
String 주민등록번호;
void 먹다() {
System.out.println(이름 + " 식사 중");
}
void 자다() {
System.out.println(이름 + " 취침 중");
}
abstract void 소개하다(); // 추상메서드로
}
package richSuperClass;
import java.util.Date;
public class 군인 extends 사람 {
String 군번;
public 군인(String 이름, Date 생일, String 주민등록번호, String 군번) {
this.이름 = 이름;
this.생일 = 생일;
this.주민등록번호 = 주민등록번호;
this.군번 = 군번;
}
void 훈련하다() {
System.out.println(이름 + " 훈련 중");
}
@Override
void 소개하다() {
String msg = "";
msg += "이름: " + 이름;
msg += "\n생일: " + 생일;
msg += "\n주민등록번호: " + 주민등록번호;
msg += "\n군번: " + 군번;
System.out.println(msg);
}
}
package richSuperClass;
import java.util.Date;
public class 학생 extends 사람 {
String 학번;
public 학생(String 이름, Date 생일, String 주민등록번호, String 학번) {
this.이름 = 이름;
this.생일 = 생일;
this.주민등록번호 = 주민등록번호;
this.학번 = 학번;
}
void 공부하다() {
System.out.println(이름 + " 공부 중");
}
@Override
void 소개하다() {
String msg = "";
msg += "이름: " + 이름;
msg += "\n생일: " + 생일;
msg += "\n주민등록번호: " + 주민등록번호;
msg += "\n학번: " + 학번;
System.out.println(msg);
}
}
5.5 DIP - 의존 역전 원칙
- 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다.
<의존 역전 원칙 적용 전>
<의존 역전 원칙 적용 후>
'공부 > JAVA객체지향' 카테고리의 다른 글
자바가 확장한 객체 지향 (0) | 2021.04.29 |
---|---|
자바와 객체 지향 (0) | 2021.04.22 |
자바와 절차적/구조적 프로그래밍 (0) | 2021.04.22 |