[24.09.27] TIL

yy·2024년 9월 26일

개발일지

목록 보기
111/122

JAVA 공부중

추상 클래스, 추상 메서드

animal과 같이 추상적인 의미의 클래스를 추상 클래스로 만들어서 사용한다.
(왜냐면 animal의 자식들인 개, 고양이, 소 등과 같은 객체를 만들어서 사용하지 animal자체로는 뭘 생성해서 하지 않기 때문)

추상 클래스

  • 선언 시 abstract 키워드 붙여서 사용하며, 인스턴스 생성이 불가능.
abstract class AbstractAnimal {...}

추상메서드

  • 추상 메서드가 있는 클래스는 무조건 추상 클래스가 됨.
  • 부모 클래스에서는 메서드 선언만 하고, 자식 클래스에서 오버라이딩하여 바디를 구현해야함.
  • 자식이 부모의 메서드를 오버라이딩 안하면 자식도 추상클래스가 되어야함.
public abstract void sound()

순수 추상 클래스

모든 메서드가 추상 메서드인 추상 클래스

public abstract class AbstractAnimal { 
		public abstract void sound();
		public abstract void move();
	}
  • 인스턴스를 생성할 수 없고,
  • 상속 시 모든 메서드를 자식이 오버라이딩해야하고,
  • 주로 다형성을 위해 사용됨.

=> 이건 규격이 있는 인터페이스 같음. (usb 인터페이스처럼 규격에 맞게 제품을 개발해야하는듯한 너낌)

=> 순수 추상 클래스를 더 편리하게 사용할 수 있도록 인터페이스 기능을 제공한다.

인터페이스

  • 인스턴스를 생성할 수 없고,
  • 상속 시 모든 메서드를 자식이 오버라이딩해야하고,
  • 주로 다형성을 위해 사용됨.
  • 인터페이스의 메서드 모두 public, abstract (메서드에서는 생략해야함)
  • 인터페이스는 다중 구현(다중 상속) 지원 (implements 사용)
  • 클래스 간 상속이라고 하지만 인터페이스는 부모에서 자식이 상속받는다기 보다 인터페이스를 구현한다고함.
public interface InterfaceAnimal { 
		void sound();
		void move(); 
}

다형적 참조에서부터 정리해보자면
각 인스턴스를 생성해서 각 메서드를 불러야했었다. 그런데 다형적 참조를 통해서 타입을 동일하게 하지만 각기 다른 자식 인스턴스를 사용하여 한꺼번에 메서드를 호출하거나 활용할 수 있었음.
그러다가 부모 인스턴스를 생성하지 않아도 되는 상황에 봉착함. (인스턴스를 만들면안된다는 제약 추가) 추상적인 존재로만 있으면 되는 부모를 추상 클래스로 만들 수 있었음. 자식들이 메서드를 오버라이딩하여 사용할 수 있었음. 그러다가 추상 메서드만을 가진 추상 클래스가 존재하게 되었고 그걸 보고 추상클래스에서 제약이 추가된 인터페이스를 만듦.
인터페이스는 메서드가 부모에서는 선언만 되어있고, 자식에서 반드시 구현해야한다. 그렇게 함으로써 인터페이스의 메서드를 반드시 구현해야하는 제약을 준다. 더 나아가 인터페이스는 부모 하나만 두는게 아니라 여러 부모를 둘 수가 있는 다중 상속(다중 구현)을 지원함.

클래스
-> 추상 클래스(제약: 부모 인스턴스 생성 불가, 부모에서 메서드 정의해도되지만 자식이 메서드 오버라이딩해야함)
-> 인터페이스(제약: 부모에서 메서드 선언만 하고 구현X, 자식이 메서드 구현해야함, 다중상속(구현))



OCP 원칙 (Open-Closed Principle)

  • 객체 지향 설계 원칙 중 하나
    Open for extension: 확장에는 열려있고,
    Closed for modification: 수정에는 닫혀있어야함.

