다형성 3
역할과 구현 에제를 통해 더 이해해 보자!!
public interface Car {
void startEngine();
void offEngine();
void pressAccelerator();
}
-------------------------------------------------------------------
public class K3Car implements Car {
@Override
public void startEngine() {
System.out.println("K3Car Engine started");
}
@Override
public void offEngine() {
System.out.println("K3Car Engine off");
}
@Override
public void pressAccelerator() {
System.out.println("K3Car press accelerator");
}
}
-------------------------------------------------------------------
public class Model3Car implements Car {
@Override
public void startEngine() {
System.out.println("Model3Car startEngine");
}
@Override
public void offEngine() {
System.out.println("Model3Car offEngine");
}
@Override
public void pressAccelerator() {
System.out.println("Model3Car pressAccelerator");
}
}
-------------------------------------------------------------------
public class Driver {
private Car car;
public void setCar(Car car) {
System.out.println("자동차를 설정합니다" + car);
this.car = car;
}
public void drive(){
System.out.println("자동차를 운전합니다.");
car.startEngine();
car.pressAccelerator();
car.offEngine();
}
}
---------------------------------------------------------------------
public class CarMain1 {
public static void main(String[] args) {
Driver driver = new Driver();
//차량 선택(K3)
K3Car k3Car = new K3Car();
driver.setCar(k3Car);
driver.drive();
//차량 변경 (K3 ->model3)
Model3Car model3Car = new Model3Car();
driver.setCar(model3Car);
driver.drive();
}
}

먼저 Driver와 K3 Car를 생성한다.
driver.setCar(k3 Car)를 호출해서 Driver의 Car car 필드가 K3 Car의 인스턴스를 참조하도록 한다.
driver.drive()를 호출하면 x001을 참조한다.
car 필드가 Car 타입이므로 Car 타입을 찾아서 실행하지만 메서드 오버라이딩에 의해 K3 Car의 기능이 호출된다.
Model3 Car를 생성한다.
driver.setCar(model3 Car)를 호출해서 Driver의 Car car 필드가 Model3 Car의 인스턴스를 참조 하도록 변경한다.
driver.drive() 를 호출하면 x002을 참조한다.
car 필드가 Car 타입이므로 Car 타입을 찾아서 실행 하지만 메서드 오버라이딩에 의해 Model3Car 의 기능이 호출된다.
OCP(Open-Closed Principle) 원칙
좋은 객체 지향 설계 원칙 중 하나로 OCP 원칙이라는 것이 있다.
-Open for extension: 새로운 기능의 추가나 변경 사항이 생겼을 때, 기존 코드는 확장할 수 있어야 한다.
-Closed for modification: 기존의 코드는 수정되지 않아야 한다. (예?)
확장에는 열려있고, 변경에는 닫혀 있다는 뜻인데, 쉽게 이야기해서 기존의 코드 수정 없이 새로운 기능을 추가할 수 있다는 의미다. 약간 말이 안 맞는 것 같지만 우리가 앞서 개발한 코드가 바로 OCP 원칙을 잘 지키고 있는 코드다.

