paint-brush
11 patrones de diseño clave: una guía esencialpor@ssukhpinder
1,221 lecturas
1,221 lecturas

11 patrones de diseño clave: una guía esencial

por Sukhpinder Singh33m2024/09/08
Read on Terminal Reader

Demasiado Largo; Para Leer

Descubra los secretos de la arquitectura de software con *Mastering Software Architecture: 11 Key Design Patterns Explained*. El libro está escrito en el lenguaje de programación C# 8.0. Hay 11 patrones de diseño que se pueden utilizar para crear una fábrica para crear fábricas. El patrón de fábrica abstracto es un método de fábrica de extensión; se recomienda seguir el método de fábrica.
featured image - 11 patrones de diseño clave: una guía esencial
Sukhpinder Singh HackerNoon profile picture
0-item
1-item
2-item
3-item
4-item

Descubra los secretos de la arquitectura de software con Mastering Software Architecture: 11 patrones de diseño clave explicados .

Tabla de contenido

  1. Patrón de diseño: Abstract Factory
  • Objetivos de aprendizaje
  • Empezando
  • ¿Cómo utilizar un proveedor de fábrica abstracta?
  • Producción

2. Patrón de diseño: Adaptador

  • Caso de uso
  • Objetivos de aprendizaje
  • Empezando

3. Patrón de diseño: Constructor

  • Caso de uso
  • Objetivos de aprendizaje
  • Empezando
  • Cómo utilizar el patrón constructor del método Main()
  • Producción

4. Cómo utilizar el patrón de la cadena de responsabilidad

  • Caso de uso
  • Empezando
  • ¿Cómo utilizar el patrón Cadena de Responsabilidad?
  • Producción

5. Patrón de diseño: Decorador

  • Caso de uso
  • Objetivos de aprendizaje
  • Empezando
  • Patrón Decorator en acción
  • Código completo
  • Producción

6. Patrón de diseño: método de fábrica

  • Objetivos de aprendizaje
  • Empezando
  • ¿Cómo utilizar el método de fábrica?
  • Producción

7. Patrón de diseño: Iterador

  • Caso de uso
  • Empezando
  • Patrón iterador en acción
  • Producción

8. Patrón de diseño: Mediador

  • Caso de uso
  • Objetivos de aprendizaje
  • Empezando
  • Cómo utilizar el patrón mediador del método principal

9. Patrón de diseño: Observador

  • Caso de uso
  • Objetivos de aprendizaje
  • Empezando
  • ¿Cómo utilizar un patrón observador?
  • Producción

10. Patrón de propiedad avanzada C# 8.0

  • Vamos a empezar
  • Programa de comparación de patrones con nueva sintaxis de conmutación
  • Programa de prueba
  • Salida de consola

11. Patrón de diseño: Singleton

  • Objetivos de aprendizaje
  • Empezando
  • Producción
  • Seguridad de los hilos

Patrón de diseño: Abstract Factory

Según Gang of Four, los patrones de fábrica abstractos pueden asumirse como la fábrica para crear fábricas.


Objetivos de aprendizaje

  • ¿Qué es el patrón de diseño de fábrica abstracta?
  • ¿Cómo escribir código utilizando el patrón de diseño de fábrica abstracta?
  • ¿Cómo crear un proveedor de fábrica?
  • Cómo crear una aplicación cliente (desde el método principal) que utiliza un proveedor de fábrica

Prerrequisitos

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.

  • Conocimientos básicos de conceptos OOPS.
  • Cualquier conocimiento de lenguaje de programación.

Empezando

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; } }

¿Cómo utilizar un proveedor de fábrica abstracta?

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.

Producción

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

Patrón de diseño: adaptador

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.

Caso de uso

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.

Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño de adaptador?

Empezando

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.

Patrón de diseño: Constructor

Según Gang of Four, un patrón de creación “Builder” permite separar y reutilizar un método específico para construir algo.


Caso de uso

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; } }

Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño de constructor?

Empezando

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

  • Métodos abstractos para cada propiedad de la clase Car.
  • Un método virtual que genera la instancia de la clase Car.


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.

ModeloSuv.cs

 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; } }

ModeloSedan.cs

 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; } }

Cómo crear patrones de constructor de usuarios desde el método Main()

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.

Producción

Cómo utilizar el patrón de la cadena de responsabilidad

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.


Caso de uso

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;} }

Empezando

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);

¿Cómo utilizar el patrón de cadena de responsabilidad?

  1. Definir aprobador de reclamaciones: junior, aunque no puede aprobar ninguna reclamación.
  2. Defina el aprobador del reclamo: gerente “sukhpinder”.
  3. Defina el aprobador del reclamo: gerente senior “Singh”.
  4. Establecer una relación de jerarquía para los junior, es decir, el aprobador de reclamaciones es el gerente.
  5. Establecer una relación de jerarquía para el gerente, es decir, el aprobador de reclamaciones es el gerente senior.
  6. Crea dos gamas diferentes de reclamaciones.
  7. Junior envía la solicitud de reclamo al gerente.
  8. El gerente envía la solicitud de reclamo al gerente superior.

