FireDrago

[객체지향과 디자인패턴] 3장 다형성과 추상타입 본문

프로그래밍/디자인패턴

[객체지향과 디자인패턴] 3장 다형성과 추상타입

화이용 2024. 8. 28. 14:50

3장 다형성과 추상 타입

객체지향은 캡슐화, 추상화를 통해 구현 변경의 유연함을 제공한다. 추상화에 대해서 알아보자

1. 상속 개요

상속은 하나의 타입을 그대로 사용하면서 구현을 추가할 수 있도록 해주는 방법을 제공한다.

public class Coupon {
    private int discountAmount;

    public Coupon(int discountAmount) {
        this.discountAmount = discountAmount;
    }

    public int getDiscountAmount() {
        return discountAmount;
    }

    public int calculateDiscountAmount(int price) {
        if (price < discountAmount) {
            return 0;
        }
        return price - discountAmount;
    }
}
public class LimitPriceCoupon extends Coupon {
    private int limitPrice;

    public LimitPriceCoupon(int limitPrice, int discountAmount) {
        super(discountAmount);
        this.limitPrice = limitPrice;
    }

    public int getLimitPrice() {
        return limitPrice;
    }

    @Override
    public int calculateDiscountAmount(int price) {
        if (price < limitPrice) {
            return price;
        }
        return super.calculateDiscountAmount(price);
    }
}
LimitPriceCoupon lpCounpon = new LimitPriceCoupon(5000, 1000);
int discountAmount = lpCoupon.getDiscountAmount(); // Coupon 클래스에서 물려받음
int limitPrice = lpCoupon.getLimitPrice(); // LimitPriceCoupon 클래스에 정의

2. 다형성과 상속

  • 다형성 : 한 객체가 여러가지 타입을 가질 수 있다는 것
public class Plane {
    public void fly() {
        // 비행
    }
}

public interface Turbo {
    public void boost();
}

public class TurboPlane extends Plane implements Turbo {
    public void boost() {
        // 가속
    }
}
TurboPlane tp = new TurboPlane();
tp.fly(); // Plane에 정의/구현된 메서드 실행
tp.boost(); // Turbo에 정의되고 TurboPlane에 구현된 메서드 실행
TurboPlane tp = new TurboPlane();
Plane p = tp; // TurboPlane 객체는 Plane 타입도 된다.
p.fly();

Turbo t = tp; // TurboPlane 객체는 Turbo 타입도 된다.
t.boost();

2.1 인터페이스 상속과 구현 상속

  • 인터페이스 상속 : 클래스가 특정 인터페이스를 구현(implements)하는 것을 의미한다. 타입의 정의만을 상속받는다.
    • Turbo 인터페이스가 있고, TurboPlane이 이를 구현한다면, TurboPlane은 반드시 boost() 메서드를 구현해야 한다.
  • 구현 상속 : 상위클래스의 기능을 재사용하기위해 사용한다.
    • TurboPlane 클래스는 Plane 클래스로부터 fly() 메서드를 상속받아 사용할 수 있다.

3. 추상타입과 유연함

  • 추상화 : 데이터나 프로세스등을 의미가 비슷한 개념이나 표현으로 정의하는 과정
  • public class FlowController { private boolean useFile; public FlowController(boolean useFile) { this.useFile = useFile; } public void process() { byte[] data = null; if (useFile) { FileDataReader fileReader = new FileDataReader(); data = fileReader.read(); } else { SocketDataReader fileReader = new SocketDataReader(); data = fileReader.read(); } // 암호화 // 파일작성 } }

위 코드는 파일 방식의 읽기와 소켓 방식의 읽기를 각각의 객체를 생성하여 처리한다.

이런 상황에서 추상화가 필요하다. 바이트 데이터 읽기를 추상화한 ByteSource 인터페이스를 생성한다.

public interface ByteSource {
    public byte[] read();
}

public class FileDataReader implements ByteSource {
    public byte[] read() {
        ...
    }
}

public class SocketDataReader implements ByteSource {
    public byte[] read() {
        ...
    }
}

이제 FlowController는 공통의 타입으로 FileDataReader와 SocketDataReader 객체를 다룰수 있다.

하지만 상황에 따라 '객체를 생성'하는 것은 여전히 필요하다.

객체생성 자체를 추상화 하기위해 Factory 객체를 만들어보자 (두번째 추상화)

public class ByteSourceFactory {

    public ByteSource create() {
        if (useFile()) {
            return new FileDataReader();
        } else {
            return new SocketDataReader();
        }
    }
    // 시스템 속성 'useFile'이 true인 경우에만 true 반환  
    private boolean useFile() {
        String useFileVal = System.getProperty("useFile");
        return useFileVal != null && Boolean.valueOf(useFileVal);
    }

    // 싱글톤 패턴 
    private ByteSourceFactory instance = new ByteSourceFactory();
    public static ByteSourceFactory getInstance() {
        return instance;
    }
    private ByteSourceFactory() {}
}

팩토리 클래스를 통해 객체 생성까지 추상화 했다.

이제 HTTP를 이용하여 데이터를 읽어오는 경우가 추가된다고 해도, 팩토리 클래스만 변경되고 컨트롤러는 영향이 없다

컨트롤러는 흐름을 제어하는 책임을, 팩토리는 객체를 생성하는 책임을 가지도록 분리해주었다.

public class FlowController {

    public void process() {
        BytsSource source = ByteSourceFactory.getInstance().create();
        byte[] data = source.read();

        // 암호화
        // 파일작성
    }
}

3.3 변화되는 부분을 추상화하자

추상화를 통해 변경의 유연함을 얻을 수 있다는 것을 배웠지만, 이를 능숙하게 다루기 위해서는 많은 경험이 필요하다.

추상화를 적용하기위한 좋은 기준은 변경되는 부분을 찾는것이다. 요구사항이 추가되거나 바뀌는 부분은 향후 변경될 소지가 많다.

모든 부분을 인터페이스로 사용할 필요는 없다. 복잡도가 너무 증가될 수 있다. 인터페이스는 변화가능성이 높은 경우에 한하여 사용한다.