Skip to content
IRC-CodingIRC-Coding
UMLPolimorfismoBinding dinámicoSobrescrituraSobrecargaGenericsPOOHerencia

Polimorfismo UML: Binding Dinámico y Sobrescritura

Polimorfismo UML con binding dinámico, sobrescritura, sobrecarga y Generics. Ejemplos en Java.

S

schutzgeist

14 min read
Polimorfismo UML: Binding Dinámico y Sobrescritura

Fundamentos de Polimorfismo en UML: Vinculación Dinámica y Sobrescritura

El polimorfismo es un concepto central de la programación orientada a objetos y el modelado UML. Permite que objetos de diferentes clases respondan de manera distinta al mismo mensaje.

¿Qué es el polimorfismo?

El polimorfismo (literalmente, múltiples formas) describe la capacidad de los objetos de usar la misma interfaz pero con implementaciones diferentes. En UML, esto se representa mediante jerarquías de herencia e interfaces.

Tipos de polimorfismo

  1. Sobrescritura (Override): Una método en la subclase reemplaza el de la clase base
  2. Sobrecarga (Overload): Múltiples métodos con el mismo nombre pero parámetros diferentes
  3. Polimorfismo paramétrico: Genéricos para reutilización type-safe
  4. Polimorfismo ad-hoc: Sobrecarga de métodos y conversión de tipos

Representación UML del polimorfismo

Diagrama de clases con polimorfismo

@startuml
abstract class Shape {
    -color: String
    -x: double
    -y: double
    +Shape(color: String, x: double, y: double)
    +move(dx: double, dy: double): void
    +area(): double {abstract}
    +perimeter(): double {abstract}
    +toString(): String
}

class Rectangle {
    -width: double
    -height: double
    +Rectangle(color: String, x: double, y: double, width: double, height: double)
    +area(): double
    +perimeter(): double
    +setDimensions(width: double, height: double): void
    +toString(): String
}

class Circle {
    -radius: double
    +Circle(color: String, x: double, y: double, radius: double)
    +area(): double
    +perimeter(): double
    +setRadius(radius: double): void
    +toString(): String
}

class Triangle {
    -base: double
    -height: double
    +Triangle(color: String, x: double, y: double, base: double, height: double)
    +area(): double
    +perimeter(): double
    +toString(): String
}

Shape <|-- Rectangle
Shape <|-- Circle
Shape <|-- Triangle

@enduml

Diagrama de secuencia para vinculación dinámica

@startuml
actor User
User -> ShapeProcessor: processShapes(shapes)
activate ShapeProcessor

loop para cada forma
    ShapeProcessor -> Shape: area()
    activate Shape
    
    alt Rectangle
        Shape --> ShapeProcessor: Rectangle.area()
    else Circle
        Shape --> ShapeProcessor: Circle.area()
    else Triangle
        Shape --> ShapeProcessor: Triangle.area()
    end
    
    deactivate Shape
    ShapeProcessor -> Shape: perimeter()
    activate Shape
    
    alt Rectangle
        Shape --> ShapeProcessor: Rectangle.perimeter()
    else Circle
        Shape --> ShapeProcessor: Circle.perimeter()
    else Triangle
        Shape --> ShapeProcessor: Triangle.perimeter()
    end
    
    deactivate Shape
end

ShapeProcessor --> User: Resultados
deactivate ShapeProcessor
@enduml

Vinculación dinámica en Java

Sobrescritura y envío dinámico

public class PolymorphismDemo {
    
    // Clase base abstracta
    public abstract class Shape {
        protected String color;
        protected double x, y;
        
        public Shape(String color, double x, double y) {
            this.color = color;
            this.x = x;
            this.y = y;
        }
        
        // Método que puede ser sobrescrito
        public void move(double dx, double dy) {
            this.x += dx;
            this.y += dy;
            System.out.println(color + " forma movida a (" + x + ", " + y + ")");
        }
        
        // Métodos abstractos - deben ser sobrescritos
        public abstract double area();
        public abstract double perimeter();
        
        // Método concreto que puede ser sobrescrito
        public String getDescription() {
            return "Una forma " + color + " en posición (" + x + ", " + y + ")";
        }
        
        // Getters
        public String getColor() { return color; }
        public double getX() { return x; }
        public double getY() { return y; }
    }
    
