15 Stimmen

Wie geht man mit Java-Polymorphismus in einer serviceorientierten Architektur um?

Was ist der Weg des geringsten Übels beim Umgang mit Polymorphismus und Vererbung von Entitätstypen in einer serviceorientierten Architektur?

Ein Prinzip von SOA (so wie ich es verstehe) ist es, Entitätsklassen als reine Datenkonstrukte zu haben, die keine Geschäftslogik enthalten. Die gesamte Geschäftslogik ist in eng umgrenzten, lose gekoppelten Diensten enthalten. Das bedeutet, dass die Service-Implementierungen so klein wie möglich sind, um die lose Kopplung zu fördern, und dass die Entitäten nicht wissen müssen, was sie tun. jede Verhalten, das das System an ihnen vornehmen kann.

Aufgrund der recht verwirrenden Entscheidung von Java, die angegebener Typ bei der Entscheidung über die zu verwendende überladene Methode wird jegliches polymorphes Verhalten in den Dienstimplementierungen durch eine Reihe von Konditionalitäten ersetzt, die Folgendes überprüfen object.getClass() oder mit instanceof . Dies erscheint in einer OOPL eher rückständig.

Ist die Verwendung von Konditionalen die akzeptierte Norm in SOA? Sollte die Vererbung in Entitäten aufgegeben werden?

UPDATE

Ich meine definitiv das Überladen und nicht das Überschreiben.

Ich definiere SOA so, dass das Verhalten des Systems nach Anwendungsfällen in Schnittstellen gruppiert wird und die Logik für diese in der Regel in einer Klasse pro Schnittstelle implementiert wird. Als solche wird eine Entitätsklasse (z.B. Product ) wird zu nichts weiter als einem POJO mit Gettern und Settern. Sie sollte auf keinen Fall Geschäftslogik enthalten, die mit einem Dienst zusammenhängt, denn dann führen Sie einen Kopplungsschwerpunkt ein, bei dem die Entitätsklasse über alle Geschäftsprozesse Bescheid wissen muss, die jemals auf ihr ablaufen könnten, was den Zweck einer lose gekoppelten SOA völlig negiert.

Da man also kein geschäftsprozessspezifisches Verhalten in eine Entitätsklasse einbetten sollte, kann man mit diesen Entitätsklassen keinen Polymorphismus verwenden - es gibt kein Verhalten, das man überschreiben könnte.

UPDATE 2

Das oben beschriebene Verhalten ist einfacher zu erklären als eine überlastet Pfad wird gewählt bei Kompilierzeit und ein außer Kraft gesetzt Pfad bei Laufzeit .

Es wäre eine schlechte Praxis, eine Unterklasse Ihrer Service-Implementierung für jeden Untertyp der Domänenmodellklasse zu haben, auf die sie wirkt, also wie umgeht man das Problem des Überladens zur Kompilierzeit?

6voto

Low Flying Pelican Punkte 5922

Sie können dieses Problem vermeiden, indem Sie die Geschäftslogik in verschiedenen Klassen auf der Grundlage des Entitätstyps entwerfen. Auf der Grundlage des Prinzips der einzigen Verantwortung wäre es am besten, wenn Sie die Geschäftslogik in einer Dienstschicht platzieren und eine Fabrik zur Erstellung der Logikimplementierung verwenden, z. B.

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);

4voto

Tom Punkte 41119

Ich habe beim Lesen eine Weile gebraucht, um herauszufinden, worum Sie wirklich bitten.

Meine Interpretation ist, dass Sie eine Reihe von POJO-Klassen haben, bei denen, wenn sie an einen Dienst übergeben werden, Sie möchten, dass der Dienst in der Lage ist, verschiedene Operationen durchzuführen, je nach der speziellen POJO-Klasse, die ihm übergeben wird.

Normalerweise würde ich versuchen, eine breite oder tiefe Typenhierarchie zu vermeiden und mich mit instanceof usw. zu befassen, wenn die ein oder zwei Fälle benötigt werden.

Wenn es aus irgendeinem Grund eine breite Typenhierarchie geben muss, würde ich wahrscheinlich ein Handler-Muster wie das folgende verwenden.

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);
    }
}

3voto

Sean Patrick Floyd Punkte 283617

Aufgrund der recht verwirrenden Entscheidung von Java, den deklarierten Typ zu verwenden, wenn Entscheidung, welche überladene Methode verwendet werden soll

Wer hat Sie auf diese Idee gebracht? Java wäre eine wertlose Sprache, wenn es so wäre!

Lesen Sie dies: Java Tutorial > Vererbung

Hier ist ein einfaches Testprogramm:

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();
    }
}

Die Ausgabe ist natürlich "bar", nicht "foo"!

2voto

Tomas Malmsten Punkte 161

Ich glaube, hier werden verschiedene Anliegen durcheinander gebracht. SOA ist ein architektonischer Weg zur Lösung der Interaktion zwischen Komponenten. Jede Komponente innerhalb einer SOA-Lösung behandelt einen Kontext innerhalb eines größeren Bereichs. Jeder Kontext ist ein Bereich für sich selbst. Mit anderen Worten: SOA ist etwas, das eine lose Kopplung zwischen Domänenkontexten oder Anwendungen ermöglicht.

Die Objektorientierung in Java wird bei der Arbeit in dieser Art von Umgebung auf jede Domäne angewendet. Hierarchien und reichhaltige Domänenobjekte, die mit einer Art domänenorientiertem Design modelliert wurden, befinden sich also auf einer Ebene unterhalb der Dienste in einer SOA-Lösung. Es gibt eine Ebene zwischen dem Dienst, der anderen Kontexten ausgesetzt ist, und dem detaillierten Domänenmodell, das reichhaltige Objekte für die Domäne erstellt, mit denen diese arbeiten kann.

Jede Kontext-/Anwendungsarchitektur mit SOA zu lösen, wird keine sehr gute Anwendung liefern. Genauso wie die Lösung der Interaktion zwischen ihnen mit OO.

Um also die Frage nach dem Kopfgeld genauer zu beantworten: Es geht nicht darum, das Problem technisch zu umgehen. Es geht um die Anwendung des richtigen Musters auf jeder Ebene des Designs.

Für ein großes Unternehmens-Ökosystem ist SOA die Art und Weise, wie ich die Interaktion zwischen Systemen lösen würde, z. B. zwischen HR-System und Lohnbuchhaltung. Aber bei der Arbeit mit HR (oder wahrscheinlich jedem Kontext innerhalb von HR) und der Gehaltsabrechnung würde ich die Muster aus DDD verwenden.

Ich hoffe, das klärt die Sache ein wenig auf.

1voto

Tom Punkte 41119

Nachdem ich etwas mehr darüber nachgedacht habe, bin ich auf einen alternativen Ansatz gekommen, der ein einfacheres Design ermöglicht.

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);
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X