프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
특징 - 추상화, 캡슐화, 상속, 다형성
역할과 구현


자바 언어의 다형성

클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
결국엔 인터페이스를 안정적으로 설계하는 것이 가장 중요!
ex) 자동차 -> 비행기 인터페이스 자체가 변하면 서버 모두에 큰 변경이 발생!!!
간단한 운전자와 자동차의 관계
package poly.car0;
public class K3Car {
public void startEngine() {
System.out.println("K3Car.startEngine");
}
public void offEngine() {
System.out.println("K3Car.offEngine");
}
public void pressAccelerator() {
System.out.println("K3Car.pressAccelerator");
}
}
package poly.car0;
public class Model3Car {
public void startEngine() {
System.out.println("Model3Car.startEngine");
}
public void offEngine() {
System.out.println("Model3Car.offEngine");
}
public void pressAccelerator() {
System.out.println("Model3Car.pressAccelerator");
}
}
package poly.car0;
public class Driver {
private K3Car k3Car; // 자동차 객체를 담는 private 변수
private Model3Car model3Car; // 추가
public void setK3Car(K3Car k3Car){ //밖에서 k3car를 설정해줄 수 있다. setK3Car를 통해서만 접근 가능하게 해준다.
this.k3Car = k3Car; // 외부에서 자동차 객체를 설정할 수 있게 하는 public 메서드
}
//추가
public void setModel3Car(Model3Car model3Car) { // set을 쓰면 메소드를 알아서 만들어줆.
this.model3Car = model3Car;
}
public void drive() {
System.out.println("자동차를 운전합니다");
if (k3Car != null) {
k3Car.startEngine();
k3Car.pressAccelerator();
k3Car.offEngine();
} else if (model3Car != null) {
model3Car.startEngine();
model3Car.pressAccelerator();
model3Car.offEngine();
}
}
}

private를 까먹음. 그래서 이해를 못했음.
package poly.car0;
public class Model3Car {
public void startEngine() {
System.out.println("Model3Car.startEngine");
}
public void offEngine() {
System.out.println("Model3Car.offEngine");
}
public void pressAccelerator() {
System.out.println("Model3Car.pressAccelerator");
}
}
package poly.car0;
public class Driver {
private K3Car k3Car;
private Model3Car model3Car; //추가
public void setK3Car(K3Car k3Car) {
this.k3Car = k3Car;
}
//추가
public void setModel3Car(Model3Car model3Car) {
this.model3Car = model3Car;
}
//변경
public void drive() {
System.out.println("자동차를 운전합니다.");
if (k3Car != null) {
k3Car.startEngine();
k3Car.pressAccelerator();
k3Car.offEngine();
} else if (model3Car != null) {
model3Car.startEngine();
model3Car.pressAccelerator();
model3Car.offEngine();
}
}
}
main 추가
Model3Car model3Car = new Model3Car();
driver.setK3Car(null);
driver.setModel3Car(model3Car);
driver.drive();

추가해야 하는 차량의 종류가 계속 늘어나면 점점 더 변경해야 하는 코드가 많아질 것이다.
package poly.car1;
public interface Car {
void StartEngine();
void offEngine();
void pressAccelerator();
}
package poly.car1;
public class CarMain1 {
public static void main(String[] args) {
Driver driver = new Driver();
//차량선택(k3)
K3Car k3Car = new K3Car();
//Car car = k3Car;
driver.setCar(k3Car);
driver.drive();
//차량변경(k3 -> model3Car)
Model3Car model3Car = new Model3Car();
driver.setCar(model3Car);
driver.drive();
//차량변경(model3Car -> newCar)
NewCar newCar = new NewCar();
driver.setCar(newCar);
driver.drive();
}
}
package poly.car1;
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();
}
}
package poly.car1;
public class NewCar implements Car {
@Override
public void StartEngine() {
System.out.println("NewCar.StartEngine");
}
@Override
public void offEngine() {
System.out.println("NewCar.offEngine");
}
@Override
public void pressAccelerator() {
System.out.println("NewCar.pressAccelerator");
}
}