    // Rectangle sobrescribe los métodos abstractos
    public class Rectangle extends Shape {
        private double width, height;
        
        public Rectangle(String color, double x, double y, double width, double height) {
            super(color, x, y);
            this.width = width;
            this.height = height;
        }
        
        @Override
        public double area() {
            return width * height;
        }
        
        @Override
        public double perimeter() {
            return 2 * (width + height);
        }
        
        @Override
        public String getDescription() {
            return super.getDescription() + " (rectángulo " + width + "x" + height + ")";
        }
        
        // Método adicional
        public void setDimensions(double width, double height) {
            this.width = width;
            this.height = height;
        }
    }
    
    // Circle sobrescribe los métodos abstractos
    public class Circle extends Shape {
        private double radius;
        
        public Circle(String color, double x, double y, double radius) {
            super(color, x, y);
            this.radius = radius;
        }
        
        @Override
        public double area() {
            return Math.PI * radius * radius;
        }
        
        @Override
        public double perimeter() {
            return 2 * Math.PI * radius;
        }
        
        @Override
        public String getDescription() {
            return super.getDescription() + " (círculo con radio " + radius + ")";
        }
        
        public void setRadius(double radius) {
            this.radius = radius;
        }
    }
    
    // Demostración de vinculación dinámica
    public void demonstrateDynamicBinding() {
        List<Shape> shapes = new ArrayList<>();
        shapes.add(new Rectangle("rojo", 0, 0, 5, 3));
        shapes.add(new Circle("azul", 10, 10, 2));
        shapes.add(new Rectangle("verde", 5, 5, 2, 2));
        
        // Procesamiento polimórfico - envío dinámico
        for (Shape shape : shapes) {
            System.out.println(shape.getDescription());
            
            // Vinculación dinámica - se invoca el método correcto según el tipo del objeto
            double area = shape.area();        // Elige Rectangle.area() o Circle.area()
            double perimeter = shape.perimeter(); // Elige Rectangle.perimeter() o Circle.perimeter()
            
            System.out.println("  Área: " + String.format("%.2f", area));
            System.out.println("  Perímetro: " + String.format("%.2f", perimeter));
            
            // move() también puede ser sobrescrito
            shape.move(1, 1);
            System.out.println();
        }
    }
}

Sobrecarga de métodos

Sobrecarga en Java

public class MethodOverloading {
    
    // Métodos sobrecargados para diferentes tipos de parámetros
    public class Calculator {
        
        // Sobrecarga para int
        public int add(int a, int b) {
            System.out.println("int add(int, int) invocado");
            return a + b;
        }
        
        // Sobrecarga para double
        public double add(double a, double b) {
            System.out.println("double add(double, double) invocado");
            return a + b;
        }
        
        // Sobrecarga para tres parámetros
        public int add(int a, int b, int c) {
            System.out.println("int add(int, int, int) invocado");
            return a + b + c;
        }
        
        // Sobrecarga para arrays
        public int add(int[] numbers) {
            System.out.println("int add(int[]) invocado");
            int sum = 0;
            for (int num : numbers) {
                sum += num;
            }
            return sum;
        }
        
        // Sobrecarga con varargs
        public int addVarargs(int... numbers) {
            System.out.println("int addVarargs(int...) invocado");
            return add(numbers);
        }
        
        // Sobrecarga para diferentes tipos de objetos
        public String concatenate(String a, String b) {
            System.out.println("String concatenate(String, String) invocado");
            return a + b;
        }
        
        public String concatenate(String a, String b, String c) {
            System.out.println("String concatenate(String, String, String) invocado");
            return a + b + c;
        }
    }
    
    // Demostración de sobrecarga
    public void demonstrateOverloading() {
        Calculator calc = new Calculator();
        
        // Se invocan diferentes sobrecargas
        System.out.println("5 + 3 = " + calc.add(5, 3));
        System.out.println("5.5 + 3.3 = " + calc.add(5.5, 3.3));
        System.out.println("1 + 2 + 3 = " + calc.add(1, 2, 3));
        System.out.println("Array sum = " + calc.add(new int[]{1, 2, 3, 4, 5}));
        System.out.println("Varargs sum = " + calc.addVarargs(1, 2, 3, 4, 5));
        System.out.println("Hello + World = " + calc.concatenate("Hello", "World"));
        System.out.println("A + B + C = " + calc.concatenate("A", "B", "C"));
    }
}