- 코드 수정은 닫혀 있다는 의미 새로운 차를 추가하게 되면 기능이 추가되기 때문에 기존 코드의 수정은 불가피하다. 당연히 어딘가의 코드는 수정해야 한다.
- 변하지 않는 부분 새로운 자동차를 추가할 때 가장 영향을 받는 중요한 클라이언트는 바로 Car의 기능을 사용하는 Driver이다. 핵심은 Car 인터페이스를 사용하는 클라이언트인 Driver의 코드를 수정하지 않아도 된다는 뜻이다.
- 변하는 부분 main()과 같이 새로운 차를 생성하고 Driver에게 필요한 차를 전달해 주는 역할은 당연히 코드 수정이 발생한다. main()은 전체 프로그램을 설정하고 조율하는 역할을 한다. 이런 부분은 OCP를 지켜도 변경이 필요하다.
- 정리 Car를 사용하는 클라이언트 코드인 Driver 코드의 변경 없이 새로운 자동차를 확장할 수 있다.
다형성을 활용하고 역할과 구현을 잘 분리한 덕분에 새로운 자동차를 추가해도 대부분의 핵심 코드들을 그대로 유지할 수 있게 되었다.
전략 패턴(Strategy Pattern)
: 디자인 패턴 중 가장 중요한 패턴을 하나 꼽으라면 전략 패턴을 들 수 있다.
전략 패턴은 클라이언트 코드를 변경하지 않고도 알고리즘을 쉽게 교체할 수 있게 해 준다.
방금 설명한 코드가 바로 전략 패턴을 적용한 예시다.
여기서 Car 인터페이스가 전략을 정의하는 인터페이스 역할을 하고, 각 차량 클래스가 전략의 구체적인 구현체가 된다.
이를 통해 클라이언트 코드(Driver)를 수정하지 않고도 전략을 손쉽게 교체할 수 있다.
글에 게시하기에는 너무 다양한 코드들이 있어서, 내가 푼 예제들을 말로 정리하고 넘어가야겠다.
문제 1: 다중 메시지 발송
한 번에 여러 곳에 메시지를 발송하는 프로그램을 개발하자. 다음 코드를 참고해서 클래스를 완성하자
- 요구사항 다형성을 활용하세요.
Sender 인터페이스를 사용하세요. EmailSender , SmsSender , FaceBookSender를 구현하세요
public class SendMain {
public static void main(String[] args) {
Sender[] senders = {new EmailSender(), new SmsSender(), new FaceBookSender()};
for (Sender sender : senders) {
sender.sendMessage("환영합니다!");
}
}
}
----------------------------------------------------------------
public interface Sender {
void sendMessage(String message);
}
- Sender intergace에 message를 매개변수로 받는 메서드를 만들었다.
- 각각의 클래스에서 ‘어디서 보내는지”만 출처를 바꿔서 구현한 이후 메인에서 실행하였다.
문제 2: 결제 시스템 개발
여러분은 기대하던 결제 시스템 개발팀에 입사하게 되었다. 이 팀은 현재 2가지 결제 수단을 지원한다. 앞으로 5개의 결제 수단을 추가로 지원할 예정이다. 새로운 결제수단을 쉽게 추가할 수 있도록, 기존 코드를 리펙토링 해라.
- 요구사항 OCP 원칙을 지키세요.
메서드를 포함한 모든 코드를 변경해도 됩니다. 클래스나 인터페이스를 추가해도 됩니다. 단 프로그램을 실행하는 PayMain0 코드는 변경하지 않고, 그대로 유지해야 합니다. 리펙토링 후에도 실행 결과는 기존과 같아야 합니다.
public class PayMain1 {
public static void main(String[] args) {
PayService payService = new PayService();
//카카오 결제
String option1 = "kakao";
int amount1 = 5000;
payService.processPay("kakao", 5000);
//네이버 결제
String option2 = "naver";
int amount2 = 10000;
payService.processPay("naver", 10000);
//삼성 결제
String option3 = "samsaung";
int amount3 = 15000;
payService.processPay("samsaung", 15000);
//잘못된 결제
String option4 = "samsaung123";
int amount4 = 15000;
payService.processPay("samsaung123", 15000);
}
}
----------------------------------------------------------------------
public abstract class PayStore {
//결제 수단 추가 시 변하는 부분
public static Pay findPay(String option){
if (option.equals("kakao")){
return new KakaoPay();
}else if ((option.equals("naver"))){
return new NaverPay();
}else if (option.equals("samsung")){
return new SamSungPay();
}else if (option.equals("google")){
return new GooglePay();
}else{
return new DefaultPay();
}
}
}
이 문제는 다형성의 원칙에 맞혀 리팩토링 하는 것이 핵심이었다.

- 여러 결제 시스템(카카오, 네이버, 구글)의 인터페이스인 Pay를 만들었다.
- PayStore라는 추상 클래스를 만들어 Pay들의 저장소를 만들었다.
- PayService에서는 결제 수단들을 PayStore에서 찾아 결괏값을 반환시키고, 그 후에 메인에서 실행했다.
느낀 점 :
다형성을 비롯하여 자바의 기본을 공부하면서 느낀 점은 기본기가 엄청나게 부족하다는 것이다.
아직까진 기본형/참조형 타입도 내가 자유자재로 쓸 수 있을지도 모르겠고, 강의에 맞춰했던 예제들을 스스로 아무 도움 받지 않고 할 수 있을 지도 미지수다. 하지만 개발의 꽃이라 하는 '객체 지향 프로그래밍'에 대해 많이 부족하지만 그래도 깨달은 점이 많다.
왜 해야 되는지? 어떻게 해야되는지? 에 대해서도 많이 배웠고 , 결국 내가 할 일은은 물리적인 시간을 소비하면서 이 모든 것을 ‘체화’하는 것이다.
스스로 다시 한번 , 내 정리를 보면서 정말 내 걸로 만들어야겠다.필요한 것은 당연 물리적인 시간 + 노력 + 자기주도 이다.
많은 시간을 소요하더라도, 내가 그 시간을 자기주도적으로 하지않고 무의미하게 소비한다면, 내 지식이 될 수 없을 것이다.
내 단점은 한 번 무너지면 , 회복하는 데 꽤나 오랜 시간이 걸린다. 내 단점을 인지하고, 절대 무너지지 않고 꾸준히 노력해야겠다.
(오늘은 진지하게 끝내겠습니다. feat 궁서체)
'Language > ☕ Java' 카테고리의 다른 글
| [Java] 불변 객체 (0) | 2024.08.20 |
|---|---|
| [Java] Object 클래스 (0) | 2024.08.19 |
| [Java] 다형성 2 (0) | 2024.08.14 |
| [Java] 다형성이란? (2) | 2024.08.13 |
| [Java] 상속 (0) | 2024.08.12 |