Composición frente a herencia
Parte de la sección Programación orientada a objetos del Journey de GDScript de Coddy. Lección 40 de 65.
Las clases reutilizan código de dos maneras. La herencia (extends) indica que una clase es un tipo de otra clase: un Mage es un Player. La composición indica que una clase tiene una parte: almacena otro objeto en una variable miembro y le delega el trabajo:
# motor.gd
class_name Motor
extends RefCounted
func start() -> String:
return "motor running"
# car.gd
class_name Car
extends RefCounted
var motor := Motor.new()
func start() -> String:
return "Car starts, " + motor.start()Car.new().start() devuelve Car starts, motor running. El coche no sabe cómo funciona un motor: llama a motor.start(). Pasar una llamada a una pieza de esta manera se llama delegación. (La clase se llama Motor porque Engine ya es una clase de Godot.)
La composición resulta útil cuando las partes cambian o se combinan. Con la herencia, un personaje que camina y uno que vuela son dos subclases, y un murciélago que empieza a volar no puede cambiar de clase. Con la composición, el personaje tiene una parte de movimiento, y cambiarla requiere una sola asignación:
# character.gd
class_name Character
extends RefCounted
var char_name: String
var movement: Movement
func _init(new_name: String, new_movement: Movement) -> void:
char_name = new_name
movement = new_movement
func move() -> String:
return char_name + " " + movement.move()
# main.gd
var bat := Character.new("Bat", Walk.new())
print(bat.move()) # Bat camina
bat.movement = Fly.new()
print(bat.move()) # Bat vuelaAquí, Walk y Fly extienden una pequeña clase Movement: las dos técnicas funcionan juntas. Una clase GDScript solo puede extend una clase, así que la composición también permite que un objeto tenga varias habilidades al contener varias partes.
Usa la herencia para una relación «es un» real que nunca cambia. Usa la composición cuando la clase «tiene» algo, o cuando haya que cambiar la parte mientras se ejecuta el juego.
Desafío
FácilUn caballero no hereda de un arma: un caballero tiene un arma y puede cambiarla. Crea ambas clases.
Weaponalmacenaweapon_nameydamage, ambos definidos por el constructor:Weapon.new("Sword", 7).Knightalmacenaknight_name(del constructor) y un arma, que al principio esnull.equip(new_weapon)reemplaza el arma ydrop()la vuelve a establecer ennull.attack()devuelveAda attacks with Sword for 7 damage, usando el nombre del caballero y el nombre y el daño del arma, oAda has no weaponcuando no hay ninguna.
El código proporcionado lee el nombre del caballero y cuántos comandos vienen después (equip Sword 7, drop, attack) y los ejecuta. Para Ada y attack, equip Sword 7, attack, equip Axe 12, attack, drop, attack, imprime:
Ada has no weapon
Equipped Sword
Ada attacks with Sword for 7 damage
Equipped Axe
Ada attacks with Axe for 12 damage
Dropped the weapon
Ada has no weaponTu código va en weapon.gd y knight.gd. main.gd contiene el código proporcionado de entrada/salida y no se puede editar.
Pruébalo tú mismo
extends Node
func _ready():
# Código de entrada/salida proporcionado: mantenlo tal como está
var knight := Knight.new(OS.read_string_from_stdin())
var count := int(OS.read_string_from_stdin())
for i in count:
var parts := OS.read_string_from_stdin().split(" ")
if parts[0] == "equip":
knight.equip(Weapon.new(parts[1], int(parts[2])))
print("Equipped ", parts[1])
elif parts[0] == "drop":
knight.drop()
print("Dropped the weapon")
else:
print(knight.attack())
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Fundamentos de la programación orientada a objetos
Archivos externosIntroducción a la programación orientada a objetosClases vs. objetosLa palabra clave selfMétodosPropiedadesConstructor (_init)Repaso: calculadora sencilla4Polimorfismo
Repaso de la sobrescritura de métodosTipado patoClases y métodos abstractosDiseño de interfacesRepaso: calculadora de formas7Señales
¿Qué es una señal?Declarar y emitirConectarse a las señalesSeñales con argumentosRepaso: sistema de salud10Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite2Propiedades de clase
Variables de instancia y estáticasFunciones estáticasMétodos setter de propiedadesMétodos getter de propiedadesPrivado por convenciónRepaso: administrador de cuentas bancarias5Encapsulación
Miembros públicos y privadosValidación con settersOcultación de informaciónPropiedades de solo lecturaRepaso: sistema de registros de estudiantes8Conceptos avanzados de POO
Composición frente a herenciaClases internasNodos como componentesRefCounted frente a NodeEnums en clases3Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosCadenas de herenciaComprobaciones de tipo con isRepaso: jerarquía de empleados6Métodos especiales
Introducción a los métodos virtualesEl método _to_stringIteradores personalizadosRepaso: lista personalizada9Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de GDScript online