Fachada (patrón de diseño)
La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la
versión revisada el 4 de julio de 2020; las comprobaciones requieren
5 ediciones .
El patrón de fachada ( eng. Facade ) es un patrón de diseño estructural que le permite ocultar la complejidad del sistema al reducir todas las llamadas externas posibles a un objeto , delegándolas a los objetos correspondientes del sistema.
Descripción
Problema
¿Cómo proporcionar una interfaz unificada con un conjunto de implementaciones o interfaces dispares, por ejemplo, a un subsistema, si no se desea un fuerte acoplamiento a ese subsistema, o si la implementación del subsistema podría cambiar?
Solución
Defina un punto de interacción con el subsistema: un objeto de fachada que proporcione una interfaz común con el subsistema y confíele la responsabilidad de interactuar con sus componentes. Una fachada es un objeto externo que proporciona un único punto de entrada para los servicios del subsistema. La implementación de otros componentes del subsistema es privada y no es visible para los componentes externos. El objeto Facade proporciona la implementación GRASP del patrón de Variaciones Protegidas en términos de protección contra cambios en la implementación del subsistema.
Características de la aplicación
Una plantilla se utiliza para establecer algún tipo de política en otro grupo de objetos. Si la política debe ser brillante y notable, debe utilizar los servicios de la plantilla Fachada. Si es necesario proporcionar secreto y precisión (transparencia), el patrón Proxy es una opción más adecuada .
Ejemplos
C++
Texto fuente en
C++
#incluir <iostream>
#incluir <cadena>
#include <memoria>
#incluir <vista_cadena>
/** Músico abstracto: no es una parte obligatoria del patrón, se introdujo para simplificar el código */
clase músico {
const char * nombre ;
público :
Músico ( std :: string_viewname ) { _
esto -> nombre = nombre . datos ();
}
virtual ~ Músico () = predeterminado ;
protegido :
salida vacía ( std :: string_view texto ) {
std :: cout << esto -> nombre << "" << texto << "." << estándar :: endl ;
}
};
/** Músicos específicos */
Vocalista de clase : Músico público {
público :
Vocalista ( std :: string_view nombre ) : Músico ( nombre ) {}
void singCouplet ( int coupletNumber ) {
std :: texto de cadena = "verso cantado #" ;
text += std :: to_string ( coupletNumber );
salida ( texto );
}
void cantarCoro () {
salida ( "cantó el coro" );
}
};
guitarrista de clase : músico público {
público :
Guitarrista ( std :: string_view nombre ) : Músico ( nombre ) {}
void playCoolOpening () {
salida ( "comienza con una introducción genial" );
}
void jugarCoolRiffs () {
salida ( "toca riffs geniales" );
}
void reproducirAnotherCoolRiffs () {
salida ( "reproduce otros riffs geniales" );
}
void jugarIncreíblementeGenialSolo () {
salida ( "produce un solo increíblemente genial" );
}
void jugarFinalAccord () {
salida ( "finaliza la canción con un acorde potente" );
}
};
bajista de clase : músico público {
público :
Bajista ( std :: string_view nombre ) : Músico ( nombre ) {}
void seguir los tambores () {
salida ( "sigue los carretes" );
}
void changeRhythm ( std :: string_viewtype ) { _
std :: string text = ( "cambiado al ritmo" );
texto += tipo ;
texto += "a" ;
salida ( texto );
}
void dejar de jugar () {
salida ( "deja de reproducir" );
}
};
baterista de clase : músico público {
público :
Baterista ( std :: string_view nombre ) : Músico ( nombre ) {}
void empezar a jugar () {
salida ( "comienza a reproducir" );
}
void dejar de jugar () {
salida ( "deja de reproducir" );
}
};
/** Fachada, en este caso una famosa banda de rock */
clase BlackSabbath {
std :: unique_ptr < Vocalista > vocalista ;
std :: unique_ptr < Guitarrista > guitarrista ;
std :: unique_ptr < Bajista > bajista ;
std :: unique_ptr < Baterista > baterista ;
público :
sábado negro () {
vocalista = std :: make_unique < Vocalista > ( "Ozzy Osbourne" );
guitarrista = std :: make_unique < Guitarrista > ( "Tony Iommi" );
bajista = std :: make_unique < Bajista > ( "Geezer Butler" );
baterista = std :: make_unique < Baterista > ( "Bill Ward" );
}
void playCoolSong () {
guitarrista -> playCoolOpening ();
baterista -> empezar a tocar ();
bajista -> seguir a los tambores ();
guitarrista -> playCoolRiffs ();
vocalista -> cantarCouplet ( 1 );
bajista -> changeRhythm ( "coro" );
guitarrista -> tocarAnotherCoolRiffs ();
vocalista -> cantarCoro ();
bajista -> changeRhythm ( "verso" );
guitarrista -> playCoolRiffs ();
vocalista -> cantarCouplet ( 2 );
bajista -> changeRhythm ( "coro" );
guitarrista -> tocarAnotherCoolRiffs ();
vocalista -> cantarCoro ();
bajista -> changeRhythm ( "verso" );
guitarrista -> tocarIncreíblementeCoolSolo ();
guitarrista -> playCoolRiffs ();
vocalista -> cantarCouplet ( 3 );
bajista -> changeRhythm ( "coro" );
guitarrista -> tocarAnotherCoolRiffs ();
vocalista -> cantarCoro ();
bajista -> changeRhythm ( "verso" );
guitarrista -> playCoolRiffs ();
bajista -> dejar de tocar ();
baterista -> dejar de tocar ();
guitarrista -> tocarFinalAccord ();
}
};
int principal () {
std :: cout << "SALIDA:" << std :: endl ;
banda Black Sabbath ;
banda _ reproducirCanciónFresca ();
devolver 0 ;
}
/**
* SALIDA:
* Tony Iommi comienza con una introducción genial.
* Bill Ward comienza a tocar.
* Geezer Butler sigue la batería.
* Tony Iommi toca grandes riffs.
* Ozzy Osbourne cantó el verso #1.
* Geezer Butler cambió al ritmo del coro.
* Tony Iommi toca otros riffs geniales.
* Ozzy Osbourne cantó el coro.
* Geezer Butler cambió al ritmo del verso.
* Tony Iommi toca grandes riffs.
* Ozzy Osbourne cantó el verso #2.
* Geezer Butler cambió al ritmo del coro.
* Tony Iommi toca otros riffs geniales.
* Ozzy Osbourne cantó el coro.
* Geezer Butler cambió al ritmo del verso.
* Tony Iommi ofrece un solo increíblemente genial.
* Tony Iommi toca grandes riffs.
* Ozzy Osbourne cantó el verso #3.
* Geezer Butler cambió al ritmo del coro.
* Tony Iommi toca otros riffs geniales.
* Ozzy Osbourne cantó el coro.
* Geezer Butler cambió al ritmo del verso.
* Tony Iommi toca grandes riffs.
* Geezer Butler deja de jugar.
* Bill Ward deja de jugar.
* Tony Iommi termina la canción con un potente acorde.
*/
JavaScript
Código fuente
JavaScript
/* Partes complejas */
function SubSystem1 () {
this . método1 = función () {
consola . log ( "SubSystem1.method1 llamado" );
};
}
función SubSystem2 () {
este . método2 = función () {
consola . log ( "SubSystem2.method2 llamado" );
};
esto _ métodoB = función () {
consola . log ( "SubSystem2.methodB llamado" );
};
}
/* Fachada */
function Fachada () {
var s1 = new SubSystem1 (),
s2 = new SubSystem2 ();
esto _ m1 = función () {
consola . log ( "Fachada.m1 llamado" );
s1 . método1 ();
s2 _ método2 ();
};
esto _ m2 = función () {
consola . log ( "Fachada.m2 llamado" );
s2 _ métodoB ();
};
}
/* Cliente */
prueba de función () { var fachada = nueva Fachada (); fachada _ m1 (); fachada _ m2 (); }
prueba ();
/*
Salida:
"Fachada.m1 llamado"
"SubSistema1.método1 llamado"
"SubSistema2.método2 llamado"
"Fachada.m2 llamado"
"SubSistema2.métodoB llamado"
*/
CoffeeScript
Texto fuente en lenguaje
CoffeeScript
#
Clase de cargador de imágenes ImageLoader
loadImage = (src) ->
# ...
constructor: (hash = {}) ->
@images = {}
@images [ nombre ] = loadImage ( src ) para nombre , src de hash
#
Clase de cargador de audio SoundLoader
loadSound = (src) ->
# ...
constructor: (hash = {}) ->
@sounds = {}
@sounds [ name ] = loadSound ( src ) for name , src of hash
# Constructor de cargador de clase Facade : ({imágenes, sonidos}) -> @images = nuevo ImageLoader ( imágenes ). imágenes @sounds = nuevo SoundLoader ( sonidos ). sonidos
sonido: (nombre) ->
@sounds [ nombre ]
imagen: (nombre) ->
@images [ nombre ]
PHP
codigo fuente
php
/**
* Implementaciones de partes individuales de computadora.
* Cada método de clase tiene algún tipo de implementación, en este ejemplo se omite.
*/
/**
* Class CPU, responsable de ejecutar la CPU
*/
class CPU
{
public function freeze () {}
public function jump ( $ position ) {}
public function execute () {}
}
/**
* Class Memory, responsable del funcionamiento de la memoria
*/
class Memory
{
const BOOT_ADDRESS = 0x0005 ;
carga de función pública ( $posición , $datos ) {} }
/**
* Class HardDrive, responsable del funcionamiento del disco duro
*/
class HardDrive
{
const BOOT_SECTOR = 0x001 ;
const SECTOR_SIZE = 64 ;
lectura de función pública ( $ lba , $ tamaño ) {} }
/**
* Un ejemplo del patrón "Fachada"
* La computadora se usa como un objeto unificado.
* Detrás de este objeto se ocultarán todos los detalles del trabajo de sus partes internas.
*/
class Computadora
{
protected $cpu ;
$memoria protegida ; disco duro protegido $ ;
/**
* Constructor de la computadora.
* Inicializar partes
*/
public function __construct ()
{
$this -> cpu = new CPU ();
$esto -> memoria = nueva memoria ();
$this -> disco duro = nuevo disco duro ();
}
/**
* Manejo simplificado del comportamiento de "inicio de la computadora"
*/
public function startComputer ()
{
$cpu = $this -> cpu ;
$memoria = $esto -> memoria ;
$disco duro = $esto -> disco duro ;
$cpu -> congelar ();
$memoria -> cargar (
$memoria :: BOOT_ADDRESS ,
$hardDrive -> read ( $hardDrive :: BOOT_SECTOR , $hardDrive :: SECTOR_SIZE )
);
$cpu -> saltar ( $memoria :: BOOT_ADDRESS );
$cpu -> ejecutar ();
}
}
/**
* Los usuarios de computadoras cuentan con una fachada (computadora)
* que oculta toda la complejidad de trabajar con componentes individuales.
*/
$computadora = nueva Computadora ();
$computadora -> iniciarComputadora ();
Pitón
Código fuente en
Python
# Partes complejas de la
clase del sistema CPU ( objeto ):
def __init__ ( self ):
# ...
pasar
def congelar ( auto ):
# ...
pasar
def saltar ( auto , dirección ):
# ...
pasar
def ejecutar ( auto ):
# ...
pasar
clase Memoria ( objeto ):
def __init__ ( auto ):
# ...
pasar
def cargar ( auto , posición , datos ):
# ...
pasar
class HardDrive ( objeto ):
def __init__ ( self ):
# ...
pasar
def read ( self , lba , size ):
# ...
pasar
# Clase de fachada Computadora ( objeto ):
def __init__ ( self ):
self . _cpu = cpu ()
auto . _memory = Memoria ()
self . _disco duro = disco duro ()
def iniciarComputadora ( self ):
self . _cpu . congelar ()
auto . _memoria _ cargar ( BOOT_ADDRESS , self . _hardDrive . leer ( BOOT_SECTOR , SECTOR_SIZE ))
self . _cpu . saltar ( BOOT_ADDRESS )
self . _cpu . ejecutar ()
# Lado del cliente
si __name__ == "__main__" :
fachada = Computadora ()
fachada . iniciarComputadora ()
C#
Texto fuente en
C#
utilizando el sistema ;
biblioteca de
espacio de nombres {
/// <summary>
/// clase de subsistema
/// </summary>
/// <comentarios>
/// <li>
/// <lu>implementa la funcionalidad del subsistema;</lu>
/// <lu>hace el trabajo asignado por el objeto <see cref="Facade"/>;</lu>
/// <lu>no "sabe" nada sobre la existencia de la fachada, es decir, no almacena referencias a él;</lu>
/ // </li>
/// </remarks>
clase interna SubsistemaA { cadena interna A1 () { return "Subsistema A, Método A1\n" ; } cadena interna A2 () { return "Subsistema A, Método A2\n" ; } } clase interna SubsistemaB { cadena interna B1 () { return "Subsistema B, Método B1\n" ; } } clase interna SubsystemC { cadena interna C1 () { return "Subsistema C, Método C1\n" ; } } }
/// <summary>
/// Fachada - fachada
/// </summary>
/// <comentarios>
/// <li>
/// <lu>"sabe" con qué clases de subsistema abordar la solicitud;< /lu >
/// <lu>delegar las solicitudes de los clientes a los objetos apropiados dentro del subsistema;</lu>
/// </li>
/// </remarks>
public class Facade
{
Library . SubsistemaA a = nueva biblioteca . SubsistemaA ();
biblioteca _ SubsistemaB b = nueva biblioteca . SubsistemaB ();
biblioteca _ SubsystemC c = nueva biblioteca . SubsistemaC ();
public void Operation1 ()
{
Consola . WriteLine ( "Operación 1\n" +
a . A1 () +
a . A2 () +
b . B1 ());
}
public void Operation2 ()
{
Consola . WriteLine ( "Operación 2\n" +
b . B1 () +
c . C1 ());
}
}
class Program
{
static void Main ( string [] args )
{
Fachada fachada = nueva Fachada ();
fachada _ Operación1 ();
fachada _ Operación2 ();
// Esperar a la
Consola del usuario . leer ();
}
}
Rubí
Texto fuente en lenguaje
ruby
Biblioteca de módulos
# <summary>
# Subsystem class
# </summary>
# <remarks>
# <li>
# <lu>implementa la funcionalidad del subsistema;</lu>
# <lu>hace el trabajo asignado por <see cref="Facade"/> ;</lu>
# <lu>no "sabe" nada sobre la existencia de la fachada, es decir, no almacena referencias a ella;</lu>
# </li>
# </remarks>
class SubsystemA
def a1 ; "Subsistema A, Método a1 \n " ; enddef
a2 ; _ "Subsistema A, Método a2 \n " ; final final
clase SubsistemaB
def b1 ; "Subsistema B, Método b1 \n " ; final
final
clase SubsistemaC
def c1 ; "Subsistema C, Método c1 \n " ; final
final
final
# <summary>
# Facade
# </summary>
# <remarks>
# <li>
# <lu>"sabe" a qué clases de subsistema dirigir las solicitudes;</lu>
# <lu>delega las solicitudes a los clientes en los objetos apropiados dentro el subsistema ;</lu>
# </li>
# </remarks>
clase Fachada
def inicializar
@a = Biblioteca :: SubsistemaA . nuevo ;
@b = Biblioteca :: SubsistemaB . nuevo ;
@c = Biblioteca :: SubsistemaC . nuevo ;
final
def operación1
pone "Operación 1 \n " +
@a . a1 +
@a . a2 +
@b . final b1
def operación2
pone "Operación 2 \n " +
@b . b1 () +
@c . c1 ()
final
final
fachada = fachada . nueva
fachada . operación1
fachada . operacion2
# Espere a que el usuario
obtenga
VB.NET
Texto fuente en lenguaje
VB.NET
Biblioteca de espacios de nombres
'Clase de subsistema
'. implementa la funcionalidad del subsistema
' . realiza el trabajo asignado por el objeto Fachada
'. no "sabe" nada sobre la existencia de la fachada, es decir, no almacena referencias a ella
Friend Class SubsystemA
Friend Function A1 () As String
Return "Subsystem A, Method A1" & vbCrLf
End Function
Función amiga A2 () Como cadena
Devuelve "Subsistema A, Método A2" & vbCrLf
Función final Clase final
Amigo Clase SubsistemaB
Amigo Función B1 () Como cadena
Devuelve "Subsistema B, Método B1" & vbCrLf
Función final Clase final
Amigo Clase SubsistemaC
Amigo Función C1 () Como cadena
Devuelve "Subsistema C, Método C1" & vbCrLf
Función final Clase final
espacio de nombres final
'Fachada
'. "sabe" qué clases de subsistema abordar la solicitud
' . delega las solicitudes de los clientes a los objetos apropiados dentro del subsistema
Public NotInheritable Class Facade
Sub privado Nuevo ()
End Sub
Biblioteca compartida como nueva . _ SubsistemaA () Compartido b Como nueva biblioteca . SubsystemB () Compartido c Como nueva biblioteca . SubsistemaC ()
Suboperación pública compartida 1 ( ) Consola . WriteLine ( "Operación 1" & vbCrLf & a . A1 () & a . A2 () y b . B1 ()) Fin sub
Suboperación pública compartida 2 ( ) Consola . WriteLine ( "Operación 2" & vbCrLf & b . B1 () & c . C1 ()) End Sub End Class
programa de clase
Sub principal compartido ()
Fachada . Operación1 ()
Fachada . Operación2 ()
'Esperando la acción del usuario
Consola . Lectura ()
End Sub
End Class
Delfos
Texto fuente en
Delphi
programa Patrón de Fachada ;
{$CONSOLA DE TIPO DE APLICACIÓN}
utiliza
SysUtils ;
tipo
TComputer = clase
procedimiento público
PlugIn ; procedimiento PowerMonitor ; procedimiento Poder ; fin ;
procedimiento TComputer . enchufar ;
begin
WriteLn ( 'Incluido en la red' ) ;
fin ;
procedimiento TComputer . PowerMonitor ;
begin
WriteLn ( 'Enciende el monitor' ) ;
fin ;
procedimiento TComputer . poder ;
begin
WriteLn ( 'Enciende la unidad del sistema' ) ;
fin ;
escriba
TNotebook = procedimiento de clase
Power ; fin ;
procedimiento TNotebook . poder ;
comenzar
WriteLn ( 'Presione el botón de encendido' ) ;
fin ;
tipo
TKettle = complemento de procedimiento de clase
; procedimiento Poder ; fin ;
procedimiento TKettle . poder ;
comenzar
WriteLn ( 'Presione el botón de encendido' ) ;
fin ;
procedimiento TKettle . enchufar ;
begin
WriteLn ( 'Incluido en la red' ) ;
fin ;
tipo
TFacade = clase
procedimiento público
PowerOn ( aDevice : TObject ) ; fin ;
procedimiento TFachada . PowerOn ( unDispositivo : TObject ) ;
comenzar
si aDevice es TComputer entonces
con TComputer ( aDevice ) comience PlugIn ; _
PowerMonitor ; poder ; fin ;
si aDevice es TNotebook entonces
con TNotebook ( aDevice ) hacer
Power ;
si aDevice es TKettle entonces
con TKettle ( aDevice ) comience PlugIn ; _
poder ; fin ;
WriteLn
end ;
comience
con TFacade . Crear intente PowerOn ( TComputer . Create ) ; _
Encendido ( TNotebook.Create ) ; _ _ Encendido ( TKettle.Create ) ; _ _ finalmente Libre ; fin ; Leerln ; fin _
Java
Fuente
Java
/* Partes complejas */
clase CPU {
public void freeze () {
System . fuera _ println ( "congelar" );
}
public void jump ( posición larga ) { System . fuera _ println ( "posición de salto =" + posición ); }
public void ejecutar () {
System . fuera _ println ( "ejecutar" );
}
}
class Memory {
public void load ( posición larga , byte [] datos ) { System . fuera _ println ( "cargar posición = " + posición + ", datos = " + datos ); } }
class HardDrive {
byte público [] leído ( long lba , int size ) { System . fuera _ println ( "leer lba = " + lba + ", tamaño = " + tamaño ); devolver nuevo byte [ tamaño ] ; } }
/* fachada */
clase Computadora {
privada final estática larga BOOT_ADDRESS = 1L ;
privado final estático largo BOOT_SECTOR = 2L ;
privado final estático int SECTOR_SIZE = 3 ;
CPU CPU privada ;
memoria privada ; _ disco duro privado disco duro ;
Computadora pública () {
this . cpu = nueva cpu ();
esto _ memoria = nueva memoria ();
esto _ disco duro = nuevo disco duro ();
}
public void startComputer () {
cpu . congelar ();
memoria _ cargar ( BOOT_ADDRESS , disco duro . leer ( BOOT_SECTOR , SECTOR_SIZE ));
CPU _ saltar ( BOOT_ADDRESS );
CPU _ ejecutar ();
}
}
/* Cliente */
aplicación de clase {
public static void main ( String [] args ) {
Computer computer = new Computer ();
computadora _ iniciarEquipo ();
}
}
haxe
Texto de origen en
idioma Haxe
/**
* Implementaciones de partes individuales de computadora.
* Cada método de clase tiene algún tipo de implementación, en este ejemplo se omite.
*/
/**
* Class CPU, responsable del funcionamiento del procesador
*/
class CPU {
public function new () {
}
función pública congelar (): Void { //... }
salto de función pública ( posición : Int ): Void { //... }
función pública ejecutar (): Void {
//...
}
}
/**
* Class Memory, responsable del funcionamiento de la memoria
*/
class Memory {
public static inline var BOOT_ADDRESS
: Int = 0x0005 ;
función pública nueva () {
}
carga de función pública ( posición : Int , datos : haxe . io . Bytes ): Void { //... } }
/**
* Class HardDrive, responsable del funcionamiento del disco duro
*/
class HardDrive {
public static inline var BOOT_SECTOR
: Int = 0x001 ;
public static inline var SECTOR_SIZE
: Int = 64 ;
función pública nueva () {
}
función pública de lectura ( lba : Int , tamaño : Int ): haxe . yo _ Bytes { //... devuelve nulo ; } }
/**
* Un ejemplo del patrón "Fachada"
* La computadora se usa como un objeto unificado.
* Detrás de este objeto se ocultarán todos los detalles del trabajo de sus partes internas.
*/
class Computadora {
private var cpu
: CPU ;
memoria
var privada : Memoria ; unidad de disco duro
privada var : unidad de disco duro ;
/**
* Constructor de la computadora.
* Inicializar partes
*/
función pública new () { this . cpu = nueva cpu (); esto _ memoria = nueva memoria (); esto _ disco duro = nuevo disco duro (); }
/**
* Manejo simplificado del comportamiento de "inicio de la computadora"
*/
public function startComputer (): Void {
cpu . congelar ();
memoria _ cargar (
Memoria . BOOT_ADDRESS ,
disco duro . leer ( Disco duro . BOOT_SECTOR , Disco duro . SECTOR_SIZE ) ); CPU _ saltar ( Memoria.BOOT_ADDRESS ) ; _ CPU _ ejecutar (); } }
/**
* Los usuarios de computadoras cuentan con una fachada (computadora)
* que oculta toda la complejidad de trabajar con componentes individuales.
*/
class Application {
public static function main (): Void {
var computer
: Computer = new Computer ();
computadora _ iniciarEquipo ();
}
}
Rápido
Código fuente
rápido
//
CPU de clase lógica {
public func freeze () -> String {
devuelve "Procesador de congelación".
}
public func jump ( posición : String ) -> String {
return "Saltando a: \( posición ) "
}
función pública ejecutar () -> Cadena {
devuelve "Ejecutando".
}
}
memoria de clase {
public func load ( posición : Cadena , datos : Cadena ) -> Cadena {
return "Cargando desde \( posición ) datos: \( datos ) "
}
}
disco duro de clase {
public func read ( lba : String , size : String ) -> String {
return "Algunos datos del sector \( lba ) con tamaño \( size ) "
}
}
//
Clase de fachada ComputerFacade {
privado dejar cpu = CPU ()
privado dejar memoria = memoria ()
privado dejar disco duro = disco duro ()
inicio de función pública () { cpu . congelar () let ssd = disco duro . leer ( lba : "100" , tamaño : "1024" ) memoria . cargar ( posición : "0x00" , datos : ssd ) cpu . salto ( posición : "0x00" ) CPU . ejecutar () }
}
// Cliente
let pc = ComputerFacade ()
pc . empezar ()
Literatura
- E. Gamma, R. Helm, R. Johnson, J. Vlissides . Técnicas de diseño orientado a objetos. Patrones de Diseño = Patrones de Diseño: Elementos de Software Reutilizable Orientado a Objetos. - San Petersburgo. : " Pedro ", 2007. - S. 366. - ISBN 978-5-469-01136-1 . (también ISBN 5-272-00355-1 )
Fuentes y enlaces