확장

car 인터페이스를 통해서 새로운 차량을 추가할 수 있다. driver 또한 car의 인터페이스를 통해서 추가된 차량을 자유롭게 호출할 수 있음 -> 확장에 열려있음.

폐쇄

변하는 부분은 반드시 있다.
main(), driver에게 차를 전달해주는 코드, 새로운 차량 생성에 대한 코드는 변경이 일어나지만 그것은 설정과 조율의 코드임.

중요한건 새로운 차량을 추가하더라도 운전자(클라이언트)의 코드가 변하지 않는다는 점.

이는 다형성을 활용하고 역할과 구현을 잘 분리한 탓에 새로운 차량을 추가하더라도 대부분의 핵심코드를 유지할 수 있다는 점.
마치 해결방법 여러가지를 나열해서 상황에 맞게 갈아끼울 수 있게 하는 방법과도 같다.


객체 지향 설계를 할 때

  • 역할: 인터페이스 (driver, car의 역할 / 배우의 역할)

  • 구현: 인터페이스를 구현한 클래스, 구현 객체

  • 객체를 설계할 때 역할과 구현을 명확히 분리해야한다.

  • 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체를 만들어야함.

  • 객체의 협력이라는 관계부터 생각해야함.


다형성의 본질

  • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있음
  • 다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야함
  • 클라이언트 변경X, 서버의 구현 기능을 유연하게 변경할 수 있음




package class1.polyEx3;

import java.util.Scanner;

public class PayMain {
    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("시스템을 종료합니다.");
                break;
            }

            System.out.println("결제금액을 선택하세요.");
            int amount = scanner.nextInt();
            scanner.nextLine();

            payService.processPay(payOption, amount);

        }
        
// ↓ 사용자 입력을 받기 전
//        //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);
//
//        //결제 수단 추가
//        String payOption4 = "new";
//        int amount4 = 35000;
//        payService.processPay(payOption4, amount4);
    }
}
package class1.polyEx3;

public class PayService {
    public void processPay(String option, int amount) {
        System.out.println("결제를 시작합니다: option=" + option + ", amount=" + amount);
        // 인스턴스 생성
        PayInterface pay = PayStore.findPay(option);
        // 결제 메소드 분리
        boolean result = pay.pay(amount);

        if (result) {
            System.out.println("결제가 성공했습니다.");
        } else {
            System.out.println("결제가 실패했습니다.");
        }
    }
}
package class1.polyEx3;

public abstract class PayStore { //추상클래스로 만들어서 객체 생성 막기
    public static PayInterface findPay(String option) {
        return switch (option) {
            case "kakao" -> new KakaoPay();
            case "naver" -> new NaverPay();
            case "new" -> new NewPay();
            default -> new DefaultPay();
        };
    }
}
package class1.polyEx3;

public class KakaoPay implements PayInterface {
    @Override
    public boolean pay(int amount) {
        System.out.println("카카오페이 시스템과 연결합니다.");
        System.out.println(amount + "원 결제를 시도합니다.");
        return true;
    }
}
package class1.polyEx3;

public class NaverPay implements PayInterface {
    @Override
    public boolean pay(int amount) {
        System.out.println("네이버페이 시스템과 연결합니다.");
        System.out.println(amount + "원 결제를 시도합니다.");
        return true;
    }
}
package class1.polyEx3;

public class DefaultPay implements PayInterface {
    @Override
    public boolean pay(int amount) {
        System.out.println("결제 수단이 없습니다.");
        return false;
    }
}
package class1.polyEx3;

public class NewPay implements PayInterface {
    @Override
    public boolean pay(int amount) {
        System.out.println("새로운 결제 시스템과 연결합니다.");
        System.out.println(amount + "원 결제를 시도합니다.");
        return true;
    }
}
package class1.polyEx3;

public interface PayInterface {
    boolean pay(int amount);
}
profile
시간이 걸릴 뿐 내가 못할 건 없다.

0개의 댓글