Java공부(코딩)

코딩초보의 자바(Java)공부 14일차 { 다형성과 설계 }

동곤일상 2024. 12. 24. 23:43
반응형

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

결제를 종료합니다