c# - pattern - patrón singleton
¿Cómo describirías el patrón Observer en el lenguaje para principiantes? (19)
Actualmente, mi nivel de comprensión está por debajo de todos los ejemplos de codificación en la web sobre el patrón de observador. Lo entiendo simplemente como casi una suscripción que actualiza todos los demás eventos cuando se realiza un cambio que el delegado registra. Sin embargo, soy muy inestable en mi verdadera comprensión de los beneficios y usos. He hecho algunas búsquedas en Google, pero la mayoría está por encima de mi nivel de comprensión.
Estoy tratando de implementar este patrón con mi tarea actual, y para que realmente tenga sentido en mi proyecto necesito una mejor comprensión del patrón en sí y quizás un ejemplo para ver cuál es su uso. No quiero forzar este patrón en algo solo para presentarlo, necesito entender el propósito y desarrollar mis métodos en consecuencia para que realmente sirva para un buen propósito. Mi texto realmente no entra en eso, solo lo menciona en una oración. MSDN me resultó difícil de entender, ya que soy un principiante en esto, y parece ser un tema más avanzado.
¿Cómo describirías este patrón de Observer y sus usos en C # para un principiante? Por ejemplo, mantenga el código muy simple para que pueda entender el propósito más que los fragmentos de código complejos. Intento usarlo de manera efectiva con algunas manipulaciones simples de cadenas de texto y el uso de delegados para mi tarea, ¡así que un puntero ayudaría!
Echa un vistazo a "Head First: Design Patterns" para ver algunas descripciones realmente fáciles de seguir de los principales patrones.
Para Observer, es importante comprender que describe una relación de uno a varios y utiliza un modelo de suscripción para contarle a otras clases cuando ha habido un cambio. RSS, Atom y Twitter funcionan en esta línea.
El Observer quiere saber cuándo algo cambia, por lo que se suscribe al Sujeto. El Sujeto no conoce al Observador. Esta es la parte importante. El sujeto simplemente define la interfaz (o delegado) que el observador debe proporcionar, y permite el registro.
En resumen: el patrón Observer le permite a su observador ser llamado por un sujeto, que no le importa quién es el observador y si incluso existe.
El mejor ejemplo que se me ocurre es el de una lista de correo (como ejemplo).
Usted, el observador, suscríbase a una lista de correo y observe la lista. Cuando ya no está interesado en la lista, se da de baja.
Este concepto es el patrón del observador. Dos o más clases están involucradas. Una o más clases, se suscribe a una clase de editor (hay diferentes nombres) y luego la primera clase (y todas las clases suscriptoras) recibirán una notificación cuando lo desee el editor.
Así es como se lo expliqué a mi esposa, que a menudo escucha mis desvaríos sobre programación y teoría del diseño. Tenía sentido para ella. Me doy cuenta de que esto puede ser demasiado simple para ti, pero es un buen comienzo ...
Saludos,
Franco
El observador es un medio de desacoplo, es decir, aflojar las conexiones entre dos objetos. Desea eso porque hace que su código sea más ordenado y más fácil de mantener. Ese es prácticamente el objetivo de todos los patrones de diseño: código más fácil de leer y fácil de mantener.
En este patrón, tiene dos clases, dos objetos: editor y observador. Publisher es una clase que realmente hace un poco de trabajo, luego de vez en cuando llamará a los métodos sobre cualquier observador para contarles al respecto. Sabe a qué clases llamar porque mantiene una lista de observadores que se suscribieron.
Por lo tanto, su editor podría verse más o menos así:
class Publisher
{
List<Observer> observers = new List<Observer>();
public Add(Observer o)
{
observers.Add(o);
}
private AlertObservers()
{
foreach(Observer o in observers)
{
o.Alert();
}
}
Publisher realmente hace la mayor parte del trabajo. Todo lo que el observador debe hacer es agregarse a la lista e implementar el método llamado. Al igual que:
class Observer
{
public Observer(Publisher pub)
{
pub.Add(this);
}
public Alert()
{
System.Console.WriteLine("Oh no, stuff is happening!");
}
}
Esa es una idea bastante simple de cómo funciona. Ahora, ¿por qué es eso valioso? Se ve muy bien acoplado ¿eh? Una razón para ello es porque no uso una interfaz, lo que me permitiría configurar muchas clases con la funcionalidad de Observer, y Publisher nunca debe saber nada más sobre ellas, excepto que pueden recibir una llamada Alert (). También tenga en cuenta que Publisher intentará llamar a Alert en todos los observadores que tenga, incluso si no tiene ninguno.
Ahora, en el mundo C #, el lenguaje tiene una versión integrada de este patrón a través de sus objetos Event. Los eventos son muy potentes y hacen uso de Delegados, que es una manera de pasar un método como parámetro en otra llamada a método. Permiten un cierto desacoplamiento, pero lo guardaría para una nueva pregunta.
El patrón del observador es como suena -
Es un medio para que algunos objetos miren un objeto, observándolo por cambios.
En C #, esto se vuelve algo simple, ya que los eventos son básicamente un medio específico de lenguaje para implementar el patrón de observador. Si alguna vez ha usado eventos, ha usado el patrón de observador.
En otros idiomas, esto no está incorporado, por lo que ha habido muchos intentos de formalizar enfoques para manejar esto.
En términos muy simples, hay dos componentes: Observador y Observado.
Externamente, Observed necesita una forma de agregar (registrar) y eliminar un observador.
Internamente, el Observado necesita una lista de Observadores registrados.
El observador necesita un método público como Notify () o Notify (params).
Cada vez que le sucede algo en particular a Observed, recorrerá la lista y llamará a Notify () sobre cada observador registrado.
En el caso más simple, es una notificación simple que dice "Oye, observador, cambiaron mis datos, ven y refréscate tú mismo" En versiones más complejas, los parámetros pueden ser más allá de dejar que el observador sepa qué ha cambiado.
En un Modelo-Vista-Controlador, el Observado es generalmente un objeto de entidad - Algo que contiene datos. El controlador es el observador. Observa los cambios en el Modelo y le dice a la Vista que se actualice solo si está interesado en el cambio.
Los oyentes de eventos de Java son una implementación del mundo real de este patrón.
Este patrón es probablemente uno de los más básicos, si no el patrón más básico que existe.
Hay dos "personas" involucradas; el editor y el suscriptor / observador .
Un observador simplemente le pide al editor que le avise cuando hay "noticias". Las noticias pueden ser importantes aquí. Puede ser la temperatura del aire, puede ser una nueva publicación en un sitio web, puede ser la hora del día.
Hay dos objetos NOTIFICADOR y OBSERVADOR. NOTIFIER no sabe nada acerca de OBSERVER, mientras que OBSERVER sabe que NOTIFER implementa un evento.
OBSERVER usa el evento para informar a otros objetos que sucedió algo. Simplemente hablado, un evento es una lista de métodos. Como OBSERVER desea que se le notifique si ocurre algo, OBSERVER agrega un método, que debe invocarse si algo sucede, ante el evento de NOTIFER.
Entonces, si sucede algo que NOTIFIER publica con este evento, NOTIFIER simplemente pasa por la lista de métodos y los llama. Cuando se llama al método agregado por OBSERVER, OBSERVER sabe que sucedió y puede hacer lo que sea necesario en este caso.
Aquí hay una clase de notificador de ejemplo con un evento ValueChanged()
.
// Declare how a method must look in order to be used as an event handler.
public delegate void ValueChangedHandler(Notifier sender, Int32 oldValue, Int32 newValue);
public class Notifier
{
// Constructor with an instance name.
public Notifier(String name)
{
this.Name = name;
}
public String Name { get; private set; }
// The event that is raised when ChangeValue() changes the
// private field value.
public event ValueChangedHandler ValueChanged;
// A method that modifies the private field value and
// notifies observers by raising the ValueChanged event.
public void ChangeValue(Int32 newValue)
{
// Check if value really changes.
if (this.value != newValue)
{
// Safe the old value.
Int32 oldValue = this.value;
// Change the value.
this.value = newValue;
// Raise the ValueChanged event.
this.OnValueChanged(oldValue, newValue);
}
}
private Int32 value = 0;
// Raises the ValueChanged event.
private void OnValueChanged(Int32 oldValue, Int32 newValue)
{
// Copy the event handlers - this is for thread safty to
// avoid that somebody changes the handler to null after
// we checked that it is not null but before we called
// the handler.
ValueChangedHandler valueChangedHandler = this.ValueChanged;
// Check if we must notify anybody.
if (valueChangedHandler != null)
{
// Call all methods added to this event.
valueChangedHandler(this, oldValue, newValue);
}
}
}
Aquí un ejemplo de clase de observador.
public class Observer
{
// Constructor with an instance name.
public Observer(String name)
{
this.Name = name;
}
public String Name { get; private set; }
// The method to be registered as event handler.
public void NotifierValueChanged(Notifier sender, Int32 oldValue, Int32 newValue)
{
Console.WriteLine(String.Format("{0}: The value of {1} changed from {2} to {3}.", this.Name, sender.Name, oldValue, newValue));
}
}
Una pequeña aplicación de prueba.
class Program
{
static void Main(string[] args)
{
// Create two notifiers - Notifier A and Notifier B.
Notifier notifierA = new Notifier("Notifier A");
Notifier notifierB = new Notifier("Notifier B");
// Create two observers - Observer X and Observer Y.
Observer observerX = new Observer("Observer X");
Observer observerY = new Observer("Observer Y");
// Observer X subscribes the ValueChanged() event of Notifier A.
notifierA.ValueChanged += observerX.NotifierValueChanged;
// Observer Y subscribes the ValueChanged() event of Notifier A and B.
notifierA.ValueChanged += observerY.NotifierValueChanged;
notifierB.ValueChanged += observerY.NotifierValueChanged;
// Change the value of Notifier A - this will notify Observer X and Y.
notifierA.ChangeValue(123);
// Change the value of Notifier B - this will only notify Observer Y.
notifierB.ChangeValue(999);
// This will not notify anybody because the value is already 123.
notifierA.ChangeValue(123);
// This will not notify Observer X and Y again.
notifierA.ChangeValue(1);
}
}
La salida será la siguiente.
Observer X: The value of Notifier A changed from 0 to 123. Observer Y: The value of Notifier A changed from 0 to 123. Observer Y: The value of Notifier B changed from 0 to 999. Observer X: The value of Notifier A changed from 123 to 1. Observer Y: The value of Notifier A changed from 123 to 1.
Para comprender los tipos de delegados los voy a comparar con tipos de clases.
public class Example
{
public void DoSomething(String text)
{
Console.WriteLine(
"Doing something with ''" + text + "''.");
}
public void DoSomethingElse(Int32 number)
{
Console.WriteLine(
"Doing something with ''" + number.ToString() + "''.");
}
}
Definimos un Example
clase simple con dos métodos. Ahora podemos usar este tipo de clase.
Example example = new Example();
Mientras esto funciona, lo siguiente no funciona porque los tipos no coinciden. Usted obtiene un error de compilación.
Example example = new List<String>();
Y podemos usar el example
variable.
example.DoSomething("some text");
Ahora lo mismo con un tipo de delegado. Primero definimos un tipo de delegado; esta es solo una definición de tipo como la definición de clase anterior.
public delegate void MyDelegate(String text);
Ahora podemos usar el tipo de delegado, pero no podemos almacenar datos normales en una variable de tipo de delegado, sino un método.
MyDelegate method = example.DoSomething;
Ahora hemos almacenado el método DoSomething()
del example
objeto. Lo siguiente no funciona porque definimos MyDelegate
como un delegado que toma un parámetro de cadena y devuelve vacío. DoSomethingElse
devuelve void pero toma un parámetro entero para que obtenga un error de compilación.
MyDelegate method = example.DoSomethingElse;
Y finalmente puedes usar el method
la variable. No puede realizar la manipulación de datos porque la variable no almacena datos sino un método. Pero puede llamar al método almacenado en la variable.
method("Doing stuff with delegates.");
Esto llama al método que almacenamos en la variable - example.DoSomething()
.
Imagine que tiene un objeto cuyo comportamiento (o estado) desea observar. Por ejemplo, cuando el campo A llega al valor 10, usted desea informarse sobre ese evento sin realmente asociarse con los detalles de implementación de este objeto complejo que desea observar. Define una interfaz, llámala Observable y deja que tu objetivo implemente esta interfaz, debe tener al menos dos métodos para registrar y anular el registro de un observador, que a su vez es el objeto que Observer llamará cuando el campo A llegue a 10. Tu observador simplemente llama a Observable para registrarse (y anular el registro cuando termine). Observable normalmente mantiene una lista de observadores y los notifica a la vez, o según lo desee. También se puede hacer de forma síncrona o asíncrona, depende de usted. Esta es una explicación muy simplista sin escribir código. Una vez que lo comprende, las implementaciones pueden diferir en detalles para adaptarse a sus necesidades particulares.
Observer es como una línea de comunicación directa. En lugar de que todos sus parientes lo llamen para averiguar cómo está, cuando se enferma, escriba una tarjeta y todos los que estén interesados la reciban (o una copia). Cuando te recuperas, envías una tarjeta. Cuando te cortas el dedo del pie, envías una carta. Cuando obtienes una A, envías una carta.
Cualquier persona a la que le importe se sube a su lista de correo masivo y puede responder como mejor le parezca.
Esta dependencia es excelente para la IU. Si tengo un proceso que es lento (por ejemplo), puede disparar incluso cuando se realiza un progreso. Un elemento de barra de progreso podría observar eso y actualizar su cobertura. Un botón OK podría observar eso y activarse al 100%. Un cursor podría observar una animación hasta que el progreso sea 100%. Ninguno de estos observadores necesita saber el uno del otro. Además, ninguno de estos elementos estrictamente necesita saber qué los impulsa tampoco.
Si digo Evento , ¿está engañando?
Una palabra: Twitter
alt text http://headfirstlabs.com/Images/brain2.png Como dice: " Head First: Design Patterns ", también tienen algunos foros sobre el libro y una meditación de diseño .
Observer Pattern sigue el principio de Hollywood "No nos llames, te llamamos"
Buen sitio para patrones http://www.dofactory.com/Patterns/PatternObserver.aspx
En una oración:
Un objeto (el Sujeto) permite que otros objetos (Observadores) se registren para las notificaciones.
Ejemplo práctico:
Digamos que tienes una aplicación y quieres que otros desarrolladores creen complementos.
Podrías crear una clase PluginSubject y ponerle un método llamado NotifyOrderCreated. Cada vez que se crea un nuevo pedido en la pantalla de su orden, llama a PluginSubject.NotifyOrderCreated.
Cuando eso sucede, PluginSubject obtiene una lista de servidores de PluginObs y llama a PluginObserver.Notify en cada uno de ellos, pasando un mensaje que describe el evento.
Eso permite una funcionalidad realmente ordenada.
Mucho más de lo que quieres saber:
De hecho, lo hice recientemente, así que voy a dar un ejemplo más profundo: si requiere que sus observadores implementen una interfaz especial, digamos IPluginObserver, puede usar el reflejo para recorrer los tipos en su ensamblaje y crear instancias de los complementos sobre la marcha.
Luego, puede permitir a los usuarios registrar sus propios ensamblajes (debe almacenar una lista de nombres de ensamblaje en algún lugar y luego caminar), y ¡bam, tiene capacidad de extensión!
Observer (Publicar / Suscribir)
Cuando un objeto cambia de estado, notifica a otros objetos que han registrado su interés en el tiempo de ejecución. El objeto notificador (editor) envía un evento (publicación) a todos sus observadores (suscriptores).
Probablemente, lo que está teniendo problemas es definir las interfaces adecuadas. La interfaz define la interacción entre el suscriptor y el editor.
Primero crea una aplicación C # WinForms
Configurar Program.cs como este
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
interface IObserver
{
void Refresh(List<string> DisplayList);
}
class ObserverList : List<IObserver>
{
public void Refresh(List<String> DisplayList)
{
foreach (IObserver tItem in this)
{
tItem.Refresh(DisplayList);
}
}
}
}
Estamos haciendo dos cosas aquí. La primera es la interfaz que los suscriptores implementarán. Luego, una lista para que el editor contenga todos los suscriptores.
Luego haga la forma uno con dos botones, uno etiquetado como el Formulario 2 y el otro Formulario etiquetado 3. Luego agregue un cuadro de texto, luego otro botón etiquetado Agregar
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private List<string> DataList= new List<string>();
private ObserverList MyObservers = new ObserverList();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form2 frmNewForm = new Form2();
MyObservers.Add(frmNewForm);
frmNewForm.Show();
MyObservers.Refresh(DataList);
}
private void button2_Click(object sender, EventArgs e)
{
Form3 frmNewForm = new Form3();
MyObservers.Add(frmNewForm);
frmNewForm.Show();
MyObservers.Refresh(DataList);
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button3_Click(object sender, EventArgs e)
{
DataList.Add(textBox1.Text);
MyObservers.Refresh(DataList);
textBox1.Text = "";
}
}
}
Deliberadamente configuré el botón Form2 y el botón FOrm3 para hacer varias copias de cada tipo de formulario. Por ejemplo, puedes tener doce al mismo tiempo.
Notarás que después de crear cada formulario lo pongo en la lista de Observadores. Puedo hacer esto porque tanto Form2 como Form3 implementan IObserver. Después de mostrar el Formulario que llamo actualizar en la lista Observer, el nuevo formulario se actualiza con los datos más recientes. Tenga en cuenta que podría haberlo lanzado a una variable de IObserver y haber actualizado solo ese formulario. Estoy tratando de ser lo más breve posible.
Luego, para el botón Agregar ''Button3'', extraigo el texto del cuadro de texto, lo almacena en mi DataList y luego actualizo todos los observadores.
Luego crea Form2. Agregue un cuadro de lista y el siguiente código.
usando el sistema;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form2 : Form,IObserver
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
}
void IObserver.Refresh(List<string> DisplayList)
{
this.listBox1.Items.Clear();
foreach (string s in DisplayList)
{
this.listBox1.Items.Add(s);
}
this.listBox1.Refresh();
}
}
}
A continuación, agregue Form3, un cuadro combinado y agregue el siguiente código.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form,IObserver
{
public Form3()
{
InitializeComponent();
}
private void Form3_Load(object sender, EventArgs e)
{
}
void IObserver.Refresh(List<string> DisplayList)
{
this.comboBox1.Items.Clear();
foreach (string s in DisplayList)
{
this.comboBox1.Items.Add(s);
}
this.comboBox1.Refresh();
}
}
}
Observará que cada formulario implementa el método de actualización de la interfaz IObserver ligeramente diferente. Uno es para un cuadro de lista y el otro para un cuadro combinado. El uso de interfaces es el elemento clave aquí.
En una aplicación del mundo real, este ejemplo sería más complejo. Por ejemplo, en lugar de pasar la lista de cadenas en la interfaz de actualización. No tendría ningún parámetro. En su lugar, el publicador (Form1 en este ejemplo) implementaría una interfaz de editorial y se registraría con los observadores a medida que se iniciasen. Cada observador podría aceptar un editor en su rutina de inicialización. Luego, cuando se actualice, sacará la lista de cadenas del publicador a través de un método expuesto a través de la interfaz.
Para aplicaciones más complejas con múltiples tipos de datos, esto le permite personalizar qué datos está sacando el editor del formulario que implementa IObserver.
Por supuesto, si SOLO desea que el observador pueda mostrar una lista de cadenas o datos específicos. Luego pásalo como parte de los parámetros. La interfaz hace explícito lo que quiere que haga cada capa. De esta forma, en 5 años a partir de ahora, podrás ver el código y el código "Oh, qué es lo que está haciendo".
Aquellos que han sugerido que los eventos en .NET realmente son una implementación del patrón Observer no están tirando de su cadena; es verdad. En cuanto a cómo funciona esto, tanto desde una perspectiva de alto nivel como en términos de más detalles específicos de la implementación, usaré una analogía.
Piensa en un editor de periódico. En términos de POO, podríamos pensar en un periódico como algo observable . pero como funciona? Claramente, los detalles de implementación del periódico en sí (es decir, en esta analogía, los periodistas, escritores, editores, etc. todos trabajando en la oficina para armar el periódico) no están expuestos públicamente. Las personas ( observadores ) no se reúnen y observan a los empleados del editor de periódicos haciendo su trabajo. No pueden simplemente hacer eso, porque no hay un protocolo (o interfaz ) para hacerlo.
Así es como observas (es decir, lees) un periódico: te suscribes. Usted obtiene su nombre en una lista de suscriptores de ese documento, y luego el editor sabe entregar uno en la puerta de su casa todas las mañanas. Si no quiere observar (leer) más, se da de baja; obtiene su nombre sacado de esa lista.
Ahora, esto podría parecer una analogía abstracta; pero en realidad es un paralelo casi perfecto de cómo funcionan los eventos .NET .
Dada alguna clase destinada a ser observable, su implementación en general no necesita ser conocida por el público. Sin embargo, expondrá un tipo particular de interfaz para el público, y esa interfaz es un evento. El código que quiere observar este evento esencialmente se registra como un suscriptor:
// please deliver this event to my doorstep
myObject.SomeEvent += myEventHandler;
Cuando este mismo código decide que ya no desea ser notificado de este evento, cancela la suscripción:
// cancel my subscription
myObject.SomeEvent -= myEventHandler;
Ahora para una discusión rápida de los delegados y cómo funciona realmente este código. Un delegado, como usted puede o no puede saber, es esencialmente una variable que almacena la dirección de un método. Normalmente, esta variable tiene un tipo, al igual que las variables de valor declaradas como int
, double
, string
, etc. todas tienen tipos. En el caso de los tipos de delegados, este tipo se define mediante la firma del método; es decir, sus parámetros y su valor de retorno. Un delegado de un tipo particular puede señalar cualquier método que realice cualquier acción, siempre que ese método tenga la firma apropiada.
Entonces, vuelva a la analogía del periódico: para suscribirse con éxito a un periódico, debe seguir un patrón particular. Específicamente, debe proporcionar una dirección válida donde desea que se entregue el periódico. No puedes simplemente decir: "Sí, envíalo a Dan". No puedes decir: "Voy a comer una hamburguesa de tocino". Debe proporcionar al editor información con la que pueda trabajar de forma significativa. En el mundo de los eventos .NET, esto se traduce en la necesidad de suministrar un controlador de eventos con la firma correcta.
En la mayoría de los casos, esta firma termina siendo un método como este:
public void SomeEventHandler(object sender, EventArgs e) {
// anything could go in here
}
Lo anterior es un método que podría almacenarse en una variable de delegado de tipo EventHandler
. Para casos más específicos, está el tipo de delegado genérico EventHandler<TEventArgs>
, que describe un método similar al anterior, pero con un parámetro e
de algún tipo derivado de EventArgs
.
Teniendo en cuenta que los delegados son realmente variables que apuntan a métodos, no es difícil establecer la conexión final entre los eventos .NET y las suscripciones a periódicos. La forma en que se implementan los eventos es a través de una lista de delegados, a / desde la cual se pueden agregar y eliminar elementos. Esto es realmente como la lista de suscriptores de un editor de periódico, cada uno de los cuales recibe una copia cuando los periódicos se distribuyen todas las mañanas.
De todos modos, espero que esto te haya ayudado a familiarizarte con el patrón Observer. Hay muchos otros tipos de implementaciones de este patrón, por supuesto, pero los eventos .NET son un paradigma con el que la mayoría de los desarrolladores de .NET están familiarizados, así que creo que es un buen punto de partida para desarrollar la comprensión.
Es como un tablero de anuncios .
Muy pocos ejemplos en tiempo real:
- Suscripción a periódicos / revistas / listas de correo o cualquier suscripción en general
- Etiquetar a un colega en MS Office Communicator
- Gorjeo