Descubra los secretos de la arquitectura de software con Mastering Software Architecture: 11 patrones de diseño clave explicados .
2. Patrón de diseño: Adaptador
3. Patrón de diseño: Constructor
4. Cómo utilizar el patrón de la cadena de responsabilidad
5. Patrón de diseño: Decorador
6. Patrón de diseño: método de fábrica
7. Patrón de diseño: Iterador
8. Patrón de diseño: Mediador
9. Patrón de diseño: Observador
10. Patrón de propiedad avanzada C# 8.0
11. Patrón de diseño: Singleton
Según Gang of Four, los patrones de fábrica abstractos pueden asumirse como la fábrica para crear fábricas.
El patrón de fábrica abstracto es puramente un método de fábrica de extensión; se recomienda repasar el método de fábrica antes de comprender el diseño de fábrica abstracto.
Consideremos el mismo ejemplo de cualquier banco con tipos de cuenta como cuentas de ahorro y cuentas corrientes. Ahora, implementemos el ejemplo anterior utilizando el patrón de diseño de fábrica abstracta.
En primer lugar, implemente las interfaces ISavingAccount y ICurrentAccount de la siguiente manera:
public interface ISavingAccount{ } public interface ICurrentAccount{ }
Heredar la interfaz en las clases siguientes
public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } }
Escribamos una clase abstracta con métodos abstractos para cada tipo de cuenta.
public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); }
Ahora, creemos una implementación de fábrica llamada “Bank1Factory”, que proporciona la implementación de métodos abstractos.
public class Bank1Factory : AccountTypeFactory { public override ICurrentAccount CurrentAccountFactory(string message) { return new CurrentAccount(message); } public override ISavingAccount SavingAccountFactory(string message) { return new SavingsAccount(message); } }
El patrón de diseño de fábrica abstracta se diferencia del método de fábrica en que necesita implementar un proveedor de fábrica, que devuelve fábricas según la definición.
Ahora que hemos creado todas las abstracciones y fábricas, diseñemos el proveedor de fábricas. A continuación, encontrará el fragmento de código para el proveedor de fábricas, donde un método estático creará una fábrica en función del nombre de la cuenta.
public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } }
Tomemos un ejemplo de una lista de números de cuenta donde si un nombre de cuenta consta literalmente de “ B1 ”, entonces utilizará la instancia Bank1Factory devuelta a través del proveedor de fábrica.
static void Main(string[] args) { List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" }; for (int i = 0; i < accNames.Count; i++) { AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]); if (anAbstractFactory == null) { Console.WriteLine("Invalid " + (accNames[i])); } else { ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving"); ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current"); } } Console.ReadLine(); }
Si el nombre de la cuenta no contiene el literal “B1”, el programa generará un {{accountName}} no válido.
A continuación encontrará el resultado del fragmento de código anterior.
Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987
Según Gang of Four, el patrón Adaptador convierte las interfaces de una clase en interfaces que el cliente requiere.
En otras palabras, el patrón de diseño del adaptador ayuda a que las interfaces incompatibles funcionen colectivamente.
Consideremos un ejemplo de fusión de dos organizaciones: la organización X asume el control de la organización Y, pero al combinar el código, las interfaces no son compatibles. Supongamos que la interfaz que proporciona una lista de transacciones de la organización Y no es compatible con la de X.
Entonces, el patrón de diseño del adaptador ayuda a resolver este problema cuya implementación es muy sencilla.
Creemos una lista de transacciones de la organización Y que se convierten en patrones que requiere la aplicación cliente de la organización X. La clase anterior se conoce como “Adaptee”.
public class OrgYTransactions { public List<string> GetTransactionsList() { List<string> transactions = new List<string>(); transactions.Add("Debit 1"); transactions.Add("Debit 2"); transactions.Add("Debit 3"); return transactions; } }
En segundo lugar, vamos a crear una interfaz de destino.
public interface ITransactions{ List<string> GetTransactions(); }
Ahora finalmente, implementemos la clase adaptadora de la siguiente manera.
public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } }
Una vez realizadas todas las implementaciones anteriores, comprendamos cómo usar la clase adaptadora en una aplicación de consola.
class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } }
Si observa detenidamente el uso que se muestra a continuación, hemos utilizado la interfaz de destino ITransactions y la clase de adaptador TransAdapter sin tener en cuenta el aspecto de las interfaces de la clase de terceros OrgYTransactions. Ese es el poder del patrón de diseño del adaptador: convierte las interfaces de una clase en interfaces que el cliente requiere.
Según Gang of Four, un patrón de creación “Builder” permite separar y reutilizar un método específico para construir algo.
Tomemos el ejemplo de un automóvil y el usuario desea construir dos modelos, es decir, un SUV y un sedán.
El patrón de diseño Builder resulta útil en el caso de uso anterior; veamos una demostración paso a paso.
La clase Car tiene las siguientes propiedades.
public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } }
En primer lugar, implementemos un generador de clases abstracto ampliado para diferentes modelos de automóviles, como SUV o sedanes, según el caso de uso.
public abstract class CarBuilder { protected readonly Car _car = new Car(); public abstract void SetName(); public abstract void SetSpeed(); public abstract void SetIsSUV(); public virtual Car GetCar() => _car; }
La clase abstracta consta de los siguientes métodos
Ahora, creemos una fábrica que utilice la clase CarBuilder para construir diferentes modelos de automóviles y devuelva la instancia del automóvil fabricado.
public class CarFactory { public Car Build(CarBuilder builder) { builder.SetName(); builder.SetSpeed(); builder.SetIsSUV(); return builder.GetCar(); } }
Por último, implementar diferentes modelos de coches.
public class ModelSuv : CarBuilder { public override void SetIsSUV() { _car.IsSUV = true; } public override void SetName() { _car.Name = "Maruti SUV"; } public override void SetSpeed() { _car.TopSpeed = 1000; } }
public class ModelSedan : CarBuilder { public override void SetIsSUV() { _car.IsSUV = false; } public override void SetName() { _car.Name = "Maruti Sedan"; } public override void SetSpeed() { _car.TopSpeed = 2000; } }
Por último, utilicemos patrones de diseño para construir diferentes modelos de automóviles con la ayuda del método factory.Build(<model>).
static void Main(string[] args) { var sedan = new ModelSedan(); var suv = new ModelSuv(); var factory = new CarFactory(); var builders = new List<CarBuilder> { suv, sedan }; foreach (var b in builders) { var c = factory.Build(b); Console.WriteLine($"The Car details" + $"\n--------------------------------------" + $"\nName: {c.Name}" + $"\nIs SUV: {c.IsSUV}" + $"\nTop Speed: {c.TopSpeed} mph\n"); } }
El uso anterior muestra con qué elegancia podemos construir diferentes modelos de automóviles utilizando el patrón de diseño del constructor.
El patrón de código es muy fácil de mantener y extensible. Si en el futuro necesitamos desarrollar un nuevo modelo, solo es necesario que el nuevo modelo extienda la clase CarBuilder y listo.
Según Gang of Four, se define una cadena de responsabilidades para procesar una solicitud. En otras palabras, pasar la solicitud de un objeto a otro hasta que un objeto acepte su responsabilidad.
Consideremos un ejemplo de sistema de reclamaciones en cualquier empresa corporativa. Aquí se muestra la lista de rangos de precios que pueden aprobarse y quién puede hacerlo.
100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager
Si el monto está fuera del rango de 10.000, se requiere una aprobación excepcional del gerente superior.
El caso de uso anterior se puede implementar fácilmente utilizando el patrón de diseño Cadena de responsabilidad. Por lo tanto, la clase de reclamo tiene las siguientes propiedades.
public class Claim{ public int Id{get;set;} public double amount{get;set;} }
En primer lugar, definamos qué funciones puede realizar un aprobador de reclamaciones y establezcamos una jerarquía para los empleados en diferentes niveles. Implementemos una clase abstracta como se muestra a continuación
public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); }
Según el caso de uso, vamos a controlar la clase solicitante de reclamos "junior/senior". Tenga en cuenta que esta clase/designación de empleados no puede aprobar ningún reclamo.
public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } }
De manera similar, definamos la implementación para los roles de Gerente y Gerente Senior.
public class Manager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount >= 100 && claim.amount <= 1000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Manager"); } else if (claimApprover != null) { claimApprover.ApproveRequest(claim); } } }
Tenga en cuenta que, en función del rango de monto, si está dentro del rango del Gerente, el reclamo puede ser aprobado por el Gerente; de lo contrario, la solicitud se pasará al Gerente Senior.
public class SeniorManager : ClaimApprover { public override void ApproveRequest(Claim claim) { if (claim.amount > 1000 && claim.amount <= 10000) { System.Console.WriteLine($"Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } else { System.Console.WriteLine($"Exceptional approval for Claim reference {claim.Id} with amount {claim.amount} is approved by Senior Manager"); } } }
De igual forma, si el rango de monto está dentro del rango del Gerente Senior, el reclamo puede ser aprobado por el Gerente; de lo contrario, al estar último en la jerarquía, se hace una aprobación excepcional por un monto fuera del rango.
ClaimApprover junior = new Manager(); ClaimApprover sukhpinder = new Manager(); ClaimApprover singh = new SeniorManager(); junior.SetHierarchy(sukhpinder); sukhpinder.SetHierarchy(singh); Claim c1 = new Claim() { amount = 999, Id = 1001 }; Claim c2 = new Claim() { amount = 10001, Id = 1002 }; junior.ApproveRequest(c1); sukhpinder.ApproveRequest(c2);
Claim reference 1001 with amount 999 is approved by Manager Exceptional approval for Claim reference 1002 with amount 10001 is approved by Senior Manager
Para la salida de la línea 1, el monto estaba dentro del rango, por lo que el gerente lo aprobó.
Para la salida de la línea 2, aunque el gerente superior lo aprobó, el monto estaba fuera del rango.
Según Gang of Four, el patrón agrega responsabilidades adicionales a un objeto de clase de forma dinámica.
Consideremos el ejemplo de comprar un automóvil que vale diez lakhs; la empresa ofrece las siguientes características adicionales.
Con algunas características adicionales, el precio total del automóvil aumenta. Implementemos el caso de uso anterior utilizando el patrón Decorator.
Implementemos el caso de uso definido anteriormente. En primer lugar, definamos una clase abstracta Car y sus métodos básicos.
public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); }
Consideremos un automóvil pequeño que se extiende por encima de la clase abstracta Automóvil.
public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; }
Ahora, implemente la clase CarDecorator utilizando el componente Car.
public class CarDecorator : Car { protected Car _car; public CarDecorator(Car car) { _car = car; } public override int CarPrice() => _car.CarPrice(); public override string GetName() =>_car.GetName(); }
Ahora, crearemos una clase separada para cada característica adicional disponible para Car heredando la clase CarDecorator.
Según el caso de uso, las características adicionales son un techo corredizo y un sistema de música avanzado.
Anular los métodos como
Añade el coste adicional de un “sistema de música avanzado” al precio total del coche.
Actualice el nombre del automóvil con un nombre de función adicional.
public class AdvanceMusic : CarDecorator { public AdvanceMusic(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 3000; public override string GetName()=> "Alto Lxi with advance music system"; }
Anular los métodos como
public class Sunroof : CarDecorator { public Sunroof(Car car) : base(car) { } public override int CarPrice() => _car.CarPrice() + 2000; public override string GetName() => "Alto Lxi with Sunroof"; }
Crea una instancia de SmallCar y muestra el nombre y el precio del automóvil.
Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice());
Ahora, agreguemos funciones adicionales como se muestra a continuación.
var car1 = new Sunroof(car); var car2 = new AdvanceMusic(car);
static void Main(string[] args) { Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice()); var car1 = new Sunroof(car); Console.WriteLine($"Price of car {car1.GetName()} : " + car1.CarPrice()); var car2 = new AdvanceMusic(car); Console.WriteLine($"Price of car {car2.GetName()} : " + car2.CarPrice()); }
¡Felicitaciones! Has implementado exitosamente el caso de uso usando el patrón decorador.
Según la Banda de los Cuatro, el método de fábrica permite a la subclase determinar qué objeto de clase debe crearse.
Consideremos un ejemplo de cualquier banco con tipos de cuenta como cuentas de ahorro y cuentas corrientes. Ahora, implementemos el ejemplo anterior utilizando el patrón de diseño de fábrica
En primer lugar, cree una clase abstracta de tipo cuenta.
public abstract class AccoutType { public string Balance { get; set; } }
Implemente clases de cuenta corriente y de ahorro heredando la clase abstracta AccountType como se muestra a continuación.
public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } }
Por último, implementemos la interfaz de fábrica, que proporcionará un contrato que ayudará a crear un objeto de clase. Esta interfaz también se conoce como Creador.
public interface IAccountFactory { AccoutType GetAccoutType(string accountName); }
Por último, escriba una implementación del método de interfaz del creador como se muestra a continuación. La clase que implementa el creador se conoce como Concrete Creator.
public class AccountFactory : IAccountFactory { public AccoutType GetAccoutType(string accountName) { if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase)) { return new SavingsAccount(); } else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase)) { return new CurrentAccount(); } else { throw new ArgumentException("Invalid account name"); } } }
Eso es todo. Has implementado con éxito el método de fábrica utilizando el ejemplo del banco.
Una subclase decidirá qué objeto de clase “AccountType” se creará en función del nombre de la cuenta.
class Program { static void Main(string[] args) { IAccountFactory accountFactory = new AccountFactory(); var savingAccount = accountFactory.GetAccoutType("SAVINGS"); Console.WriteLine("Saving account balance: " + savingAccount.Balance); var currentAccount = accountFactory.GetAccoutType("CURRENT"); Console.WriteLine("Current account balance: " + currentAccount.Balance); } }
Por ejemplo, si el nombre de la cuenta es “AHORROS”, se creará y devolverá el objeto de clase “CuentaDeAhorro”.
De manera similar, si el nombre de la cuenta es “CURRENT”, entonces se creará una instancia del objeto de clase “CurrentAccount” y se devolverá.
Saving account balance: 10000 Rs Current account balance: 20000 Rs
Según Gang of Four, el patrón iterador proporciona un proceso para obtener el objeto agregador sin conocer su implementación.
Tomemos como ejemplo una lista de colección de automóviles y string[] una matriz de motocicletas; necesitamos diseñar un objeto agregador para que uno pueda iterar sobre la colección sin saber si es una lista o una matriz.
El patrón de diseño de iterador ayuda a resolver este problema en el que un iterador estándar recorrerá diferentes tipos de colecciones.
Considerando el caso de uso anterior, definamos una interfaz de iterador personalizada que actúe como una capa abstracta sobre el iterador de lista y matriz.
public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); }
Ahora, escriba iteradores de automóvil y motocicleta que implementen la interfaz anterior según el caso de uso.
public class CarIterator : IVehicleIterator { private List<string> _cars; private int _current; public CarIterator(List<string> cars) { _cars = cars; _current = 0; } public string Current() { return _cars.ElementAt(_current); } public void First() { _current = 0; } public bool IsDone() { return _current >= _cars.Count; } public string Next() { return _cars.ElementAt(_current++); } }
El iterador del automóvil se implementa sobre la colección List<string> y proporciona una implementación de métodos de interfaz.
El iterador de motocicleta se implementa sobre la colección string[] y proporciona una implementación de métodos de interfaz.
public class MotercycleIterator : IVehicleIterator { private string[] _motercylces; private int _current; public MotercycleIterator(string[] motercylces) { _motercylces = motercylces; _current = 0; } public string Current() { return _motercylces[_current]; } public void First() { _current = 0; } public bool IsDone() { return _current >= _motercylces.Length; } public string Next() { return _motercylces[_current++]; } }
Una vez definidos todos los iteradores anteriores, defina una interfaz de objeto agregador estándar que cree iteradores.
public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); }
Por último, escriba las clases que implementan la interfaz del agregador anterior. Según el caso de uso, tanto la clase Car como la clase Motorcycle implementarán la interfaz del agregador.
El método de la interfaz del agregador devuelve el iterador relevante como se muestra a continuación.
public class Car : IVehicleAggregate { private List<string> _cars; public Car() { _cars = new List<string> { "Car 1", "Car 2", "Car 3" }; } public IVehicleIterator CreateIterator() { return new CarIterator(_cars); } }
El método de la interfaz del agregador devuelve el iterador relevante como se muestra a continuación.
public class Motercycle : IVehicleAggregate { private string[] _motercycles; public Motercycle() { _motercycles = new[] { "Bike 1", "Bike 2", "Bike 3" }; } public IVehicleIterator CreateIterator() { return new MotercycleIterator(_motercycles); } }
Los métodos PrintVehicles comprueban si !iterator.isDone y luego muestran el elemento de la colección. Sin importar con qué colección estemos trabajando, implementa métodos como First, IsDone y Next.
static void Main(string[] args) { IVehicleAggregate car = new Vehicles.Car(); IVehicleAggregate motercycle = new Vehicles.Motercycle(); IVehicleIterator carIterator = car.CreateIterator(); IVehicleIterator motercycleIterator = motercycle.CreateIterator(); PrintVehicles(carIterator); PrintVehicles(motercycleIterator); } static void PrintVehicles(IVehicleIterator iterator) { iterator.First(); while (!iterator.IsDone()) { Console.WriteLine(iterator.Next()); } }
No conocemos el tipo de colección subyacente, pero se itera de todas formas mediante el patrón de diseño Iterator. Si continúa y lo ejecuta, se muestra el siguiente resultado.
Según Gang of Four, el patrón Mediador encapsula la interacción de los objetos entre sí.
El patrón de diseño mediador nos ayuda a diseñar aplicaciones acopladas de forma flexible al encapsular las interacciones de los objetos.
Consideremos un ejemplo de una sala de chat donde los participantes se registran y cómo comunicarse de manera eficiente.
Es necesario implementar la siguiente conversación de sala de chat utilizando el patrón de diseño de mediador.
David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?'
El primer paso es crear una lista de nombres de usuario que se utilizarán en una sala de chat. A continuación se muestra una enumeración pública para ello.
public enum Username{ Ashley, David, Jennifer, Scott }
Ahora, lo primero y más importante es implementar una capa abstracta de la sala de chat.
public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); }
Y una clase que define métodos abstractos. Los métodos validan si el usuario existe en el diccionario. Por ejemplo, el método de registro valida si el usuario ya existe o no. Si no existe, entonces solo registra al usuario en la sala de chat.
public class Chatroom : AChatroom { private Dictionary<string, User> _users = new Dictionary<string, User>(); public override void Post(string fromUser, string toUser, string msg) { User participant = _users[toUser]; if (participant != null) { participant.DM(fromUser, msg); } } public override void Register(User user) { if (!_users.ContainsValue(user)) { _users[user.Name] = user; } user.Chatroom = this; } }
Por último, implementemos las acciones que el usuario puede realizar, como publicar un mensaje a un usuario en la sala de chat o recibir un DM de otro usuario.
public class User { private Chatroom _chatroom; private string _name; public User(string name) => this._name = name; public string Name => _name; public Chatroom Chatroom { set { _chatroom = value; } get => _chatroom; } public void Post(string to, string message) => _chatroom.Post(_name, to, message); public virtual void DM(string from, string message) => Console.WriteLine("{0} to {1}: '{2}'", from, Name, message); }
static void Main(string[] args) { Chatroom chatroom = new Chatroom(); User Jennifer = new UserPersona(Username.Jennifer.ToString()); User Ashley = new UserPersona(Username.Ashley.ToString()); User David = new UserPersona(Username.David.ToString()); User Scott = new UserPersona(Username.Scott.ToString()); chatroom.Register(Jennifer); chatroom.Register(Ashley); chatroom.Register(David); chatroom.Register(Scott); David.Post(Username.Scott.ToString(), "Hey"); Scott.Post(Username.David.ToString(), "I am good how about you."); Jennifer.Post(Username.Ashley.ToString(), "Hey ashley... david is back in the group"); Jennifer.Post(Username.David.ToString(), "Where have you been?"); Ashley.Post(Username.David.ToString(), "How come you aren't active here anymore?"); Console.ReadKey(); }
La ejecución del programa describe únicamente el método Post de la clase de usuario.
Salida: El historial de la sala de chat de la ejecución del programa anterior.
David to Scott: 'Hey' Scott to David: 'I am good how about you.' Jennifer to Ashley: 'Hey ashley... david is back in the group' Jennifer to David: 'Where have you been?' Ashley to David: 'How come you aren't active here anymore?'
Según Gang of Four, el patrón observador define la dependencia entre dos o más objetos. Por lo tanto, cuando el estado de un objeto cambia, se notifica a todos sus dependientes.
En otras palabras, un cambio en un objeto inicia la notificación en otro objeto.
Tomemos como ejemplo a una celebridad influyente de Instagram que tiene una cantidad “ x ” de seguidores. Por lo tanto, en el momento en que la celebridad agrega una publicación, todos los seguidores reciben una notificación.
Implementemos el caso de uso mencionado anteriormente utilizando el patrón de diseño Observer.
Según el caso de uso, el primero implementa una interfaz que contiene qué acciones puede realizar una celebridad. Se la conoce como “ Sujeto ”.
public interface ICelebrityInstagram{ string FullName { get; } string Post { get; set; } void Notify(string post); void AddFollower(IFollower fan); void RemoveFollower(IFollower fan); }
Notificar: Para notificar a todos los seguidores.
AddFollower: agrega un nuevo seguidor a la lista de celebridades.
RemoveFollower: elimina un seguidor de la lista de celebridades.
Ahora, implemente la interfaz del observador “IFollower”, que contiene la función miembro “Actualizar” para notificación.
public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); }
Finalmente, es hora de implementar la “Implementación concreta” tanto para el “ Sujeto ” como para el “ Observador ”.
Proporciona una implementación de la función miembro Actualizar, que envía el nombre de la celebridad y la publicación a la consola.
public class Follower : IFollower { public void Update(ICelebrityInstagram celebrityInstagram) { Console.WriteLine($"Follower notified. Post of {celebrityInstagram.FullName}: " + $"{celebrityInstagram.Post}"); } }
public class Sukhpinder : ICelebrityInstagram { private readonly List<IFollower> _posts = new List<IFollower>(); private string _post; public string FullName => "Sukhpinder Singh"; public string Post { get { return _post; } set { Notify(value); } } public void AddFollower(IFollower follower) { _posts.Add(follower); } public void Notify(string post) { _post = post; foreach (var item in _posts) { item.Update(this); } } public void RemoveFollower(IFollower follower) { _posts.Remove(follower); } }
El siguiente caso de uso muestra que siempre que se ejecuta la siguiente declaraciónsukhpinder.Post = “Me encantan los patrones de diseño.”; el método de actualización se activa para cada seguidor, es decir, cada objeto seguidor recibe una notificación de una nueva publicación de “Sukhpinder”.
static void Main(string[] args) { var sukhpinder = new Sukhpinder(); var firstFan = new Follower(); var secondFan = new Follower(); sukhpinder.AddFollower(firstFan); sukhpinder.AddFollower(secondFan); sukhpinder.Post = "I love design patterns."; Console.Read(); }
El artículo describe cómo la coincidencia de patrones proporciona una forma eficaz de utilizar y procesar esos datos en formas que no eran parte del sistema principal.
Tomemos como ejemplo una Calculadora de peajes y veamos cómo la coincidencia de patrones ayuda a escribir un algoritmo para ello.
public class Car { public int PassengerCount { get; set; } } public class DeliveryTruck { public int Weight { get; set; } } public class Taxi { public int Fare { get; set; } } public class Bus { public int Capacity { get; set; } public int RidersCount { get; set; } }
Ejemplo 1: Calcular la tarifa del peaje según las siguientes condiciones:
- Si el vehículo es un automóvil => 100 rupias
- Si el vehículo es un camión de reparto => 200 rupias
- Si el vehículo es Bus => 150 Rs
- Si el vehículo es un Taxi => 120 Rs
Si el tipo de vehículo coincide con el del vehículo 100, se devuelve el valor y así sucesivamente. Tenga en cuenta que null y {} son casos predeterminados para el tipo de objeto.
Además, se puede utilizar “_” para programar el escenario predeterminado. Consulte la nueva sintaxis de conmutación.
Es una forma mucho más limpia y eficiente de codificar y también recomienda el uso de nombres de variables de una sola letra dentro de la sintaxis del conmutador.
public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 };
Ejemplos de prueba desde el punto de vista de una aplicación de consola. El código siguiente ilustra cómo llamar a la función de coincidencia de patrones anterior desde el método principal.
var car = new Car(); var taxi = new Taxi(); var bus = new Bus(); var truck = new DeliveryTruck(); Console.WriteLine($"The toll for a car is {TollFare(car)}"); Console.WriteLine($"The toll for a taxi is {TollFare(taxi)}"); Console.WriteLine($"The toll for a bus is {TollFare(bus)}"); Console.WriteLine($"The toll for a truck is {TollFare(truck)}");
The toll for a car is 100 The toll for a taxi is 120 The toll for a bus is 150 The toll for a truck is 200
Ejemplo 2: Agregar precios de ocupación según el tipo de vehículo
- Los automóviles y taxis sin pasajeros pagan 10 rupias adicionales.
- Los automóviles y taxis con dos pasajeros obtienen un descuento de 10 rupias.
- Los automóviles y taxis con tres o más pasajeros obtienen un descuento de 20 rupias.
- Los autobuses con menos del 50% de pasajeros pagan 30 rupias extra.
- Los autobuses que tienen más del 90% de pasajeros obtienen un descuento de 40 rupias.
- A los camiones que pesen más de 5000 libras se les cobrará un cargo adicional de 100 rupias.
- Camiones ligeros de menos de 3000 libras, reciben un descuento de 20 rupias.
Consulte la sintaxis de coincidencia de patrones con clases de propiedad únicas y múltiples. Enlace
Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20,
Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20,
Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150,
DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200,
El siguiente ejemplo destaca las ventajas de la coincidencia de patrones: las ramas de patrones se compilan en orden. El compilador también advierte sobre el código inalcanzable.
public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch { Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20, Taxi { Fare: 0 } => 100 + 10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20, Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30, Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40, Bus b => 150, DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200, null => 0, { } => 0, };
Ejemplos de prueba desde el punto de vista de una aplicación de consola. El código siguiente ilustra cómo llamar a la función de coincidencia de patrones anterior desde el método principal.
var car1 = new Car{ PassengerCount=2}; var taxi1 = new Taxi { Fare = 0 }; var bus1 = new Bus { Capacity = 100, RidersCount = 30 }; var truck1 = new DeliveryTruck { Weight = 30000 }; Console.WriteLine($"The toll for a car is {OccupancyTypeTollFare(car1)}"); Console.WriteLine($"The toll for a taxi is {OccupancyTypeTollFare(taxi1)}"); Console.WriteLine($"The toll for a bus is {OccupancyTypeTollFare(bus1)}"); Console.WriteLine($"The toll for a truck is {OccupancyTypeTollFare(truck1)}");
The toll for a car is 90 The toll for a taxi is 110 The toll for a bus is 180 The toll for a truck is 300
“La coincidencia de patrones hace que el código sea más legible y ofrece una alternativa a las técnicas orientadas a objetos cuando no puedes agregar código a tus clases”.
Gang of Four: el patrón de diseño Singleton garantiza que una clase particular tenga solo una instancia/objeto y un punto de acceso global.
Las clases singleton se utilizan para eliminar la creación de instancias de más de un objeto de una clase particular.
public class SingletonExample { private string Name { get; set; } = "Hello from singleton"; private static SingletonExample _instance; public static SingletonExample Instance { get { if (_instance == null) { _instance = new SingletonExample(); } return _instance; } } public SingletonExample() { } public string GetName() => Name; }
Llamemos a la clase singleton dos veces y asignemos la instancia devuelta a dos variables diferentes. Por último, verifiquemos si ambos objetos son iguales utilizando la función Object.Equals.
static void Main(string[] args) { var response = SingletonExample.Instance; Console.WriteLine(response); var response1 = SingletonExample.Instance; Console.WriteLine(response1); Console.WriteLine(Object.Equals(response1, response)); }
La salida de la consola devuelve verdadero; felicitaciones. Ha implementado correctamente el patrón Singleton.
La clase anterior se conoce como clase singleton, pero actualmente no es segura para subprocesos. En un entorno de subprocesos múltiples, dos subprocesos pueden ejecutar la instrucción if (_instance == null) al mismo tiempo y terminaremos teniendo varias instancias de una clase singleton.
Una forma de lograr un hilo más seguro es utilizar un mecanismo de bloqueo, y la otra forma es crear una instancia de solo lectura para un enfoque más limpio y eficiente.
public class ThreadSafeSingleton { private static readonly ThreadSafeSingleton _instance = new ThreadSafeSingleton(); public static ThreadSafeSingleton Instance { get { return _instance; } } public ThreadSafeSingleton() { } }
https://github.com/ssukhpinder/DesignPatterns
Los patrocinios me ayudan a seguir manteniendo y construyendo nuevos proyectos como estos.
🙏 Si usas Pay, Noticed o cualquiera de mis otros proyectos, una pequeña contribución significaría MUCHO. Por sí solo, el código abierto no paga las cuentas. Con suerte, con tu ayuda, continuar con mi trabajo puede ser sostenible y no tendré que buscar un trabajo real 😛.
¡Gracias por ser parte de la comunidad C#!