Подтвердить что ты не робот

Как работать с полиморфизмом Java в сервис-ориентированной архитектуре

Каков путь наименьшего зла при работе с полиморфизмом и наследованием типов сущностей в сервис-ориентированной архитектуре?

Принцип SOA (как я понимаю) состоит в том, чтобы иметь классы объектов как простые конструкторы данных, лишенные какой-либо бизнес-логики. Вся бизнес-логика содержится в узкополосных, слабосвязанных сервисах. Это означает, что реализация служб настолько мала, насколько это возможно, способствует ослаблению связи и означает, что сущности избегают знать о каждом поведении, которое система может выполнять на них.

Из-за того, что Java довольно затруднительное решение использовать объявленный тип при принятии решения о том, какой перегруженный метод использовать, любое полиморфное поведение в реализациях сервисов вместо этого заменяется рядом проверка условных выражений object.getClass() или использование instanceof. Это кажется довольно отсталым в ООПЛ.

Является ли использование условных обозначений принятой нормой в SOA? Следует ли отказаться от наследования в сущности?

UPDATE

Я определенно подразумеваю перегрузку, а не переопределение.

Я определяю SOA, чтобы это означало, что поведение системы сгруппировано по использованию в интерфейсах, а затем логика для них реализуется в одном классе для каждого интерфейса. Поскольку такой класс сущности (скажем Product) становится не чем иным, как POJO с геттерами и сеттерами. Это абсолютно не должно содержать никакой бизнес-логики, связанной с сервисом, потому что тогда вы вводите один фокус связи, в соответствии с которым класс сущности должен знать обо всех бизнес-процессах, которые могут когда-либо работать на нем, полностью отрицая цель слабосвязанного SOA.

Итак, если вы не должны встраивать поведение бизнес-процесса в класс сущности, нельзя использовать полиморфизм с этими классами сущностей - для переопределения поведения нет никакого поведения.

ОБНОВЛЕНИЕ 2

Вышеописанное поведение объясняется более просто, так как перегруженный путь выбирается в время компиляции и переопределенный путь в время выполнения.

Было бы плохой практикой иметь подкласс вашей реализации сервиса для каждого подтипа класса модели домена, на котором он действует, и как люди могут обойти проблему перегрузки при компиляции?

4b9b3361

Ответ 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);
    }
}