Наследовать сущности полезно не для того, чтобы хранить общую часть в родительской сущности (это реализуется композицией). А для того, чтобы можно было использовать их в шаблонах проектирования: например, Strategy. Ниже рассмотрим пример.
Допустим, у нас есть заказчики, для которых нужно рассчитать скидку:
- EmployeeCustomer — это сотрудник, скидка которого зависит от времени, проведенного в компании.
- ExternalCustormer — внешний заказчик, скидка его зависит от суммы заказов.
В общем есть разные стратегии расчета скидки, и в стратегии участвуют поля сущности. А алгоритм зависит от класса сущности.
EmployeeCustomer и ExternalCustormer — наследники Customer.
Модель
Customer содержит общие данные, такие как имя (имя есть у всех):
@Entity @Inheritance( strategy = InheritanceType.JOINED ) public class Customer { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private long id; private String name; // getters/setters/constructors }
Аннотация @Inheritance
Аннотация со значением InheritanceType.JOINED говорит о том, что будет общая таблица customer для хранения общих данных , плюс данные каждого наследника тоже будут храниться в отдельной таблице.
Наследники
Теперь наследники, первый EmployeeCustomer:
@Entity @Data public class EmployeeCustomer extends Customer { private int monthsInCompany; // getters/setters/constructor }
где monthsInCompany — количество месяцев в компании.
И ExternalCustormer:
@Entity public class ExternalCustomer extends Customer { private long sum; // getters/setters/constructor }
где sum — сумма заказов, от нее зависит скидка.
Схема в базе данных
Вышеприведенные классы генерируют такую структуру в базе данных:
Первичные ключи employee_customer.id и external_customer.id являются заодно и внешними: они ссылаются на customer.id.
Расчет скидок с помощью шаблона Strategy
Суть шаблона состоит в том, что есть единая задача (рассчитать скидку). Она задается интерфейсом
public interface DiscountCalculator<T> { double calculate(T customer); Class<T> getClazz(); }
где T — конкретный класс заказчика.
Каждый класс, реализующий интерфейс DiscountCalculator, использует свою стратегию для расчета скидки.
У сотрудника скидка зависит от числа отработанных месяцев:
@Service public class EmployeeCustomerDiscountCalculator implements DiscountCalculator<EmployeeCustomer> { @Override public double calculate(EmployeeCustomer customer) { if (customer.getMonthsInCompany() > 12) return 0.1; return 0.05; } @Override public Class getClazz(){ return EmployeeCustomer.class; } }
А у внешних заказчиков от потраченной суммы:
@Service public class ExternalCustomerDiscountCalculator implements DiscountCalculator<ExternalCustomer> { @Override public double calculate(ExternalCustomer customer) { if (customer.getSum() > 100) return 0.01; return 0.05; } @Override public Class getClazz(){ return ExternalCustomer.class; } }
Поиск заказчиков
Список всех заказчиков можно найти с помощью унаследованного стандартного метода findAll() репозитория:
public interface CustomerRepository extends JpaRepository<Customer, Long> { }
findAll() найдет заказчиков всех типов: как ExternalCustomer, так и EmployeeCustomer, причем их можно привести к конкретному типу. Найдем всех заказчиков и рассчитаем для каждого скидку с помощью соответствующего (классу заказчика) алгоритма.
Делается это в методе makeDiscounts() ниже.
@Service public class MakeDiscountsService { @Autowired private CustomerRepository customerRepository; @Autowired private List<DiscountCalculator> calculators; private Map<Class, DiscountCalculator> map = new HashMap<>(); @PostConstruct private void init() { for (DiscountCalculator discountCalculator : calculators) { map.put(discountCalculator.getClazz(), discountCalculator); } } public void makeDiscounts() { List<Customer> customers = customerRepository.findAll(); for (Customer customer : customers) { DiscountCalculator discountCalculator = map.get(customer.getClass()); double discount = discountCalculator.calculate(customer); System.out.println(discount); } } }
Итоги
Мы рассмотрели тип наследования JOINED table.
Также мы применили шаблон проектирования Strategy, который стал возможен благодаря наследованию. Если бы сущности не были унаследованы от общего Customer, то не получилось бы применить шаблон Strategy для расчета скидки.
Исходный код примера есть на GitHub.