NewCar를 넣어도 기존코드는 변경 없이 추가만 할 수 있음.
: 기존의 코드 수정 없이 새로운 기능을 추가할 수 있다라는 의미
open for extension - 새로운 기능의 추가나 변경 사항이 생겼을 때, 기존 코드는 확장할 수 있어야 한다.
Closed for modification - 기존의 코드는 수정되지 않아야 한다.
다형성을 활용하고 역할과 구현을 잘 분리한 덕분에 새로운 자동차를 추가해도 대부분의 핵심 코드들을 그대로 유지할 수 있게 되었다.
문제
요구사항
package poly.ex.pay0;
public class KakaoPay {
public boolean pay(int amount) {
System.out.println("카카오페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}
}
package poly.ex.pay0;
public class NaverPay {
public boolean pay(int amount) {
System.out.println("네이버페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}
}
package poly.ex.pay0;
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) {
System.out.println("결제가 성공했습니다.");
} else {
System.out.println("결제가 실패했습니다.");
}
}
}
package poly.ex.pay0;
public class PayMain0 {
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);
}
}

정답은 없고 payService의 변경을 최소화 할 수 있다면 성공!
해설:
1. 인터페이스를 만든다. boolean과 int amount는 상속받을 예제의 코드를 보고 적음.
package poly.ex.Pay1;
public interface Pay {
boolean pay(int amount);
}
package poly.ex.Pay1;
public class KakaoPay implements Pay{
@Override
public boolean pay(int amount) {
System.out.println("카카오페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}
}
package poly.ex.Pay1;
public class NaverPay implements Pay{
@Override
public boolean pay(int amount) {
System.out.println("네이버페이 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}
}
Pay pay;로 선언했다. result = kakaoPay.pay(amount); -> result = pay.pay(amount);로 바뀌고 if절 밖으로 빼서 한번만 적도록 설계했다. 설계하는 과정에서 pay = null;과 if절을 넣어서 nullpointerexception을 방지했다.
nullpointerexception- 실제 값이 아닌 null을 가지고 있는 객체/변수를 호출할 때 발생하는 예외다.
boolean result = false;
if (option.equals("kakao")) {
pay = new KakaoPay();
} else if (option.equals("naver")) {
pay = new NaverPay();
} else {
pay = null;
System.out.println("결제 수단이 없습니다.");
}
}
if (pay != null){
result = pay.pay(amount);
}
3.위의 코드를 메서드로 뽑는다. (클래스를 만드는 것이 아니라 밑에다가 메서드를 뽑는 과정)
Pay pay = findPay(option);
public Pay findPay(Sring option) {
boolean result = false;
if (option.equals("kakao")) {
pay = new KakaoPay();
} else if (option.equals("naver")) {
pay = new NaverPay();
} else {
pay = null;
System.out.println("결제 수단이 없습니다.");
}
return pay;
}
그런데 굳이 pay를 선언할 필요없이 return을 하면 되기 때문에 수정
public Pay findPay(Sring option) {
boolean result = false;
if (option.equals("kakao")) {
return new KakaoPay();
} else if (option.equals("naver")) {
return new NaverPay();
} else {
System.out.println("결제 수단이 없습니다.");
return null;
}
}
payService를 건드리지 않을려면 수정된 위의 코드를 PayStore클래스로 만들어준다.package poly.ex.Pay1;
public abstract class PayStore {
public static Pay findPay(String option) {
Pay pay;
if (option.equals("kakao")) {
return new KakaoPay();
} else if (option.equals("naver")) {
return new NaverPay();
}else if (option.equals("new")) { //추가된 코드 6번에서
return new NewPay();
}
else {
return new DefaultPay();
}
}
}
NewPay를 만든다.package poly.ex.Pay1;
public class NewPay implements Pay{
@Override
public boolean pay(int amount) {
System.out.println("NewPay 시스템과 연결합니다.");
System.out.println(amount + "원 결제를 시도합니다.");
return true;
}
}
newpay에 관련된 코드들을 추가해준다.//new 결제 main함수 추가
String payOption4 = "new";
int amount4 = 7000;
payService.processPay(payOption4, amount4);
null if절 없애기(nullpointerexception)DefaultPay 클래스로 따로 빼주기package poly.ex.pay1practice;
public class DefaultPay implements Pay{
@Override
public boolean pay(int amount) {
System.out.println("결제 수단이 없습니다.");
return false;
}
}
package poly.ex.pay1practice;
import java.util.Scanner;
public class PayMain2 {
public static void main(String[] args) { //입력받는 버전
Scanner scanner = new Scanner(System.in);
PayService payService = new PayService();
while(true) {
System.out.println("결제수단을 입력하세요:");
String payOption = scanner.nextLine();
if (payOption.equals("exit")) {
System.out.println("프로그램을 종료합니다.");
return;
}
System.out.println("결제금액을 입력하세요:");
int amount = scanner.nextInt();
scanner.nextLine();//씹히는 오류방지
payService.processPay(payOption, amount);
}
}
}
Scanner은 까먹었었는데 다시 상기 시킴.