Sobrecarga con herencia

public class OverloadingWithInheritance {
    
    public class Animal {
        public void makeSound() {
            System.out.println("Animal makes a sound");
        }
        
        public void makeSound(String intensity) {
            System.out.println("Animal makes a " + intensity + " sound");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Dog barks");
        }
        
        // Sobrecargado, no sobrescrito
        public void makeSound(String intensity) {
            System.out.println("Dog barks " + intensity);
        }
        
        // Sobrecarga adicional
        public void makeSound(String intensity, int times) {
            for (int i = 0; i < times; i++) {
                System.out.println("Dog barks " + intensity);
            }
        }
    }
    
    public void demonstrateOverloadingInheritance() {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Animal animalDog = new Dog(); // Upcasting
        
        // Vinculación estática en sobrecarga (Compile-Time)
        animal.makeSound();           // Animal makes a sound
        animal.makeSound("loud");     // Animal makes a loud sound
        
        dog.makeSound();              // Dog barks (sobrescrito)
        dog.makeSound("loud");        // Dog barks loud (sobrecargado)
        dog.makeSound("loud", 3);     // Dog barks loud (3x) (sobrecargado)
        
        // Importante: vinculación estática en sobrecarga
        animalDog.makeSound();        // Dog barks (vinculación dinámica)
        animalDog.makeSound("loud");  // Animal makes a loud sound (vinculación estática)
    }
}

Genéricos y polimorfismo paramétrico

Clases genéricas

public class GenericPolymorphism {
    
    // Clase Container genérica
    public class Container<T> {
        private T content;
        private String label;
        
        public Container(String label, T content) {
            this.label = label;
            this.content = content;
        }
        
        public T getContent() {
            return content;
        }
        
        public void setContent(T content) {
            this.content = content;
        }
        
        public String getLabel() {
            return label;
        }
        
        // Método genérico
        public <U> Container<U> transform(Function<T, U> transformer) {
            U newContent = transformer.apply(content);
            return new Container<>(label, newContent);
        }
        
        @Override
        public String toString() {
            return label + ": " + content;
        }
    }
    
    // Processor genérico
    public class Processor<T> {
        public List<T> filter(List<T> items, Predicate<T> predicate) {
            return items.stream()
                .filter(predicate)
                .collect(Collectors.toList());
        }
        
        public <R> List<R> map(List<T> items, Function<T, R> mapper) {
            return items.stream()
                .map(mapper)
                .collect(Collectors.toList());
        }
        
        public T reduce(List<T> items, BinaryOperator<T> accumulator, T identity) {
            return items.stream()
                .reduce(identity, accumulator);
        }
    }
    
    // Demostración
    public void demonstrateGenerics() {
        // Container con diferentes tipos
        Container<String> stringContainer = new Container<>("Text", "Hello World");
        Container<Integer> intContainer = new Container<>("Zahl", 42);
        Container<List<String>> listContainer = new Container<>("Liste", 
            Arrays.asList("A", "B", "C"));
        
        System.out.println(stringContainer);
        System.out.println(intContainer);
        System.out.println(listContainer);
        
        // Transformación con método genérico
        Container<Integer> lengthContainer = stringContainer.transform(String::length);
        System.out.println("Länge: " + lengthContainer);
        
        // Processor genérico
        Processor<String> stringProcessor = new Processor<>();
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
        
        // Filtrar
        List<String> longWords = stringProcessor.filter(words, s -> s.length() > 5);
        System.out.println("Lange Wörter: " + longWords);
        
        // Mapear
        List<Integer> lengths = stringProcessor.map(words, String::length);
        System.out.println("Längen: " + lengths);
        
        // Reducir
        String concatenated = stringProcessor.reduce(words, String::concat, "");
        System.out.println("Zusammengefügt: " + concatenated);
    }
}

Métodos genéricos

public class GenericMethods {
    
