Применение функциональных интерфейсов для Stratery pattern

Есть мапер на mapstruct который в зависимости от типа тикета вызывает разные map методы и все это реализовано через if else условия. Пытаюсь переписать это на Strategy pattern или что-то похожее на него, но в данном контексте все время что-то не сходится.

// Хотелось бы как-то на месте инициализировать список условий
Map<Set<TicketType>, Function<TicketRequestDto, Ticket>> ticketMapStrategies = new ConcurrentHashMap<>(
    ... Как-то наполнить на месте, но как? ...
);


default Ticket ticketRequestDtoToTicket(TicketRequestDto dto) {
    TicketType type = dto.getType();

    if (type.equals(TicketType.EMERGENCY_STOP) || type.equals(TicketType.SETTING_UPLOAD)
            || dto.getType().equals(TicketType.SETTING_DOWNLOAD)) {
        return ticketEmergencyStopRequestDtoToTicket(dto);
    } else if (type.equals(TicketType.DEPOSIT_INCOME) || type.equals(TicketType.DEPOSIT_OUTCOME)) {
        return ticketAccountTransferRequestDtoToTicket(dto);
    } else if (type.equals(TicketType.PARTNER_REQUEST)) {
        return ticketPartnershipRequestDtoToTicket(dto);
    } else {
        return ticketInfoRequestDtoToTicket(dto);
    }
}

@Mapping(target = "instance.id", source = "instanceId")
@Mapping(target = "recipient.id", source = "recipientId")
Ticket ticketEmergencyStopRequestDtoToTicket(TicketRequestDto dto);

@Mapping(target = "account.id", source = "accountId")
@Mapping(target = "recipient.id", source = "recipientId")
Ticket ticketAccountTransferRequestDtoToTicket(TicketRequestDto dto);

@Mapping(target = "sender.id", source = "senderId")
@Mapping(target = "recipient.id", source = "recipientId")
Ticket ticketPartnershipRequestDtoToTicket(TicketRequestDto dto); 

Ticket ticketInfoRequestDtoToTicket(TicketRequestDto dto);

Хотелось бы как-то на месте инициализировать список условий а в ticketRequestDtoToTicket просто доставать по ключу. Что-то вроде:

    Set<TicketType> key = ticketMapStrategies.keySet().stream()
            .filter(set -> set.contains(dto.getType()))
            .findFirst()
            .orElse(new HashSet<>());
    return ticketMapStrategies.get(key).apply(dto);

Или что-то покомпактнее если возможно.

Ну или что-то в таком духе. Как это можно реализовать учитывая что все это происходит в интерфейсе?

Java 11


Ответы (2 шт):

Автор решения: Andrew Bystrov

как пор мне, ConcurrentHashMap вам не нужна - у вас же не происходит добавление в мапу в нескольких потоках.

Можно примерно так (если java11+)

Map<Set<TicketType>, Function<TicketRequestDto, Ticket>> map = Map.of(
    Set.of(TicketType.EMERGENCY_STOP, TicketType.SETTING_UPLOAD, TicketType.SETTING_DOWNLOAD), this::ticketEmergencyStopRequestDtoToTicket,
    Set.of(TicketType.DEPOSIT_INCOME, TicketType.DEPOSIT_OUTCOME), this::ticketAccountTransferRequestDtoToTicket,
    Set.of(TicketType.PARTNER_REQUEST), this::ticketPartnershipRequestDtoToTicket
);

default Ticket ticketRequestDtoToTicket(TicketRequestDto dto) {
    return map.entrySet()
       .stream()
       .filter(entry -> entry.getKey().contains(dto.getType))
       .map(entry -> entry.getValue())
       .findFirst()
       .orElseGet(this::ticketInfoRequestDtoToTicket)
       .apply(dto);
}

Если же у вас java8, то можно проинциализировать на месте

Map<Set<TicketType>, Function<TicketRequestDto, Ticket>> map = new HashMap() {{
    map.put(new HashSet(Arrays.asList(перечислить нужные типы тикета)), this::ticketEmergencyStopRequestDtoToTicket);
    //вставить все остальные подобным способом.
}}

Если в проект подключена либа guava, то там можно воспользовать ImmutableMap:

Map<Set<TicketType>, Function<TicketRequestDto, Ticket>> map = ImmutableMap.of(
    new HashSet(Arrays.asList(перечислить нужные типы тикета)), this::ticketEmergencyStopRequestDtoToTicket),
    // добавить все остальное
);

P.S. Вообще, тут по сути мапа как структура не нужна - мы ни разу не вызываем метод .get() у мапы => мапа как структура тут не нужна. Ее спокойно можно заменить на лист пар (List<Pair<Set<TicketType>, Function<TicketRequestDto, Ticket>>>)

→ Ссылка
Автор решения: Pavel

Пока самый приемлимый вариант который удалось найти, - это использовать @PostConstruct и в нем заполнять мапу.

Map<Set<TicketType>, Function<TicketRequestDto, Ticket>> ticketMapStrategies = new ConcurrentHashMap<>();

@PostConstruct
default void init() {
    ticketMapStrategies.put(
            Set.of(TicketType.INFO),
            this::ticketInfoRequestDtoToTicket);
    ticketMapStrategies.put(
            Set.of(TicketType.PARTNER_REQUEST),
            this::ticketPartnershipRequestDtoToTicket);
    ticketMapStrategies.put(
            Set.of(TicketType.DEPOSIT_INCOME, TicketType.DEPOSIT_OUTCOME),
            this::ticketAccountTransferRequestDtoToTicket);
    ticketMapStrategies.put(
            Set.of(TicketType.EMERGENCY_STOP, TicketType.SETTING_UPLOAD, TicketType.SETTING_DOWNLOAD),
            this::ticketEmergencyStopRequestDtoToTicket);
}

default Ticket ticketRequestDtoToTicket(TicketRequestDto dto) {
    Set<TicketType> key = ticketMapStrategies.keySet().stream()
            .filter(set -> set.contains(dto.getType())).findFirst()
            .orElseThrow(UnsupportedTicketTypeException::new);
    return ticketMapStrategies.get(key).apply(dto);
}
→ Ссылка