Producción

 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.

Patrón de diseño — Decorador

Según Gang of Four, el patrón agrega responsabilidades adicionales a un objeto de clase de forma dinámica.


Caso de uso

Consideremos el ejemplo de comprar un automóvil que vale diez lakhs; la empresa ofrece las siguientes características adicionales.

  • Techo corredizo
  • Sistema de música avanzado
  • y muchos más


Con algunas características adicionales, el precio total del automóvil aumenta. Implementemos el caso de uso anterior utilizando el patrón Decorator.

Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño decorador?

Empezando

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.

AdvanceMusic.cs

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"; }

Techo corredizo. cs

Anular los métodos como

  • Añade el coste adicional de un “techo solar” al precio total del coche.
  • Actualice el nombre del automóvil con un nombre de función adicional.
 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"; }

Patrón Decorator en acción

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);

Código completo

 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()); }

Producción

¡Felicitaciones! Has implementado exitosamente el caso de uso usando el patrón decorador.

Patrón de diseño: método de fábrica

Según la Banda de los Cuatro, el método de fábrica permite a la subclase determinar qué objeto de clase debe crearse.


Objetivos de aprendizaje

  • ¿Qué es el patrón de diseño del método de fábrica?
  • ¿Cómo escribir código utilizando el método de fábrica?

Empezando

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.

¿Cómo utilizar el método de fábrica?

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á.

Producción

 Saving account balance: 10000 Rs Current account balance: 20000 Rs

Patrón de diseño: Iterador

Según Gang of Four, el patrón iterador proporciona un proceso para obtener el objeto agregador sin conocer su implementación.


Caso de uso

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.

Empezando

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.

Iterador de coche.cs

 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.

Iterador de motocicletas.cs

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.

Coche.cs

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); } }

Motocicleta. cs

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); } }

Patrón iterador en acción

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()); } }

Producción

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.

Patrón de diseño: Mediador

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.

Caso de uso

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?'

Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño mediador?

Empezando

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); }

Cómo utilizar el patrón Mediador desde el método principal

 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(); }


  1. Se crea el objeto de clase de sala de chat.
  2. Se crean cuatro usuarios diferentes con nombres únicos.
  3. Registra a cada uno de ellos en la sala de chat.
  4. Los usuarios ahora pueden comenzar a enviarse mensajes entre sí.

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?'

Patrón de diseño: Observador

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.

Caso de uso

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.

Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño de observador?

Empezando

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); }

El sujeto contiene las siguientes funciones miembro.

  • 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 ”.

ConcreteObserver llamado “Follower.cs”

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}"); } }

Tema concreto llamado “Sukhpinder. cs”

 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); } }

¿Cómo utilizar un patrón de observador?

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(); }

Producción

Patrón de propiedad avanzada C# 8.0

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.


Vamos a empezar

Tomemos como ejemplo una Calculadora de peajes y veamos cómo la coincidencia de patrones ayuda a escribir un algoritmo para ello.

Clase de entidad utilizada en todo el artículo

 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

Programa de comparación de patrones con nueva sintaxis Switch

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 };

Prueba el programa anterior

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)}");

Salida de consola

 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.

Interruptor de coincidencia de patrones

Consulte la sintaxis de coincidencia de patrones con clases de propiedad únicas y múltiples. Enlace

Coincidencia de patrones: entidad de automóvil

 Car { PassengerCount: 0 } => 100 + 10, Car { PassengerCount: 1 } => 100, Car { PassengerCount: 2 } => 100 - 10, Car c => 100 - 20,

Coincidencia de patrones: entidad de taxi

 Taxi {Fare:0 }=>100+10, Taxi { Fare: 1 } => 100, Taxi { Fare: 2 } => 100 - 10, Taxi t => 100 - 20,

Coincidencia de patrones: entidad de bus

 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,

Coincidencia de patrones: entidad de camión de reparto

 DeliveryTruck t when (t.Weight > 5000) => 200 + 100, DeliveryTruck t when (t.Weight < 3000) => 200 - 20, DeliveryTruck t => 200,

Combinando todas las entidades

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, };

Prueba el programa anterior

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)}");

Salida de consola

 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”.

Patrón de diseño: Singleton

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.


Objetivos de aprendizaje

  • ¿Cómo codificar utilizando un patrón de diseño singleton?

Empezando

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; }

Descomponer

  1. Iteración 1 _instance==null significa que solo se crearán instancias.
  2. Iteración 2, ya que ahora _intance !=null, por lo que se devolverán las instancias creadas previamente.

Prueba usando una aplicación de consola

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)); }
  • Si devuelve verdadero, significa que se produce una única instancia cada vez.
  • Si devuelve falso, significa que la clase no está siguiendo el patrón singleton.

Producción

La salida de la consola devuelve verdadero; felicitaciones. Ha implementado correctamente el patrón Singleton.



Seguridad de los hilos

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() { } }

Ejemplo de Github

https://github.com/ssukhpinder/DesignPatterns

¡Gracias por leer!

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 😛.

Programación en C#🚀

¡Gracias por ser parte de la comunidad C#!

Compramecafe