Как работать с полиморфизмом Java в сервис-ориентированной архитектуре
Каков путь наименьшего зла при работе с полиморфизмом и наследованием типов сущностей в сервис-ориентированной архитектуре?
Принцип SOA (как я понимаю) состоит в том, чтобы иметь классы объектов как простые конструкторы данных, лишенные какой-либо бизнес-логики. Вся бизнес-логика содержится в узкополосных, слабосвязанных сервисах. Это означает, что реализация служб настолько мала, насколько это возможно, способствует ослаблению связи и означает, что сущности избегают знать о каждом поведении, которое система может выполнять на них.
Из-за того, что Java довольно затруднительное решение использовать объявленный тип при принятии решения о том, какой перегруженный метод использовать, любое полиморфное поведение в реализациях сервисов вместо этого заменяется рядом проверка условных выражений object.getClass()
или использование instanceof
. Это кажется довольно отсталым в ООПЛ.
Является ли использование условных обозначений принятой нормой в SOA? Следует ли отказаться от наследования в сущности?
UPDATE
Я определенно подразумеваю перегрузку, а не переопределение.
Я определяю SOA, чтобы это означало, что поведение системы сгруппировано по использованию в интерфейсах, а затем логика для них реализуется в одном классе для каждого интерфейса. Поскольку такой класс сущности (скажем Product
) становится не чем иным, как POJO с геттерами и сеттерами. Это абсолютно не должно содержать никакой бизнес-логики, связанной с сервисом, потому что тогда вы вводите один фокус связи, в соответствии с которым класс сущности должен знать обо всех бизнес-процессах, которые могут когда-либо работать на нем, полностью отрицая цель слабосвязанного SOA.
Итак, если вы не должны встраивать поведение бизнес-процесса в класс сущности, нельзя использовать полиморфизм с этими классами сущностей - для переопределения поведения нет никакого поведения.
ОБНОВЛЕНИЕ 2
Вышеописанное поведение объясняется более просто, так как перегруженный путь выбирается в время компиляции и переопределенный путь в время выполнения.
Было бы плохой практикой иметь подкласс вашей реализации сервиса для каждого подтипа класса модели домена, на котором он действует, и как люди могут обойти проблему перегрузки при компиляции?
Ответы
Ответ 1
Мне потребовалось некоторое время, чтобы прочитать это, чтобы выяснить, о чем вы действительно просили.
Моя интерпретация заключается в том, что у вас есть набор классов POJO, где при передаче службе вы хотите, чтобы служба могла выполнять разные операции в зависимости от конкретного класса POJO, переданного ему.
Обычно я стараюсь избегать иерархии большого или глубокого типа и иметь дело с instanceof и т.д., где нужны один или два случая.
Когда по какой-то причине должна быть иерархия с широким типом, я бы, вероятно, использовал тип шаблона обработчика, как показано ниже.
class Animal {
}
class Cat extends Animal {
}
interface AnimalHandler {
void handleAnimal(Animal animal);
}
class CatHandler implements AnimalHandler {
@Override
public void handleAnimal(Animal animal) {
Cat cat = (Cat)animal;
// do something with a cat
}
}
class AnimalServiceImpl implements AnimalHandler {
Map<Class,AnimalHandler> animalHandlers = new HashMap<Class, AnimalHandler>();
AnimalServiceImpl() {
animalHandlers.put(Cat.class, new CatHandler());
}
public void handleAnimal(Animal animal) {
animalHandlers.get(animal.getClass()).handleAnimal(animal);
}
}
Ответ 2
Вы можете избежать этой проблемы, разработав бизнес-логику в разных классах на основе типа сущности, основываясь на принципе единой ответственности, это был бы лучший способ, когда вы размещаете бизнес-логику на уровне обслуживания и используете factory для создания логической реализации, например
enum ProductType
{
Physical,
Service
}
interface IProduct
{
double getRate();
ProductType getProductType();
}
class PhysicalProduct implements IProduct
{
private double rate;
public double getRate()
{
return rate;
}
public double getProductType()
{
return ProductType.Physical;
}
}
class ServiceProduct implements IProduct
{
private double rate;
private double overTimeRate;
private double maxHoursPerDayInNormalRate;
public double getRate()
{
return rate;
}
public double getOverTimeRate()
{
return overTimeRate;
}
public double getMaxHoursPerDayInNormalRate;()
{
return maxHoursPerDayInNormalRate;
}
public double getProductType()
{
return ProductType.Service;
}
}
interface IProductCalculator
{
double calculate(double units);
}
class PhysicalProductCalculator implements IProductCalculator
{
private PhysicalProduct product;
public PhysicalProductCalculator(IProduct product)
{
this.product = (PhysicalProduct) product;
}
double calculate(double units)
{
//calculation logic goes here
}
}
class ServiceProductCalculator implements IProductCalculator
{
private ServiceProduct product;
public ServiceProductCalculator(IProduct product)
{
this.product = (ServiceProduct) product;
}
double calculate(double units)
{
//calculation logic goes here
}
}
class ProductCalculatorFactory
{
public static IProductCalculator createCalculator(IProduct product)
{
switch (product.getProductType)
{
case Physical:
return new PhysicalProductCalculator ();
case Service:
return new ServiceProductCalculator ();
}
}
}
//this can be used to execute the business logic
ProductCalculatorFactory.createCalculator(product).calculate(value);
Ответ 3
Из-за Java довольно непонятное решение использовать объявленный тип, когда решая, какой перегруженный метод использовать
Кто дал вам эту идею? Java был бы бесполезным языком, если бы это было так!
Прочтите это: Учебник по Java > Наследование
Здесь простая тестовая программа:
public class Tester{
static class Foo {
void foo() {
System.out.println("foo");
}
}
static class Bar extends Foo {
@Override
void foo() {
System.out.println("bar");
}
}
public static void main(final String[] args) {
final Foo foo = new Bar();
foo.foo();
}
}
Выход, конечно, "bar" , а не "foo" !!
Ответ 4
Я думаю, что здесь есть путаница. SOA - это архитектурный способ решения взаимодействия между компонентами. Каждый компонент в SOA-решении будет обрабатывать контекст в более крупном домене. Каждый контекст является его собственностью. Другими словами, SOA - это то, что позволяет потерять связь между контекстами домена или приложениями.
Ориентация объектов в Java при работе в подобной среде применима к каждому домену. Таким образом, иерархии и объекты с богатым доменом, смоделированные с использованием чего-то вроде управляемого доменом, будут жить на уровне ниже сервисов в SOA-решении. Существует уровень между службой, которой подвергаются другие контексты, и детальную модель домена, которая будет создавать богатые объекты для работы домена.
Для решения каждой архитектуры контекста/приложений с помощью SOA не будет очень хорошего приложения. Точно так же, как решение взаимодействия между ними с помощью OO.
Итак, чтобы более точно ответить на вопрос о щедрости:
Это не вопрос техники. Это вопрос применения правильного шаблона для каждого уровня дизайна.
Для крупной экосистемы предприятия SOA - это то, как я буду решать взаимодействие между системами, например системой HR и платежной ведомостью. Но при работе с HR (или, возможно, с каждым контекстом в HR) и платежной ведомости я бы использовал шаблоны из DDD.
Я надеюсь, что немного очистит воды.
Ответ 5
Подумав об этом немного больше, я подумал об альтернативном подходе, который упрощает дизайн.
abstract class Animal {
}
class Cat extends Animal {
public String meow() {
return "Meow";
}
}
class Dog extends Animal {
public String bark() {
return "Bark";
}
}
class AnimalService {
public String getSound(Animal animal) {
try {
Method method = this.getClass().getMethod("getSound", animal.getClass());
return (String) method.invoke(this, animal);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getSound(Cat cat) {
return cat.meow();
}
public String getSound(Dog dog) {
return dog.bark();
}
}
public static void main(String[] args) {
AnimalService animalService = new AnimalService();
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal animal : animals) {
String sound = animalService.getSound(animal);
System.out.println(sound);
}
}