    // Método genérico para comparación
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }
    
    // Método genérico para intercambio
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    // Método genérico para conversión
    public static <T, R> List<R> convertList(List<T> list, Function<T, R> converter) {
        return list.stream()
            .map(converter)
            .collect(Collectors.toList());
    }
    
    // Método genérico con wildcards
    public static void printList(List<?> list) {
        for (Object item : list) {
            System.out.println(item);
        }
    }
    
    // Upper Bounded Wildcard
    public static double sumOfNumbers(List<? extends Number> numbers) {
        return numbers.stream()
            .mapToDouble(Number::doubleValue)
            .sum();
    }
    
    // Lower Bounded Wildcard
    public static void addNumbers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
        list.add(3);
    }
    
    // Demostración
    public static void demonstrateGenericMethods() {
        // Método Max
        System.out.println("Max von 5 und 3: " + max(5, 3));
        System.out.println("Max von 'Hello' und 'World': " + max("Hello", "World"));
        
        // Método Swap
        String[] words = {"A", "B", "C"};
        System.out.println("Vor Swap: " + Arrays.toString(words));
        swap(words, 0, 2);
        System.out.println("Nach Swap: " + Arrays.toString(words));
        
        // Método Convert
        List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
        List<Integer> integers = convertList(strings, Integer::parseInt);
        System.out.println("Konvertiert: " + integers);
        
        // Métodos con wildcards
        List<String> stringList = Arrays.asList("A", "B", "C");
        List<Integer> intList = Arrays.asList(1, 2, 3);
        
        System.out.println("String Liste:");
        printList(stringList);
        
        System.out.println("Integer Liste:");
        printList(intList);
        
        // Upper bounded wildcard
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        System.out.println("Summe: " + sumOfNumbers(doubles));
        
        // Lower bounded wildcard
        List<Number> numbers = new ArrayList<>();
        addNumbers(numbers);
        System.out.println("Zahlen hinzugefügt: " + numbers);
    }
}

Interfaces y Polimorfismo

Polimorfismo basado en interfaces

public class InterfacePolymorphism {
    
    // Interface para comportamiento polimórfico
    public interface Drawable {
        void draw();
        double getArea();
        String getType();
    }
    
    public interface Movable {
        void move(double dx, double dy);
        void setPosition(double x, double y);
        double[] getPosition();
    }
    
    // Clase que implementa múltiples interfaces
    public class Circle implements Drawable, Movable {
        private double radius, x, y;
        
        public Circle(double radius, double x, double y) {
            this.radius = radius;
            this.x = x;
            this.y = y;
        }
        
        @Override
        public void draw() {
            System.out.println("Zeichne Kreis an (" + x + ", " + y + ") mit Radius " + radius);
        }
        
        @Override
        public double getArea() {
            return Math.PI * radius * radius;
        }
        
        @Override
        public String getType() {
            return "Kreis";
        }
        
        @Override
        public void move(double dx, double dy) {
            x += dx;
            y += dy;
            System.out.println("Kreis verschoben nach (" + x + ", " + y + ")");
        }
        
        @Override
        public void setPosition(double x, double y) {
            this.x = x;
            this.y = y;
        }
        
        @Override
        public double[] getPosition() {
            return new double[]{x, y};
        }
    }
    
    public class Rectangle implements Drawable, Movable {
        private double width, height, x, y;
        
        public Rectangle(double width, double height, double x, double y) {
            this.width = width;
            this.height = height;
            this.x = x;
            this.y = y;
        }
        
        @Override
        public void draw() {
            System.out.println("Zeichne Rechteck an (" + x + ", " + y + ") " + width + "x" + height);
        }
        
        @Override
        public double getArea() {
            return width * height;
        }
        
        @Override
        public String getType() {
            return "Rechteck";
        }
        
        @Override
        public void move(double dx, double dy) {
            x += dx;
            y += dy;
            System.out.println("Rechteck verschoben nach (" + x + ", " + y + ")");
        }
        
        @Override
        public void setPosition(double x, double y) {
            this.x = x;
            this.y = y;
        }
        
        @Override
        public double[] getPosition() {
            return new double[]{x, y};
        }
    }
    
    // Procesamiento polimórfico
    public void processShapes(List<Drawable> shapes) {
        for (Drawable shape : shapes) {
            shape.draw();
            System.out.println("Tipo: " + shape.getType());
            System.out.println("Área: " + shape.getArea());
            System.out.println();
        }
    }
    
