Menu
Coddy logo textTech

Patrón Factory

Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 46 de 64.

El patrón Factory crea objetos sin especificar su clase exacta. En lugar de llamar a los constructores directamente, se utiliza un método factory que decide qué clase instanciar.

Aquí hay clases de productos simples:

class Car:
    def __init__(self, brand):
        self.brand = brand
        self.type = "Car"
    
    def info(self):
        return f"{self.type}: {self.brand}"

class Bike:
    def __init__(self, brand):
        self.brand = brand
        self.type = "Bike"
    
    def info(self):
        return f"{self.type}: {self.brand}"

Crea una clase de fábrica para producir estos objetos:

class VehicleFactory:
    def create_vehicle(self, vehicle_type, brand):
        if vehicle_type == "car":
            return Car(brand)
        elif vehicle_type == "bike":
            return Bike(brand)
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

Usa la fábrica en lugar de llamar a los constructores directamente:

factory = VehicleFactory()
my_car = factory.create_vehicle("car", "Toyota")
my_bike = factory.create_vehicle("bike", "Honda")

print(my_car.info())   # Coche: Toyota
print(my_bike.info())  # Moto: Honda

Haz que la fábrica sea más flexible usando *args:

class FlexibleFactory:
    def create_vehicle(self, vehicle_type, *args):
        if vehicle_type == "car":
            return Car(args[0])  # Solo la marca
        elif vehicle_type == "truck":
            return Truck(args[0], args[1])  # Marca y capacidad
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

class Truck:
    def __init__(self, brand, capacity):
        self.brand = brand
        self.capacity = capacity
        self.type = "Truck"
    
    def info(self):
        return f"{self.type}: {self.brand} ({self.capacity}t)"

Usa la fábrica flexible:

flexible = FlexibleFactory()
car = flexible.create_vehicle("car", "Ford")
truck = flexible.create_vehicle("truck", "Volvo", "20")

print(car.info())    # Coche: Ford
print(truck.info())  # Camión: Volvo (20t)

Salida:

Car: Toyota
Bike: Honda
Car: Ford
Truck: Volvo (20t)

Punto clave: El patrón Factory le permite crear objetos sin conocer su clase exacta. El método de fábrica decide qué clase instanciar basándose en los parámetros. Utilice *args para manejar productos con diferentes parámetros de constructor. Esto hace que su código sea más flexible y más fácil de extender con nuevos tipos de productos.

challenge icon

Desafío

Intermedio

En este desafío, implementarás un sistema de fábrica de formas utilizando un diseño adecuado orientado a objetos con herencia y polimorfismo.

Completa la implementación en los siguientes archivos:

  • shape.py - Clase base Shape
  • circle.py - Implementación de Circle
  • rectangle.py - Implementación de Rectangle
  • triangle.py - Implementación de Triangle
  • shapefactory.py - Clase Factory para crear formas

Cada archivo contiene comentarios TODO detallados para guiar tu implementación. Sigue estos comentarios cuidadosamente para asegurar que tu código cumpla con todos los requisitos.

Hoja de referencia

El Patrón Factory crea objetos sin especificar su clase exacta. En lugar de llamar a los constructores directamente, se utiliza un método de fábrica que decide qué clase instanciar.

Implementación básica de una fábrica:

class VehicleFactory:
    def create_vehicle(self, vehicle_type, brand):
        if vehicle_type == "car":
            return Car(brand)
        elif vehicle_type == "bike":
            return Bike(brand)
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

# Uso
factory = VehicleFactory()
my_car = factory.create_vehicle("car", "Toyota")
my_bike = factory.create_vehicle("bike", "Honda")

Fábrica flexible utilizando *args para diferentes parámetros de constructor:

class FlexibleFactory:
    def create_vehicle(self, vehicle_type, *args):
        if vehicle_type == "car":
            return Car(args[0])  # Solo marca
        elif vehicle_type == "truck":
            return Truck(args[0], args[1])  # Marca y capacidad
        else:
            raise ValueError(f"Unknown type: {vehicle_type}")

# Uso
flexible = FlexibleFactory()
car = flexible.create_vehicle("car", "Ford")
truck = flexible.create_vehicle("truck", "Volvo", "20")

Beneficios clave: El Patrón Factory hace que el código sea más flexible y fácil de extender con nuevos tipos de productos sin modificar el código de cliente existente.

Pruébalo tú mismo

from shapefactory import ShapeFactory
from shape import Shape
from circle import Circle
from rectangle import Rectangle
from triangle import Triangle
import sys

# Ejecutor de casos de prueba
test_case = input()

factory = ShapeFactory()

if test_case == "circle_area":
    circle = factory.create_shape("circle", 5)
    print(f"{circle.area():.2f}")

elif test_case == "rectangle_perimeter":
    rectangle = factory.create_shape("rectangle", 4, 6)
    print(f"{rectangle.perimeter()}")

elif test_case == "triangle_perimeter":
    triangle = factory.create_shape("triangle", 3, 4, 5)
    print(f"{triangle.perimeter()}")

elif test_case == "invalid_shape":
    try:
        factory.create_shape("hexagon", 6)
        print("No exception raised")
    except ValueError as e:
        print(str(e))

elif test_case == "case_insensitive":
    circle = factory.create_shape("CiRcLe", 3)
    print(f"{circle.area():.2f}")

elif test_case == "shape_inheritance":
    shapes = [
        factory.create_shape("circle", 2),
        factory.create_shape("rectangle", 2, 3),
        factory.create_shape("triangle", 3, 4, 5)
    ]
    all_shapes = all(isinstance(shape, Shape) for shape in shapes)
    print(all_shapes)

elif test_case == "zero_radius_circle":
    circle = factory.create_shape("circle", 0)
    print(f"{circle.area():.2f} {circle.perimeter():.2f}")

elif test_case == "negative_dimensions":
    rectangle = factory.create_shape("rectangle", -2, -3)
    print(f"{rectangle.area()}")

elif test_case == "large_values":
    circle = factory.create_shape("circle", 1000000)
    print(f"{circle.area():.2e}")

elif test_case == "polymorphism_test":
    shapes = [
        factory.create_shape("circle", 2),
        factory.create_shape("rectangle", 3, 4),
        factory.create_shape("triangle", 3, 4, 5)
    ]
    area_sum = sum(shape.area() for shape in shapes)
    perimeter_sum = sum(shape.perimeter() for shape in shapes)
    print(f"Area sum: {area_sum:.2f}, Perimeter sum: {perimeter_sum:.2f}")

elif test_case == "triangle_area":
    triangle = factory.create_shape("triangle", 3, 4, 5)
    print(f"{triangle.area():.2f}")

elif test_case == "method_override":
    circle = factory.create_shape("circle", 2)
    rectangle = factory.create_shape("rectangle", 3, 4)
    triangle = factory.create_shape("triangle", 3, 4, 5)
    
    # Obtener objetos de método para comparar implementaciones
    circle_area = Circle.area
    rectangle_area = Rectangle.area
    triangle_area = Triangle.area
    
    circle_perimeter = Circle.perimeter
    rectangle_perimeter = Rectangle.perimeter
    triangle_perimeter = Triangle.perimeter
    
    # Verificar si todas las implementaciones son únicas
    unique_areas = len({circle_area, rectangle_area, triangle_area}) == 3
    unique_perimeters = len({circle_perimeter, rectangle_perimeter, triangle_perimeter}) == 3
    
    if unique_areas and unique_perimeters:
        print("All shapes correctly override methods")
    else:
        print("Some shapes share method implementations")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming