2024.12.23 - [Java공부(코딩)] - 코딩초보의 자바(Java)공부 13일차 { 다형성 활용 }
코딩초보의 자바(Java)공부 13일차 { 다형성 활용 }
2024.12.22 - [Java공부(코딩)] - 코딩초보의 자바(Java)공부 12일차 { 다형성 } 코딩초보의 자바(Java)공부 12일차 { 다형성 }객체지향 프로그래밍의 대표적인 특징으로는 캡슐화, 상속, 다형성이 있어요
ddkk1120.tistory.com
여기서 공부한것을 토대로 설계에 어떻게 적용할지
얘기를 해보고
예제문제를 통해 더 쉽게
이해해보도록 해요!
1) 다형성
다형성의 실제 비유
키보드 , 마우스
배우 - 역할
자동차 - 운전자
처럼
역할과 구현을 분리
역할과 구현을 분리하면
단순해지고 , 변경에 용이해진다.
역할 = 인터페이스
구현= 인터페이스를 구현 할 클래스,구현객체
클라이언트는 대상의 역할만 알면 됨
클라이언트는 구현대상의 내부구조를 몰라도 됨
클라이언트 : 요청
서버 : 응답
역할과 구현을 분리(정리)
유연하고 변경에 용이
확장 가능한 설계
클라이언트에 영향을 주지않는 변경기능
역할과 구현을 분리( 한계)
역할(인터페이스) 변경 시
클라이언트 , 서버 모두 변화가일어남
자동차를 비행기로 변경해야한다면?
정리하자면
인터페이스를 안정적으로 설계하는것이 중요
스프링의 핵심인 IOC(제어의 역전)
DIP(의존관게 주입) 도 결국
다형성을 이용하는 것.
2)다형성 - 역할과구현 예제1
운전자와 차의 관계를 다형성을 고려하지않고
설계해보겠습니다
public class G80 {
public void startEngine() {
System.out.println("G80.startEngine");
}
public void offEngine() {
System.out.println("G80.offEngine");
}
public void pressAccel() {
System.out.println("G80.pressAccelerator");
}}
///////////////////////////////////////
public class Driver {
private G80 g80;
public void setCar(G80 g80) {
this.g80 = g80;
}
public void drive() {
System.out.println("G80을 운전합니다");
g80.startEngine();
g80.pressAccel();
g80.offEngine();}}
메인코드
public class CarMain {
public static void main(String[] args) {
G80 g80 = new G80();
Driver driver = new Driver();
driver.setCar(g80);
driver.drive();
}}
driver에게 G80인스턴스의 참조값을 넘겨준다.
얼핏보면 잘 만들었다고 생각할 수도있다.
하지만 운전자가 이 G80만 운전 할 수있는건아니다
차를 바꿀수도있다.
차량을 추가한다면 클라이언트 , 서버 모든것을 바꿔야한다.
각 자동차에 의존하지않고 역할(인터페이스)에 의존하게끔
만들어보자 !
( 다형성을 활용해 이문제를 해결해보자)
3)다형성 - 역할과구현 예제2
제가 생각한 다이어그램은 이렇습니다.
Driver가 클라이언트가
Car가 서버가 되는것 입니다
Driver는 구체적인 구현체에 의존하지않고
역할(Car)에 의존
긴말. 없이 코드로 일단 보죠
역할(인터페이스)
public interface Car {
void stratEngine();
void pressAccel();
void endEngine();
}
구현체(서버)
public class DN8 implements Car{
@Override
public void stratEngine() {
System.out.println("DN8의 시동을 켭니다");
}
@Override
public void pressAccel() {
System.out.println("Dn8의 엑셀을 밟습니다");
}
@Override
public void endEngine() {
System.out.println("Dn8의 시동을 끕니다");
}}
다른 구현체들도 안에 내용물만 다르고
메서드는 같으므로 생략할게요!!!
클라이언트(Driver)
public class Driver {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void drive() {
car.stratEngine();
car.pressAccel();
car.endEngine();}}
setCar를 통해 Car의 구현체들만이
참조로 들어올 수 있게됨
들어온 인스턴스를 참조해
drive메서드 실행
메인코드
public class CarMain2 {
public static void main(String[] args) {
Car dn8 = new DN8();
Driver driver = new Driver();
driver.setCar(dn8);
driver.drive();
//차량변경
Car g80 = new G80();
driver.setCar(g80);
driver.drive();
}}
출력
차량을 탑승 : poly.car1.DN8@6ce253f1
DN8의 시동을 켭니다
Dn8의 엑셀을 밟습니다
Dn8의 시동을 끕니다
차량을 탑승 : poly.car1.G80@65ab7765
G80의 시동을 켭니다
G80의 엑셀을 밟습니다
G80의 시동을 끕니다
OCP(Open-Closed Principle) 원칙
좋은 객체 지향 설계 원칙 중 하나로 OCP 원칙이라는 것이 있다.
**Open for extension**: 새로운 기능의 추가나 변경 사항이 생겼을 때, 기존 코드는 확장할 수 있어야 한다.
**Closed for modification**: 기존의 코드는 수정되지 않아야 한다.
**정리**
`Car` 를 사용하는 클라이언트 코드인
`Driver` 코드의 변경없이
새로운 자동차를 확장할 수 있다.
다형성을 활용하고 역할과 구현을 잘 분리한 덕분에
새로운 자동차를 추가해도 대부분의 핵심 코드들을
그대로 유지할 수 있게 되었다.
4) 예제풀이
결제 시스템 개발
여러분은 기대하던 결제 시스템 개발팀에 입사하게 되었다.
이 팀은 현재 2가지 결제 수단을 지원한다. 앞으로 5개의 결제 수단을 추가로 지원할 예정이다.
새로운 결제수단을 쉽게 추가할 수 있도록, 기존 코드를 리펙토링해라.
**요구사항**
OCP 원칙을 지키세요.
메서드를 포함한 모든 코드를 변경해도 됩니다.
클래스나 인터페이스를 추가해도 됩니다.
단 프로그램을 실행하는 `Main` 코드는 변경하지 않고, 그대로 유지해야 합니다.
리펙토링 후에도 실행 결과는 기존과 같아야 합니다.
변경전
public class KakaoPay {
public boolean pay(int amount) {
System.out.println("카카오페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}}
//////////////////////////////////////////////////////////////////////////////////////////
public class NaverPay {
public boolean pay(int amount) {
System.out.println("네이버페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;}}
//////////////////////////////////////////////////////////////////////////////////////////
public class PayService {
public void processPay(String option, int amount) {
boolean result;
System.out.println("결제를 시작합니다: option=" + option + ", amount=" +
amount);
if (option.equals("kakao")) {
KakaoPay kakaoPay = new KakaoPay();
result = kakaoPay.pay(amount);
} else if (option.equals("naver")) {
NaverPay naverPay = new NaverPay();
result = naverPay.pay(amount);
} else {
System.out.println("결제 수단이 없습니다.");
result = false;
}
if (result) {
} else {
System.out.println("결제가 성공했습니다.");
System.out.println("결제가 실패했습니다.");
}}}
//////////////////////////////////////////////////////////////////////////////////////////
public class Main {
public static void main(String[] args) {
PayService payService = new PayService();
//kakao 결제
String payOption1 = "kakao";
int amount1 = 5000;
payService.processPay(payOption1, amount1);
//naver 결제
String payOption2 = "naver";
int amount2 = 10000;
payService.processPay(payOption2, amount2);
//잘못된 결제 수단 선택
String payOption3 = "bad";
int amount3 = 15000;
payService.processPay(payOption3, amount3);
}}
변경된 코드 및 출력
역할 (인터페이스)
public interface Pay {
boolean pay(int amount);
}
구현체(역할을 구현하는 객체)
public class NaverPay implements Pay {
@Override
public boolean pay(int amount) {
System.out.println("네이버페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public class KakaoPay implements Pay{
@Override
public boolean pay(int amount) {
System.out.println("카카오페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public class TossPay implements Pay { //확장에 열려있는것을 증명하기위해 추가한 구현체
@Override
public boolean pay(int amount) {
System.out.println("toss페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return false;
}}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public class DefaultPay implements Pay {
@Override
public boolean pay(int amount) {
System.out.println("잘못된 결제수단임");
return false;
}}
추상클래스
(static메서드 : 존재하는 결제수단인지 확인용)
equalsIgnoreCase == 대소문자 구분없이 동등성비교
결제수단을 찾지못했을떄는 null체크 없이
DefaultPay 반환
public abstract class PayStore {
//결제 수단 추가시 변하는 부분
public static Pay findPay(String option) {
if (option.equalsIgnoreCase("kakao")) {
return new KakaoPay();
} else if (option.equalsIgnoreCase("naver")) {
return new NaverPay();
}else if(option.equalsIgnoreCase("toss")) {
return new TossPay();
}
else {
return new DefaultPay();
}
}}
PayService는 구체적인 구현체가아닌
Pay(인터페이스)에 의존 즉
결제수단을 추가해도 코드변경X
public class PayService {
public void processPay(String option, int amount) {
boolean result;
System.out.println("결제를 시작합니다: option=" + option + ", amount=" +
amount);
Pay pay = PayStore.findPay(option);
result = pay.pay(amount);
if(result) {
System.out.println("결제에 성공했어요");
}
else {
System.out.println("결제 실패");
}}}
메인코드
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
PayService payService = new PayService();
//kakao 결제
while(true) {
System.out.println("결제수단 입력(종료하려면 exit)"
+ "");
String option = scan.nextLine();
if(option.equals("exit")) {
System.out.println("결제를 종료합니다");
break;
}
System.out.println("금액 입력(0원 입력시 종료)");
int amount = scan.nextInt();
if(amount==0) {
System.out.println("프로그램종료");
break;
}
payService.processPay(option, amount);
scan.nextLine(); //nextInt()는 \n까지 읽어들이지않으므로 nextLine()으로 없애주자
}}
}
출력예시
결제수단 입력(종료하려면 exit)
KakaO //equalusIgnore 덕분에 대소문자를 섞어도 인식가능
금액 입력(0원 입력시 종료)
1000
결제를 시작합니다: option=KakaO, amount=1000
카카오페이 시스템과 연결합니다.
1000원 결제를 시도합니다.
결제에 성공했어요
결제수단 입력(종료하려면 exit)
nAvER
금액 입력(0원 입력시 종료)
1900
결제를 시작합니다: option=nAvER, amount=1900
네이버페이 시스템과 연결합니다.
1900원 결제를 시도합니다.
결제에 성공했어요
결제수단 입력(종료하려면 exit)
Kbcard
금액 입력(0원 입력시 종료)
10
결제를 시작합니다: option=Kbcard, amount=10 //구현체로정의하지않은것을 입력으로넣으면 DefalutPay반환
잘못된 결제수단임
결제 실패
결제수단 입력(종료하려면 exit)
exit
결제를 종료합니다
'Java공부(코딩)' 카테고리의 다른 글
코딩초보의 자바(Java)공부 16일차 { String 클래스 } (4) | 2024.12.29 |
---|---|
코딩초보의 자바(Java)공부 15일차 { Object클래스 } (2) | 2024.12.25 |
코딩초보의 자바(Java)공부 13일차 { 다형성 활용 } (0) | 2024.12.23 |
코딩초보의 자바(Java)공부 12일차 { 다형성 } (0) | 2024.12.22 |
코딩초보의 자바(Java)공부 11일차 { 상속 } (2) | 2024.12.21 |