    public void moveShapes(List<Movable> movables, double dx, double dy) {
        for (Movable movable : movables) {
            movable.move(dx, dy);
        }
    }
    
    // Demostración
    public void demonstrateInterfacePolymorphism() {
        List<Drawable> shapes = new ArrayList<>();
        List<Movable> movables = new ArrayList<>();
        
        Circle circle = new Circle(2.0, 0, 0);
        Rectangle rectangle = new Rectangle(3.0, 4.0, 5, 5);
        
        shapes.add(circle);
        shapes.add(rectangle);
        movables.add(circle);
        movables.add(rectangle);
        
        System.out.println("=== Dibujar formas ===");
        processShapes(shapes);
        
        System.out.println("=== Mover formas ===");
        moveShapes(movables, 10, 10);
        
        System.out.println("=== Después de mover ===");
        processShapes(shapes);
    }
}

Notación UML para polimorfismo

Convenciones del diagrama de clases

@startuml
' Relación polimórfica en UML
abstract class PaymentProcessor {
    +processPayment(amount: double): boolean {abstract}
    +validatePayment(amount: double): boolean {abstract}
}

class CreditCardProcessor {
    +processPayment(amount: double): boolean
    +validatePayment(amount: double): boolean
    +validateCardNumber(number: String): boolean
}

class PayPalProcessor {
    +processPayment(amount: double): boolean
    +validatePayment(amount: double): boolean
    +validateEmail(email: String): boolean
}

class BankTransferProcessor {
    +processPayment(amount: double): boolean
    +validatePayment(amount: double): boolean
    +validateBankDetails(iban: String): boolean
}

PaymentProcessor <|-- CreditCardProcessor
PaymentProcessor <|-- PayPalProcessor
PaymentProcessor <|-- BankTransferProcessor

' Interface para operaciones polimórficas
interface Refundable {
    +processRefund(amount: double): boolean
    +getRefundStatus(): String
}

CreditCardProcessor ..|> Refundable
PayPalProcessor ..|> Refundable
BankTransferProcessor ..|> Refundable

@enduml

Diagrama de secuencia para llamadas polimórficas

@startuml
actor Customer
Customer -> PaymentSystem: makePayment(amount, method)
activate PaymentSystem

PaymentSystem -> PaymentProcessorFactory: createProcessor(method)
activate PaymentProcessorFactory
PaymentProcessorFactory --> PaymentSystem: processor
deactivate PaymentProcessorFactory

PaymentSystem -> PaymentProcessor: processPayment(amount)
activate PaymentProcessor

alt Credit Card
    PaymentProcessor -> CreditCardProcessor: processPayment(amount)
    CreditCardProcessor --> PaymentProcessor: success
else PayPal
    PaymentProcessor -> PayPalProcessor: processPayment(amount)
    PayPalProcessor --> PaymentProcessor: success
else Bank Transfer
    PaymentProcessor -> BankTransferProcessor: processPayment(amount)
    BankTransferProcessor --> PaymentProcessor: success
end

PaymentProcessor --> PaymentSystem: result
deactivate PaymentProcessor

PaymentSystem --> Customer: payment result
deactivate PaymentSystem
@enduml

Mejores prácticas para polimorfismo

1. Liskov Substitution Principle

// Bien: Rectangle puede reemplazar Shape en cualquier lugar
public class GoodPolymorphism {
    
    public interface Shape {
        double area();
        double perimeter();
        void move(double dx, double dy);
    }
    
    public class Rectangle implements Shape {
        private double width, height, x, y;
        
        public Rectangle(double width, double height, double x, double y) {
            this.width = width;
            this.height = height;
            this.x = x;
            this.y = y;
        }
        
        @Override
        public double area() {
            return width * height;
        }
        
        @Override
        public double perimeter() {
            return 2 * (width + height);
        }
        
        @Override
        public void move(double dx, double dy) {
            x += dx;
            y += dy;
        }
        
        // Los métodos adicionales no violan LSP
        public double getWidth() { return width; }
        public double getHeight() { return height; }
    }
    
    public class Square implements Shape {
        private double side, x, y;
        
        public Square(double side, double x, double y) {
            this.side = side;
            this.x = x;
            this.y = y;
        }
        
        @Override
        public double area() {
            return side * side;
        }
        
        @Override
        public double perimeter() {
            return 4 * side;
        }
        
        @Override
        public void move(double dx, double dy) {
            x += dx;
            y += dy;
        }
        
        public double getSide() { return side; }
    }
}

2. Segregación de Interfaces

// Malo: Interface demasiado grande
public interface BadShape {
    double area();
    double perimeter();
    void move(double dx, double dy);
    void rotate(double angle);
    void resize(double factor);
    Color getColor();
    void setColor(Color color);
}

// Bien: Interfaces especializadas
public interface Drawable {
    void draw(Graphics g);
}

public interface Movable {
    void move(double dx, double dy);
    void setPosition(double x, double y);
    double[] getPosition();
}

public interface Resizable {
    void resize(double factor);
    void setSize(double width, double height);
}

public interface Rotatable {
    void rotate(double angle);
    double getRotation();
}

public interface Colored {
    Color getColor();
    void setColor(Color color);
}

// La clase implementa solo los interfaces necesarios
public class Circle implements Drawable, Movable, Resizable, Colored {
    // Implementación...
}

3. Template Method Pattern

public abstract class DataProcessor {
    
    // Template Method - define el algoritmo
    public final void processData() {
        loadData();
        if (validateData()) {
            transformData();
            saveData();
            onSuccess();
        } else {
            onError();
        }
        cleanup();
    }
    
    // Métodos abstractos - deben implementarse
    protected abstract void loadData();
    protected abstract boolean validateData();
    protected abstract void transformData();
    protected abstract void saveData();
    
    // Hook methods - pueden sobrescribirse
    protected void onSuccess() {
        System.out.println("Procesamiento exitoso");
    }
    
    protected void onError() {
        System.out.println("Procesamiento fallido");
    }
    
    protected void cleanup() {
        System.out.println("Limpieza");
    }
}

public class CSVProcessor extends DataProcessor {
    @Override
    protected void loadData() {
        System.out.println("Cargando datos CSV");
    }
    
    @Override
    protected boolean validateData() {
        System.out.println("Validando datos CSV");
        return true;
    }
    
    @Override
    protected void transformData() {
        System.out.println("Transformando datos CSV");
    }
    
    @Override
    protected void saveData() {
        System.out.println("Guardando datos CSV");
    }
    
    @Override
    protected void onSuccess() {
        System.out.println("¡Procesamiento CSV exitoso!");
    }
}

Conceptos relevantes para el examen

Distinciones importantes

  1. Sobrescritura vs Sobrecarga

    • Sobrescritura: Misma firma en la subclase
    • Sobrecarga: Mismo nombre, parámetros diferentes
  2. Vinculación estática vs dinámica

    • Estática: Sobrecarga (Compile-Time)
    • Dinámica: Sobrescritura (Runtime)
  3. Clase abstracta vs Interface

    • Clase abstracta: Implementación compartida
    • Interface: Contrato puro
  4. Generics vs Herencia

    • Generics: Seguridad de tipos en Compile-Time
    • Herencia: Polimorfismo en Runtime

Tareas típicas de examen

  1. Dibuja diagramas UML para relaciones polimórficas
  2. Implementa métodos sobrescritos
  3. Explica la vinculación dinámica
  4. Compara diferentes tipos de polimorfismo
  5. Diseña jerarquías de clases polimórficas

Resumen

El polimorfismo es un concepto potente para arquitecturas de software flexibles:

  • Sobrescritura permite vinculación dinámica y polimorfismo en Runtime
  • Sobrecarga ofrece vinculación estática y polimorfismo en Compile-Time
  • Generics permite reutilización con seguridad de tipos
  • Interfaces definen contratos polimórficos

Un buen polimorfismo requiere cumplimiento del Liskov Substitution Principle y un diseño cuidadoso de interfaces para software mantenible y extensible.

Continúa en la ruta de aprendizaje de UML

El siguiente artículo en la ruta de aprendizaje de UML cubre UML Klassendiagramme: Beziehungen - Association, Aggregation, Komposition, Dependency, Inheritance — los detalles sobre los diferentes tipos de relaciones en diagramas de clases.

Volver al blog
Share:

Entradas relacionadas