Construcción de Software (Patrones)

483 views

Published on

Programación Orientada a Objetos

Published in: Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
483
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
9
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Construcción de Software (Patrones)

  1. 1. Construcción de SoftwareCapítulo 3Patrones de diseño
  2. 2. 2Contenidos1. Concepto de patrón2. Clasificación de patrones3. Estudio de algunos de los principales patrones GoF• Adaptador• Factoría• Singleton• Estrategia• Composite• Fachada• Observador
  3. 3. 3Bibliografía• [Larman 02] Larman, C. “UML y Patrones: Unaintroducción al análisis y diseño orientado aobjetos y al proceso unificado”, Segunda Edición,Prentice-Hall, 2002. Capítulo 23.• [Larman 05] Larman, C. “Applying UML andPatterns. An Introduction to Object-OrientedAnalysis and Design and Iterative Development”,3rd edition, Prentice-Hall, 2005. Capítulo 26.• [Gamma et al. 02] Gamma E., et al., Patrones deDiseño, Addison-Wesley, 2002.
  4. 4. 4Arquitectura software y patrones“Una arquitectura orientada a objetos bienestructurada está llena de patrones. La calidad de unsistema orientado a objetos se mide por la atenciónque los diseñadores han prestado a las colaboracionesentre sus objetos.”“Los patrones conducen a arquitecturas máspequeñas, más simples y más comprensibles”G. Booch
  5. 5. 5Diseño orientado a objetos• “Diseñar software orientado a objetos es difícil perodiseñar software orientado a objetos reutilizable esmás difícil todavía. Diseños generales y flexibles sonmuy difíciles de encontrar la primera vez”• ¿Qué conoce un programador experto que desconoceuno inexperto?Reutilizar soluciones que funcionaron en el pasado:EXPERIENCIA
  6. 6. 6Patrones• Describen un problema recurrente y una solución.• Cada patrón nombra, explica, evalúa un diseñorecurrente en sistemas OO.• Elementos principales:– Nombre– Problema– Solución: Descripción abstracta– Consecuencias
  7. 7. 7Granularidad• Patrones de Código– Nivel de lenguaje de programación• Frameworks– Diseños específicos de un dominio de aplicaciones.• Patrones de Diseño– Descripciones de clases cuyas instancias colaboran entre síque deben ser adaptados para resolver problemas de diseñogenerales en un contexto particular.– Un patrón de diseño identifica: Clases, Instancias, Roles,Colaboraciones y la Distribución de responsabilidades.
  8. 8. 8Patrones y frameworks• Un framework es una colección organizada declases que constituyen un diseño reutilizable para undominio específico de software.• Necesario adaptarlo a una aplicación particular.• Establece la arquitectura de la aplicación• Diferencias:– Patrones son más abstractos que los frameworks– Patrones son elementos arquitecturales más pequeños– Patrones son menos especializados
  9. 9. 9Modelo-Vista-Control(MVC paradigm o MVC framework)• Utilizado para construir interfaces de usuario enSmalltalk-80.• Basado en tres tipos de objetos:– Modelo: objetos del dominio– Vista: objetos presentación en pantalla (interfaz de usuario)– Controlador: define la forma en que la interfaz reacciona ala entrada del usuario.• Desacopla el modelo de las vistas.
  10. 10. 01020304050607080901er trim. 2do trim. 3er trim. 4to trim.EsteOesteNorte1tr3tr4tr2tr1tr. 2tr. 3tr. 4tr.Este 20 25 90 20Oeste 30 38 32 32Norte 47 47 45 45Vistast1=20t2=25t3=90t4=20Modelo
  11. 11. 11Modelo-Vista-Control• Utiliza los siguientes patrones:– Observer– Composite– Strategy– Decorator– Factory method
  12. 12. 12Plantilla de definición• Nombre del patrón• Propósito− ¿Qué hace? ¿Cuál es su razón y propósito? ¿Quécuestión de diseño particular o problema aborda?• Sinónimos• Motivación− Escenario que ilustra un problema particular y cómo elpatrón lo resuelve.
  13. 13. 13Plantilla de definición• Aplicabilidad− ¿En qué situaciones puede aplicarse? ¿Cómo lasreconoces? Ejemplos de diseños que pueden mejorarse• Estructura− Diagramas de clases que ilustran la estructura• Participantes− Clases que participan y sus responsabilidades• Colaboraciones− Diagramas de interacción que muestran cómocolaboran los participantes.
  14. 14. 14Plantilla de definición• Consecuencias– ¿Cómo alcanza el patrón sus objetivos? ¿Cuáles son loscompromisos y resultados de usar el patrón? Alternativas,Costes y Beneficios• Implementación– Técnicas, heurísticas y consejos para la implementación– ¿Hay cuestiones dependientes del lenguaje?• Ejemplo de código• Usos conocidos• Patrones relacionados
  15. 15. 15Clasificación de patrones de diseñoÁmbitoPropósitoEstructural ComportamientoCreaciónClaseObjetoFactory Method Adapter InterpreterTemplate MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsabilityCommandIteratorMediatorMementoObserverStateStrategyVisitor
  16. 16. 16AdaptadorEn NuevaEra...• La aplicación NuevaEra necesita soportardiferentes tipos de servicios externos de terceraspartes, entre los que se encuentran los calculadoresde impuestos, servicios de autorización de pagos,sistemas de inventario, y sistemas de contabilidad.Cada uno tiene una API diferente, que no puedeser cambiada.• Una solución es añadir un nivel de indirección conobjetos que adapten las distintas interfacesexternas a una interfaz consistente que es la que seutiliza en la aplicación.
  17. 17. 17Adaptador (GoF)• Nombre: Adaptador (Adapter / Wrapper)• Contexto/Problema: ¿Cómo resolverinterfaces incompatibles, o proporcionaruna interfaz estable para componentessimilares con diferentes interfaces?• Solución (consejo): Convertir la interfazoriginal de un componente en otra interfaz,a través de un objeto adaptador intermedio.
  18. 18. 18AdaptadorTaxMasterAdaptergetTaxes( Sale ) : List of TaxLineItemsGoodAsGoldTaxProAdaptergetTaxes( Sale ) : List of TaxLineItems«interface»ITaxCalculatorAdaptergetTaxes( Sale ) : List of TaxLineItemsAdapters use interfaces andpolymorphism to add a level ofindirection to varying APIs in othercomponents.SAPAccountingAdapterpostReceivable( CreditPayment )postSale( Sale )...GreatNorthernAccountingAdapterpostReceivable( CreditPayment )postSale( Sale )...«interface»IAccountingAdapterpostReceivable( CreditPayment )postSale( Sale )...«interface»IInventoryAdapter...«interface»ICreditAuthorizationServiceAdapterrequestApproval(CreditPayment,TerminalID, MerchantID)...
  19. 19. 19Adaptador• Una instancia de adaptador será instanciada para elservicio externo elegido, tal comoSAPAccountingAdapter, y adaptará la peticiónpostSale al interfaz externo, como p.ej. un interfazSOAP XML sobre HTTPS para un servicio Webde intranet ofrecido por SAP.• Guía: Es común es que el nombre del patrón seinserte en los nombres de los tipos involucrados, deforma que se transmita rápidamente qué patronesestán involucrados en un fragmento de código.
  20. 20. 20Adaptador:Register : SAPAccountingAdapterpostSale( sale )makePaymentthe Adapter adapts tointerfaces in other componentsSOAP overHTTPxxx...«actor»: SAPSystem
  21. 21. 21FactoríaEn NuevaEra...• ¿Quién crea el adaptador? ¿Y cómo determinarqué clase de adaptador crear?• Si los creara algún objeto del dominio, lasresponsabilidades de los objetos del dominioexcederían la lógica pura de la aplicación yentrarían en cuestiones relacionadas con laconexión con componentes software externos.
  22. 22. 22Factoría• Nombre: Factoría concreta (Simple Factory /Concrete Factory)• Contexto/Problema: ¿Quién debe ser elresponsable de la creación de los objetos cuandoexisten consideraciones especiales, como unalógica de creación compleja, el deseo de separarlas responsabilidades de la creación para mejorarla cohesión, etc.?• Solución (consejo): Crear un objeto FabricaciónPura (es decir, que no pertenece al modeloconceptual) denominado Factoría que maneje lacreación.
  23. 23. 23FactoríaServicesFactoryaccountingAdapter : IAccountingAdapterinventoryAdapter : IInventoryAdaptertaxCalculatorAdapter : ITaxCalculatorAdaptergetAccountingAdapter() : IAccountingAdaptergetInventoryAdapter() : IInventoryAdaptergetTaxCalculatorAdapter() : ITaxCalculatorAdapter...note that the factory methodsreturn objects typed to aninterface rather than a class, sothat the factory can return anyimplementation of the interfaceif ( taxCalculatorAdapter == null ){// a reflective or data-driven approach to finding the right class: read it from an// external propertyString className = System.getProperty( "taxcalculator.class.name" );taxCalculatorAdapter = (ITaxCalculatorAdapter) Class.forName( className ).newInstance();}return taxCalculatorAdapter;
  24. 24. 24FactoríaLos objetos factoría tienen varias ventajas:• Separan la responsabilidad de la creacióncompleja en objetos de apoyo cohesivos.• Ocultan la lógica de la creaciónpotencialmente compleja.• Permiten introducir estrategias para mejorarel rendimiento de la gestión de la memoria,como objetos caché o de reciclaje.
  25. 25. 25FactoríaEn NuevaEra…• La lógica para decidir qué clase se crea se resuelveleyendo el nombre de la clase de una fuenteexterna (p.ej., por medio de una propiedad delsistema, si se usa Java) y después se carga la clasedinámicamente.• Es un ejemplo de diseño dirigido por los datos.• A menudo se accede a las factorías con el patrónSingleton.
  26. 26. 26SingletonEn NuevaEra...• ¿Quién crea la factoría de servicios y cómose accede?– Sólo se necesita una instancia de la factoría enel proceso.– ¿Cómo conseguir visibilidad a la factoría?⇒ Es preciso mantener visibilidad global auna única instancia de una clase
  27. 27. 27Singleton (GoF)• Nombre: Singleton• Contexto / Problema: Se admiteexactamente una instancia de una clase –esun “singleton”. Los objetos necesitan unúnico punto de acceso global.• Solución (consejo): Definir un métodoestático de la clase que devuelva elsingleton.
  28. 28. 28Singleton1ServicesFactoryinstance : ServicesFactoryaccountingAdapter : IAccountingAdapterinventoryAdapter : IInventoryAdaptertaxCalculatorAdapter : ITaxCalculatorAdaptergetInstance() : ServicesFactorygetAccountingAdapter() : IAccountingAdaptergetInventoryAdapter() : IInventoryAdaptergetTaxCalculatorAdapter() : ITaxCalculatorAdapter...singleton staticattributesingletonstaticmethod// static methodpublic static synchronized ServicesFactory getInstance(){if ( instance == null )instance = new ServicesFactory()return instance}UML notation: in aclass box, anunderlined attribute ormethod indicates astatic (class level)member, rather thanan instance memberUML notation: this 1 can optionally be used toindicate that only one instance will be created (asingleton)
  29. 29. 29Singletonpublic class Register{public void initialize(){...hace algún trabajo...// acceso a la Factoría Singleton mediante la llamada// a “getInstancia”accountingAdapter =ServicesFactory.getInstance().getAccountingAdapter();...hace algún trabajo...}// otros métodos}
  30. 30. 30Singleton:Register1:ServicesFactoryaa = getAccountingAdapterinitialize...the ‘1’ indicates that visibilityto this instance was achievedvia the Singleton pattern
  31. 31. 31Singleton• Usualmente, inicialización perezosa:public static synchronized ServicesFactory getInstance(){if ( instance == null ){// sección crítica si es una aplicación// con varios hilosinstance = new ServicesFactory() ;}return instance ;}
  32. 32. 32Servicios externos con diversasinterfaces – Visión general:RegisteraccountingAdapter:SAPAccountingAdapterpostSale( sale )makePaymentSOAP overHTTPxxx:Register1:ServicesFactoryaccountingAdapter =getAccountingAdapter:Storecreatecreate: SAPAccountingAdapter: Paymentcreate(cashTendered)«actor»: SAPSystemcreate
  33. 33. 33EstrategiaEn NuevaEra…• La estrategia de fijación de precios de una ventapuede variar. Durante un periodo podría ser el10% de descuento en todas las ventas, despuéspodría ser un descuento de 10€ si el total de laventa es superior a 200€, y podrían ocurrir muchasotras variaciones.⇒ ¿Cómo diseñamos los diversos algoritmos defijación de precios?
  34. 34. 34Estrategia (GoF)• Nombre: Estrategia.• Contexto/Problema: ¿Cómo diseñardiversos algoritmos o políticas que estánrelacionadas? ¿Cómo diseñar que estosalgoritmos o políticas puedan cambiar?• Solución (consejo): Definir cadaalgoritmo/política/estrategia en una claseindependiente, con una interfaz común.
  35. 35. 35EstrategiaPercentDiscountPricingStrategypercentage : floatgetTotal( s:Sale ) :MoneyAbsoluteDiscountOverThresholdPricingStrategydiscount : Moneythreshold : MoneygetTotal( s:Sale ) :Money«interface»ISalePricingStrategygetTotal( Sale ) : Money{return s.getPreDiscountTotal() * percentage}???PricingStrategy......{pdt := s.getPreDiscountTotal()if ( pdt < threshold )return pdtelsereturn pdt - discount}
  36. 36. 36Estrategia• Un objeto estrategia se conecta a un objetode contexto –el objeto al que se aplica elalgoritmo, Venta en este caso.• Es habitual que el objeto de contexto paseuna referencia a él mismo (this) al objetoestrategia.• El objeto de contexto necesita tenervisibilidad de atributo de su estrategia.
  37. 37. 37Estrategia:PercentDiscountPricingStrategys : Salest = getSubtotalt = getTotallineItems[ i ] :SalesLineItemt = getTotal( s )pdt = getPreDiscountTotal{ t = pdt * percentage }note that the Sale s ispassed to the Strategy sothat it has parametervisibility to it for furthercollaborationloop
  38. 38. 38EstrategiaPercentDiscountPricingStrategypercentage : floatgetTotal( Sale ) : MoneyAbsoluteDiscountOverThresholdPricingStrategydiscount : Moneythreshold : MoneygetTotal( Sale ) : Money«interface»ISalePricingStrategygetTotal( Sale ) : MoneySaledate...getTotal()...1Sale needs attributevisibility to its StrategypricingStrategygetTotal(){...return pricingStrategy.getTotal( this )}
  39. 39. 39Creación de una Estrategiamediante una Factoría1PricingStrategyFactoryinstance : PricingStrategyFactorygetInstance() : PricingStrategyFactorygetSalePricingStrategy() : ISalePricingStrategygetSeniorPricingStrategy() : ISalePricingStrategy...{String className = System.getProperty( "salepricingstrategy.class.name" );strategy = (ISalePricingStrategy) Class.forName( className ).newInstance();return strategy;}:Sale1:PricingStrategyFactoryps =getSalePricingStrategy:RegistermakeNewSalecreatecreate(percent) ps : PercentDiscountPricingStrategy
  40. 40. 40CompositeEn NuevaEra…• ¿Cómo gestionar el caso de varias políticas contradictoriasde fijación de precios? En un momento dado puedencoexistir múltiples estrategias, según el periodo de tiempo,el tipo de producto, o el tipo de cliente.• Se debe definir la estrategia de resolución de conflictos dela tienda (normalmente, se aplica “lo mejor para elcliente”, pero no es obligatorio).• Necesitamos cambiar el diseño de forma que el objetoVenta no conozca si está tratando con una o másestrategias, y que se resuelvan los conflictos.
  41. 41. 41Composite (GoF)• Nombre: Composite• Contexto/Problema: ¿Cómo tratar un grupoo una estructura compuesta del mismomodo (polimórficamente) que un objeto nocompuesto (atómico)?• Solución (consejo): Definir las clases paralos objetos compuestos y atómicos demanera que implementen el mismo interfaz.
  42. 42. 42CompositePercentageDiscountPricingStrategypercentage : floatgetTotal( Sale ) : MoneyAbsoluteDiscountOverThresholdPricingStrategydiscount : Moneythreshold : MoneygetTotal( Sale ) : Money«interface»ISalePricingStrategygetTotal( Sale ) : Money{return sale.getPreDiscountTotal() *percentage}CompositePricingStrategyadd( ISalePricingStrategy )getTotal( Sale ) : Money{lowestTotal = INTEGER.MAXfor each ISalePricingStrategy strat in pricingStrategies{total := strat.getTotal( sale )lowestTotal = min( total, lowestTotal )}return lowestTotal}1..*CompositeBestForCustomerPricingStrategygetTotal( Sale ) : MoneyCompositeBestForStorePricingStrategygetTotal( Sale ) : MoneystrategiesAll composites maintain a list ofcontained strategies. Therefore,define a common superclassCompositePricingStrategy thatdefines this list (named strategies).Saledate...getTotal()...1pricingStrategy{...return pricingStrategy.getTotal( this )}
  43. 43. 43Composite:CompositeBestForCustomerPricingStrategys : Salest = getSubtotalt = getTotallineItems[ i ] :SalesLineItemt = getTotal( s )the Sale object treats a Composite Strategy that containsother strategies just like any other ISalePricingStrategyx = getTotal( s )strategies[ j ] :: ISalePricingStrategyUML: ISalePricingStrategy is an interface, not a class;this is the way in UML 2 to indicate an object of anunknown class, but that implements this interface{ t = min(set of all x) }looploop
  44. 44. 44Creación de múltiples instanciasEstrategiaFijarPreciosVenta• Crear un Composite que contenga las políticas de descuento de latienda en el momento actual (inicialmente, 0% si no hay ningunaactiva).:Sale1:PricingStrategyFactoryps =getSalePricingStrategy:RegistermakeNewSalecreatecreateps :CompositeBestForCustomerPricingStrategycreate( percent ) s : PercentageDiscountPricingStrategyadd( s )
  45. 45. 45Creación de múltiples instanciasEstrategiaFijarPreciosVenta• Para introducir el descuento según el tipo de cliente, espreciso introducir un nueva operación del sistema:s :Sale:RegisterenterCustomerForDiscount( custID )by Controllerby Expert andIDs to Objects:Storec = getCustomer( custID )enterCustomerForDiscount( c : Customer )by Expertref Enter CustomerFor Discount
  46. 46. 46Creación de múltiples instanciasEstrategiaFijarPreciosVentas :Sale1:PricingStrategyFactoryaddCustomerPricingStrategy( s )ps :CompositeBestForCustomerPricingStrategycreate( pct ) s : PercentageDiscountPricingStrategyadd( s )by ExpertenterCustomerForDiscount( c : Customer )c = getCustomerby Factory andHigh Cohesionby Expertps = getPricingStrategypct =getCustomerPercentage( c )by High Cohesionby Factory and CompositePassAggregateObject asParametersd Enter Customer For Discount
  47. 47. 47FachadaEn NuevaEra…• Se quiere dar soporte a reglas de negocioconectables.• Se desea definir un subsistema “motor dereglas” que será responsable de evaluar unconjunto de reglas contra una operación, eindicar si alguna de las reglas invalida laoperación.
  48. 48. 48Fachada (GoF)• Nombre: Fachada (Facade)• Contexto/Problema: Se requiere una interfaz común,unificada para un conjunto de implementaciones ointerfaces dispares –como en un subsistema. Podría no serconveniente acoplarse con muchas cosas del subsistema, ola implementación del subsistema podría cambiar. ¿Quéhacemos?• Solución (consejo): Definir un punto único de conexióncon el subsistema –un objeto fachada que envuelve alsubsistema. Este objeto fachada presenta una única interfazunificada y es responsable de colaborar con loscomponentes del subsistema.
  49. 49. 49FachadaDomain+ Sale + Register ...POSRuleEngine«interface»- IRule...- Rule1...- Rule2......package name may beshown in the tabvisibility of the package element (tooutside the package) can be shownby preceding the element name with avisibility symbol+ POSRuleEngineFacadeinstance : RuleEngineFacadegetInstance() : RuleEngineFacadeisInvalid( SalesLineItem, Sale )isInvalid( Payment, Sale )...*
  50. 50. 50Fachadapublic class Venta{public void crearLineaDeVenta( EspecificacionDelProducto espec, intcantidad){LineaDeVenta ldv = new LineaDeVenta( espec, cantidad );// llamada a la fachada, obsérvese el uso de Singletonif (FachadaMotorReglasPDV.getInstancia().esInvalido( ldv, this ))return;lineasDeVenta.add( ldv );}//…} // final de la clase
  51. 51. 51ObservadorEn NuevaEra…• Se pretende añadir lacapacidad de queuna ventana GUIactualice lainformación quemuestra sobre eltotal de la ventacuando éste cambia.Goal: When the total of the salechanges, refresh the display withthe new valueSaletotal...setTotal( newTotal )...
  52. 52. 52Observador (GoF)• Nombre: Observador / Observer / Publicar-Suscribir /Modelo de delegación de eventos• Contexto/Problema: Diferentes tipos de objetossuscriptores están interesados en el cambio de estado oeventos de un objeto emisor, y quieren reaccionar cada unoa su manera cuando el emisor genere un evento. Además,el emisor quiere mantener bajo acoplamiento con lossuscriptores. ¿Qué hacemos?• Solución (consejo): Definir un interfaz “suscriptor” u“oyente” (listener). Los suscriptores implementan esteinterfaz. El emisor dinámicamente puede registrarsuscriptores que están interesados en un evento, ynotificarles cuando ocurre un evento.
  53. 53. 53ObservadorEn NuevaEra…¿porqué no vale la siguiente solución?“Cuando la Venta cambia su total, el objeto Venta envíaun mensaje a la ventana, pidiéndole que actualice lainformación que muestra.”⇒El principio de separación Modelo-Vista disuade detales soluciones. Los objetos del modelo no deberíanconocer los objetos de la vista o presentación.⇒De esa manera, se permite reemplazar la vista o capa depresentación por una nueva sin afectar a los objetos queno pertenecen a la interfaz de usuario.
  54. 54. 54Observador«interface»PropertyListeneronPropertyEvent( source, name, value )SaleFrame1onPropertyEvent( source, name, value )initialize( Sale sale )...javax.swing.JFrame...setTitle()setVisible()...{if ( name.equals("sale.total") )saleTextField.setText( value.toString() );}SaleaddPropertyListener( PropertyListener lis )publishPropertyEvent( name, value )setTotal( Money newTotal )...*propertyListeners{total = newTotal;publishPropertyEvent( "sale.total", total );}{propertyListeners.add( lis );}{for each PropertyListener pl in propertyListenerspl.onPropertyEvent( this, name, value );}{sale.addPropertyListener( this )...}
  55. 55. 55ObservadorIdeas y pasos fundamentales:1. Se define una interfaz; en este caso, PropertyListener con la operaciónonPropertyEvent.2. Se define la ventana que implementa la interfazSaleFrame1 implementará el método onPropertyEvent.3. Cuando se inicializa la ventana SaleFrame1, se le pasa la instancia de Salede la cual está mostrando el total.4. La ventana SaleFrame1 se registra o suscribe a la instancia de Sale para quele notifique acerca de los eventos sobre la propiedad, por medio del mensajeaddPropertyListener. Es decir, cuando una propiedad (como total) cambia,la ventana quiere que se le notifique.5. Obsérvese que Sale no conoce los objetos SaleFrame1; más bien sóloconoce los objetos que implementan la interfaz PropertyListener. Estodisminuye el acoplamiento entre la Venta y la ventana –se acopla sólo conuna interfaz, no con la clase de la GUI.6. Por tanto, la instancia de Sale es un emisor de “eventos sobre la propiedad”.Cuando cambia el total, itera sobre todos los objetos PropertyListener queestán suscritos, y se lo notifica a cada uno.
  56. 56. 56Observadors : Salesf : SaleFrame1initialize( s : Sale )addPropertyListener( sf )propertyListeners :List<PropertyListener>add( sf )El observador SaleFrame1 se suscribe al emisor Sale:
  57. 57. 57Observadors :SalesetTotal( total )onPropertyEvent( s, "sale.total", total )publishPropertyEvent( "sale.total", total )propertylisteners[ i ] :PropertyListenerloop: SaleFrame1onPropertyEvent( source, name, value )saleTextField: JTextFieldsetText( value.toString() )Since this is a polymorphic operation implemented bythis class, show a new interaction diagram that startswith this polymorphic versionUML notation: Note this little expression within theparameter. This is legal and consise.Sale publica un eventosobre la propiedad atodos sus suscriptoresEl suscriptor SaleFrame1recibe la notificación deun evento publicado

×