简体   繁体   中英

Java - Generic for Payment processing with Strategy pattern

I am trying to implement Strategy pattern approach for payment processing in my Spring webflux based application.

My application supports multiple payment method like, Card Payment, Cash Payment, ... Also, we have to support Square & Stripe for Card payment.

Model class,

// Model interface
public interface PaymentModel {

}

// Base model with attributes needed for all payment types
public class BaseModel implements PaymentModel {

    private Float amount;
    private Integer userId;
}

public class SquareCardModel extends BaseModel {

    private String merchantId;
    private String device;
    private String orderId;

}

public class StripeCardModel extends BaseModel {

    private String merchantId;
    private String orderId;

}

public class CashModel extends BaseModel {

    private String name;
    private String orderId;

}

Service Class,

@Service
public interface PaymentService<T extends PaymentModel> {

    Mono<ServerResponse> pay(T model);

    String method();
}

@Service
public class CashPaymentService implements PaymentService<CashModel> {

    private static final String PAYMENT_METHOD = "cash";

    @Override
    public Mono<ServerResponse> pay(CashModel model) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String method() {
        return PAYMENT_METHOD;
    }

}

@Service
public class SquarePaymentService implements PaymentService<SquareCardModel> {

    private static final String PAYMENT_METHOD = "cash";

    @Override
    public Mono<ServerResponse> pay(SquareCardModel model) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String method() {
        return PAYMENT_METHOD;
    }

}

@Service
public class StripePaymentService implements PaymentService<StripeCardModel> {

    private static final String PAYMENT_METHOD = "cash";

    @Override
    public Mono<ServerResponse> pay(SquareCardModel model) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String method() {
        return PAYMENT_METHOD;
    }

}

Factory Class,

@Service
public class PaymentFactory<T> {

    private final List<PaymentService<? extends PaymentModel>> paymentServices;

    @Autowired
    public PaymentFactory(List<PaymentService<? extends PaymentModel>> paymentServices) {
        this.paymentServices = paymentServices;
    }

    public PaymentService<? extends PaymentModel> retrievePaymentService(final String paymentMethod) {
        Optional<PaymentService<? extends PaymentModel>> paymentService = paymentServices.stream()
                .filter(service -> service.method().equals(paymentMethod)).findFirst();

        if (paymentService.isEmpty()) {
            throw new IllegalArgumentException("Unsupported Payment method ");
        }
        return paymentService.get();
    }

}

User choose the payment method and the call comes to the backend,

@Transactional
    public Mono<ServerResponse> payBilling(ServerRequest request) {
            return request.bodyToMono(PaymentDto.class).flatMap(paymentReq -> {
                if (paymentReq.getPaymentType().equals("CC")) { // For Card
                    return processCardPayment(usr, paymentReq);
                } else {
                    return badRequest().bodyValue("Not supported yet !");
                }
            });
    }

private Mono<? extends ServerResponse> processCardPayment(
            PaymentDto paymentReq) {
            PaymentService<PaymentModel> paymentService = (PaymentService<PaymentModel>) paymentFactory
                    .retrievePaymentService(paymentReq.getPaymentType());
            PaymentModel paymentModel = buildPaymentModel((String) paymentReq.getPaymentType(), paymentReq,
                    jsonMap);
            return paymentService.pay(paymentModel);
    }

    private PaymentModel buildPaymentModel(final String paymentMethod, final PaymentDto paymentReq,
        if (paymentMethod.equals("squarePayment")) {
            SquareCardModel model = new SquareCardModel();
            model.setAmount(paymentReq.getTotal());
            model.setMerchantId(paymentReq.getMerchantid());
            model.setOrderId(orderId);

            return model;
        }

        return null;

    }

Questions:

  1. Not sure if I have implemented generics properly with the strategy pattern.
  2. Also, I dont like type casting here. (PaymentService). is there any better approach?
  3. Why do I still need to use if for creating different model.

if (paymentMethod.equals("squarePayment")) {

PaymentService<PaymentModel> paymentService = (PaymentService<PaymentModel>) paymentFactory
                        .retrievePaymentService(paymentReq.getPaymentType());
                PaymentModel paymentModel = buildPaymentModel((String) paymentReq.getPaymentType(), paymentReq,
                        jsonMap);
                return paymentService.pay(paymentModel);

Here's a simplified version of your code which I think maintains what you need to do, from a type perspective:

import java.util.Optional;

public class App {

    public interface PaymentModel { }

    public static class CashModel implements PaymentModel { }

    public interface PaymentService<T extends PaymentModel> {
        void pay(T model);
        void pay2(PaymentModel model);
    }

    public static class PaymentFactory {
        public PaymentService<PaymentModel> retrievePaymentService(final String paymentMethod) {
            Optional<PaymentService<PaymentModel>> paymentService = null;
            return paymentService.get();
        }
        public PaymentService<? extends PaymentModel> retrievePaymentService2(final String paymentMethod) {
            Optional<PaymentService<PaymentModel>> paymentService = null;
            return paymentService.get();
        }
    }

    public static void main(String... args) throws NoSuchFieldException, IllegalAccessException {
        PaymentFactory paymentFactory = null;
        PaymentService<PaymentModel> paymentService = paymentFactory
                .retrievePaymentService("foo");
        paymentService.pay(new CashModel());
        
        PaymentService<? extends PaymentModel> paymentService2 = paymentFactory
                .retrievePaymentService2("foo");
        paymentService2.pay(new CashModel()); // error
        paymentService2.pay2(new CashModel()); // ok
    }
}

Look at the difference between retrievePaymentService and retrievePaymentService2 .

retrievePaymentService returns PaymentService<PaymentModel> which says that it is a payment service which works on any PaymentModel implementation. retrievePaymentService2 returns PaymentService<? extends PaymentModel> PaymentService<? extends PaymentModel> which says that it is a payment service which works on some specific, unknown PaymentModel implementation.

As you have already made sure that your PaymentModel type matches the PaymentService you are getting from the factory, the first form is what you want.

A better design might try to not have two parallel class hierarchies which need to be matched up carefully at runtime.

Also, processCardPayment seems as though it should handle all PaymentModel s?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM