Desbloquea os segredos da arquitectura de software con Mastering Software Architecture: 11 Key Design Patterns Explained .
2. Patrón de deseño — Adaptador
3. Patrón de deseño — Construtor
4. Como utilizar o Patrón da cadea de responsabilidade
5. Patrón de deseño — Decorador
6. Patrón de deseño — Método de fábrica
7. Patrón de deseño — Iterador
8. Patrón de deseño — Mediador
9. Patrón de deseño — Observador
10. Patrón de propiedade avanzada C# 8.0
11. Patrón de deseño - Singleton
Segundo Gang of Four, os patróns de fábrica abstractos pódense asumir como a fábrica para crear fábricas.
O patrón de fábrica abstracto é puramente un método de fábrica de extensión; recoméndase pasar polo método de fábrica antes de comprender o deseño abstracto de fábrica.
Consideremos o mesmo exemplo de calquera banco con tipos de contas como aforros e contas correntes. Agora, imos implementar o exemplo anterior usando o patrón de deseño de fábrica abstracto.
En primeiro lugar, implemente as interfaces ISavingAccount e ICurrentAccount do seguinte xeito:
public interface ISavingAccount{ } public interface ICurrentAccount{ }
Herda a interface nas clases seguintes
public class CurrentAccount : ICurrentAccount { public CurrentAccount(string message) { Console.WriteLine(message); } } public class SavingsAccount : ISavingAccount { public SavingsAccount( string message) { Console.WriteLine(message); } }
Escribamos unha clase abstracta con métodos abstractos para cada tipo de conta.
public abstract class AccountTypeFactory { public abstract ISavingAccount SavingAccountFactory(string message); public abstract ICurrentAccount CurrentAccountFactory(string message); }
Agora, imos crear unha implementación de fábrica chamada "Bank1Factory", que proporciona a 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); } }
O patrón de deseño de fábrica abstracto difire do método de fábrica que necesita para implementar un provedor de fábrica, que devolve as fábricas segundo a definición.
Agora que temos todas as abstraccións e fábricas creadas. Deseñamos o provedor da fábrica. Busca a continuación o fragmento de código para o provedor de fábrica, onde un método estático creará unha fábrica baseada no nome da conta.
public class AccountFactoryProvider { public static AccountTypeFactory GetAccountTypeFactory(string accountName) { if (accountName.Contains("B1")) { return new Bank1Factory(); } else return null; } }
Poñamos un exemplo dunha lista de números de conta onde se un nome de conta consta literalmente de “ B1 ”, entón utilizará a instancia de Bank1Factory devolta a través do provedor 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(); }
Se o nome da conta non contén o literal "B1", entón o programa mostrará un {{accountName}} non válido
Busca a continuación a saída do fragmento de código anterior.
Hello saving B1-456 Hello Current B1-456 Hello saving B1-987 Hello Current B1-987
Segundo Gang of Four, o patrón do adaptador converte as interfaces dunha clase en interfaces que o cliente require.
Noutras palabras, o patrón de deseño do adaptador axuda a que as interfaces incompatibles funcionen colectivamente.
Consideremos un exemplo de fusión de dúas organizacións; A organización X está a facerse cargo de Y, pero ao combinar código, as interfaces non son compatibles. Supoña que a interface que proporciona unha lista de transaccións da organización Y non é compatible con X.
Así, o patrón de deseño do adaptador axuda a resolver este problema cuxa implementación é moi sinxela.
Imos crear unha lista de transaccións da organización Y que se converten en patróns que require a aplicación cliente da organización X. A clase anterior coñécese como "Adaptado".
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, imos crear unha interface de destino.
public interface ITransactions{ List<string> GetTransactions(); }
Agora, finalmente, imos implementar a clase do adaptador do seguinte xeito.
public class TransAdapter : OrgYTransactions, ITransactions { public List<string> GetTransactions() { return GetTransactionsList(); } }
Despois de facer todas as implementacións anteriores, imos entender como usar a clase de adaptador nunha aplicación de consola.
class Program { static void Main(string[] args) { ITransactions adapter = new TransAdapter(); foreach (var item in adapter.GetTransactions()) { Console.WriteLine(item); } } }
Se observas atentamente o seguinte uso, utilizamos a interface de destino ITransactions e a clase de adaptador TransAdapter sen ter en conta o aspecto das interfaces de clase OrgYTransactions de terceiros. Ese é o poder do patrón de deseño do adaptador que converte as interfaces dunha clase en interfaces que o cliente require.
Segundo Gang of Four, un patrón de creación "Builder" permite separar e reutilizar un método específico para construír algo.
Poñamos un exemplo dun Coche, e o usuario quería construír dous modelos, é dicir, SUV e Sedan.
O patrón de deseño do constructor é útil no caso de uso anterior e vexamos unha demostración paso a paso.
A clase Car ten as seguintes propiedades.
public class Car{ public string Name { get; set; } public double TopSpeed { get; set; } public bool IsSUV { get; set; } }
En primeiro lugar, imos implementar un constructor de clases abstracto estendido por diferentes modelos de coches como SUV ou sedans segundo o 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; }
A clase abstracta consta dos seguintes métodos
Agora, imos crear unha fábrica que utilice a clase CarBuilder para construír diferentes modelos de coches e que devolva a instancia do coche feito.
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; } }
Finalmente, usemos patróns de deseño para construír diferentes modelos de coche coa axuda dun 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"); } }
O uso anterior mostra a gracia que podemos construír diferentes modelos de coches usando o patrón de deseño do constructor.
O patrón de código é altamente mantenible e extensible. Se, no futuro, necesitamos desenvolver un novo modelo, só o novo modelo necesita estender a clase CarBuilder, e xa está.
Segundo Gang of Four, define unha cadea de responsabilidades para procesar unha solicitude. Noutras palabras, pasar a solicitude dun obxecto a outro ata que un obxecto acepte a súa responsabilidade.
Consideremos un exemplo de sistema de reclamacións en calquera empresa corporativa. Aquí está a lista do rango de prezos que se pode aprobar e por quen.
100–1000 Rs => Junior/Senior Engineers => Approved by Manager 1001–10000 Rs => Managers => Approved by Senior Manager
Se o importe está fóra do rango de 10000, é necesaria a aprobación excepcional do director superior.
O caso de uso anterior pódese implementar facilmente mediante o patrón de deseño da cadea de responsabilidade. Polo tanto, a clase de reivindicación ten as seguintes propiedades.
public class Claim{ public int Id{get;set;} public double amount{get;set;} }
En primeiro lugar, imos definir que funcións pode realizar un aprobador de reclamacións e establecer unha xerarquía para os empregados de diferentes niveis. Implementa unha clase abstracta como se mostra a continuación
public abstract class ClaimApprover { protected ClaimApprover claimApprover; public void SetHierarchy(ClaimApprover claimApprover) { this.claimApprover = claimApprover; } public abstract void ApproveRequest(Claim claim); }
Segundo o caso de uso, imos dirixir o solicitante de reclamación de clase "junior/senior". Teña en conta que esta clase/designación de empregados non pode aprobar ningunha reclamación.
public class Junior : ClaimApprover { public override void ApproveRequest(Claim claim) { System.Console.WriteLine("Cannot approve"); } }
Do mesmo xeito, imos definir a implementación para os roles de xestor e xestor superior.
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); } } }
Teña en conta que en función do intervalo de cantidades, se está dentro do intervalo do xestor, a reclamación pode ser aprobada polo xestor; en caso contrario, a solicitude será trasladada ao Director Superior.
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"); } } }
Do mesmo xeito, se o intervalo de cantidades está dentro do rango do xestor superior, a reclamación pode ser aprobada polo xerente; en caso contrario, sendo o último da xerarquía, faise unha aprobación excepcional por un importe fóra do 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 a saída da liña 1, o importe estaba dentro do intervalo, polo que o xestor aprobouno.
Para a saída da liña 2, aínda que o xestor superior o aprobou, a cantidade estaba fóra do rango.
Segundo Gang of Four, o patrón engade responsabilidades adicionais a un obxecto de clase de forma dinámica.
Consideremos o exemplo de mercar un coche que vale dez lakhs; a empresa ofrece as seguintes funcións adicionais.
Con algunhas características adicionais, o prezo total do coche aumenta. Imos implementar o caso de uso anterior usando o Patrón Decorador.
Imos implementar o caso de uso definido anteriormente. En primeiro lugar, define unha clase abstracta Car e os seus métodos base.
public abstract class Car{ public abstract int CarPrice(); public abstract string GetName(); }
Considere un coche pequeno que se estende por riba da clase abstracta Car.
public class SmallCar : Car{ public override int CarPrice() => 10000; public override string GetName() => "Alto Lxi"; }
Agora, implementa a clase CarDecorator usando o compoñente 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(); }
Agora, imos crear unha clase separada para cada función adicional dispoñible para Car que herda a clase CarDecorator.
Segundo o caso de uso, as características adicionais son un teito solar e un sistema de música avanzado.
Anular os métodos como
Engade o custo adicional dun "sistema de música avanzado" ao prezo total do coche.
Actualiza o nome do coche cun nome 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 os 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 unha instancia de SmallCar e mostra o nome e o prezo do coche.
Car car = new SmallCar(); Console.WriteLine($"Price of car {car.GetName()} : " + car.CarPrice());
Agora, imos engadir funcións adicionais como se mostra 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()); }
Parabéns..!! Implementou correctamente o caso de uso usando o patrón de decorador.
Segundo a Gang of Four, o método de fábrica permite que a subclase determine que obxecto de clase debe crearse.
Consideremos un exemplo de calquera banco con tipos de conta como Aforro e Conta corrente. Agora, imos implementar o exemplo anterior usando o patrón de deseño de fábrica
En primeiro lugar, cree unha clase abstracta tipo conta.
public abstract class AccoutType { public string Balance { get; set; } }
Implementa clases de conta actuais e de aforro herdando a clase abstracta AccountType como se mostra a continuación.
public class SavingsAccount : AccoutType { public SavingsAccount() { Balance = "10000 Rs"; } } public class CurrentAccount : AccoutType { public CurrentAccount() { Balance = "20000 Rs"; } }
Finalmente, imos implementar a interface de fábrica, que proporcionará un contrato que axuda a crear un obxecto de clase. Esta interface tamén se coñece como o Creador.
public interface IAccountFactory { AccoutType GetAccoutType(string accountName); }
Por último, escribe unha implementación do método de interface de creador como se mostra a continuación. A clase que implementa o creador coñécese 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"); } } }
Iso é. Implementou correctamente o método de fábrica usando o exemplo de Banco.
Unha subclase decidirá que obxecto de clase "AccountType" se creará en función do nome da conta.
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 exemplo, se o nome da conta é "SAVINGS", crearase e devolverase o obxecto da clase "SavingAccount".
Do mesmo xeito, se o nome da conta é "CURRENT", entón o obxecto da clase "CurrentAccount" será instanciado e devolto.
Saving account balance: 10000 Rs Current account balance: 20000 Rs
Segundo Gang of Four, o patrón iterador proporciona un proceso para obter o obxecto agregador sen coñecer a súa implementación.
Poñamos un exemplo dunha lista de colección de coches e string[] unha matriz de motocicletas, necesitamos deseñar un obxecto agregador para que se poida iterar sobre a colección sen saber se é unha lista ou unha matriz.
O patrón de deseño do iterador axuda a resolver este problema no que un iterador estándar atravesará diferentes tipos de colección.
Tendo en conta o caso de uso anterior, imos definir unha interface de iterador personalizada que actúe como unha capa abstracta sobre o iterador de lista e matriz.
public interface IVehicleIterator{ void First(); bool IsDone(); string Next(); string Current(); }
Agora, escribe iteradores de coches e motocicletas que implementen a interface anterior segundo o 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++); } }
O iterador de coches está implementado sobre a colección List<string> e proporciona unha implementación de métodos de interface.
O iterador de motocicletas está implementado sobre a colección string[] e ofrece unha implementación de métodos de interface.
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++]; } }
Despois de definir todos os iteradores anteriores, defina unha interface de obxecto de agregador estándar que cree iteradores.
public interface IVehicleAggregate{ IVehicleIterator CreateIterator(); }
Finalmente, anote as clases que implementan a interface do agregador anterior. Segundo o caso de uso, tanto as clases de coche como de motocicleta implementarán a interface de agregación.
O método da interface do agregador devolve o iterador relevante como se mostra 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); } }
O método da interface do agregador devolve o iterador relevante como se mostra 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); } }
Os métodos PrintVehicles verifican se !iterator.isDone saen entón o elemento de colección. Non importa a colección coa que esteamos a tratar, implementa métodos como First, IsDone e 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()); } }
Non coñecemos o tipo de colección subxacente, pero aínda se repite a través do patrón de deseño do iterador. Se continúa e executa, mostra a seguinte saída.
Segundo Gang of Four, o patrón Mediator encapsula a interacción do obxecto entre si.
O patrón de deseño do mediador axúdanos a deseñar aplicacións pouco acopladas ao encapsular as interaccións de obxectos.
Consideremos un exemplo de sala de chat onde se rexistran os participantes e como comunicarse de forma eficiente.
É necesario implementar a seguinte conversa na sala de chat usando o patrón de deseño do 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?'
O paso principal é crear unha lista de nomes de usuario que se utilizarán dentro dunha sala de chat. A continuación móstrase unha enumeración pública para iso.
public enum Username{ Ashley, David, Jennifer, Scott }
Agora, en primeiro lugar, implementa unha capa abstracta da sala de chat.
public abstract class AChatroom { public abstract void Register(User user); public abstract void Post(string fromUser, string toUser, string msg); }
E unha clase que define métodos abstractos. Os métodos validan se o usuario existe no dicionario. Por exemplo, o método de rexistro valida se o usuario xa existe ou non. Se non existe, só rexistra o usuario na 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; } }
Finalmente, imos implementar as accións que pode realizar o usuario, como publicar unha mensaxe a un usuario na sala de chat ou recibir un DM doutro 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(); }
A execución do programa describe só o método Post da clase de usuario.
Saída: o historial da sala de chat da execución do 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?'
Segundo Gang of Four, o patrón do observador define a dependencia b/w de dous ou máis obxectos. Entón, cando o estado dun obxecto cambia, todos os seus dependentes son notificados.
Noutras palabras, un cambio nun obxecto inicia a notificación noutro obxecto.
Poñamos un exemplo dun influencer famoso de Instagram que ten " x " número de seguidores. Entón, no momento en que a celebridade engade unha publicación, todos os seguidores son notificados.
Imos implementar o caso de uso mencionado anteriormente usando o patrón de deseño de Observer.
Segundo o caso de uso, o primeiro implementa unha interface que contén que accións pode realizar unha celebridade. Coñécese como " Suxeito ".
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 os seguidores.
Engadir seguidor: engade un novo seguidor á lista de famosos.
Eliminar seguidor: elimina un seguidor da lista de famosos.
Agora, implementa a interface de observador "IFollower", que contén a función de membro "Actualizar" para a notificación.
public interface IFollower{ void Update(ICelebrityInstagram celebrityInstagram); }
Finalmente, é hora de implementar a "Implementación concreta" tanto para " Suxeito " como para " Observador ".
Proporciona unha implementación da función de membro Actualizar, que mostra o nome e a publicación da celebridade na 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); } }
O seguinte caso de uso mostra que sempre que se executa a seguinte instruciónsukhpinder.Post = "Encántanme os patróns de deseño."; O método de actualización desenvólvese para cada seguidor, é dicir, cada obxecto seguidor recibe unha notificación dunha nova 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(); }
O artigo describe como a correspondencia de patróns proporciona un xeito eficaz de utilizar e procesar eses datos en formularios que non formaban parte do sistema principal.
Poñamos un exemplo da calculadora de peaxes e vexamos como a coincidencia de patróns axuda a escribir un algoritmo para iso.
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; } }
Exemplo 1: calcula a tarifa de peaxe segundo as seguintes condicións:
- Se o vehículo é Coche => 100 Rs
- Se o vehículo é DeliveryTruck => 200 Rs
- Se o vehículo é Autobús => 150 Rs
- Se o vehículo é un Taxi => 120 Rs
Se o tipo de vehículo coincide co Car 100 é devolto e así por diante. Teña en conta que nulo e {} son casos predeterminados para o tipo de obxecto.
Ademais, "_" pódese usar para programar o escenario predeterminado. Consulte a nova sintaxe do interruptor.
É unha forma moito máis limpa e eficiente de codificar e tamén recomenda o uso de nomes de variable dunha soa letra dentro da sintaxe do switch.
public static int TollFare(Object vehicleType) => vehicleType switch { Car c => 100, DeliveryTruck d => 200, Bus b => 150, Taxi t => 120, null => 0, { } => 0 };
Exemplos de proba desde o punto de vista da aplicación de consola. O seguinte código ilustra como chamar á función de coincidencia de patróns anterior desde o 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
Exemplo 2: engade o prezo de ocupación en función do tipo de vehículo
- Os coches e taxis con pasaxeiros "NON" pagan 10 Rs.
- Os coches e taxis con dous pasaxeiros teñen un desconto de 10 Rs.
- Os coches e taxis con tres ou máis pasaxeiros reciben un desconto de 20 Rs.
- Os autobuses que son menos do 50% dos pasaxeiros pagan 30 Rs.
- Os autobuses que teñen máis do 90% dos pasaxeiros teñen un desconto de 40 Rs.
- Os camións de máis de 5000 libras cobran 100 Rs.
- Camións lixeiros de menos de 3000 libras, con desconto de 20 Rs.
Consulte a sintaxe de coincidencia de patróns con clases de propiedades únicas e múltiples. Ligazón
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,
O seguinte exemplo destaca as vantaxes da coincidencia de patróns: as ramas de patróns compílanse en orde. O compilador tamén advirte sobre o 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, };
Exemplos de proba desde o punto de vista da aplicación de consola. O seguinte código ilustra como chamar a función de coincidencia de patróns anterior desde o 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
"A coincidencia de patróns fai que o código sexa máis lexible e ofrece unha alternativa ás técnicas orientadas a obxectos cando non podes engadir código ás túas clases".
Gang of Four: o patrón de deseño Singleton garante que unha clase particular teña só unha instancia/obxecto e un punto de acceso global.
As clases singleton úsanse para eliminar a instanciación de máis dun obxecto dunha 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; }
Chamemos dúas veces á clase singleton e asignemos a instancia devolta a dúas variables diferentes. Finalmente, verifique se ambos obxectos son iguais mediante a función Obxecto.Igual.
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)); }
A saída da consola devolve verdadeiro; parabéns. Implementou correctamente o patrón Singleton.
A clase anterior coñécese como clase singleton, pero actualmente non é segura para fíos. Nun entorno multifío, dous fíos poden chegar á instrución if (_instance == null) ao mesmo tempo, e acabaremos tendo varias instancias dunha clase singleton.
Unha forma de crear un fío máis seguro é usar un mecanismo de bloqueo e a outra forma é facer unha instancia de só lectura para un enfoque máis limpo e 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
Os patrocinios axúdanme a seguir mantendo e construíndo novos proxectos coma estes.
🙏 Se usas Pay, Noticed ou calquera dos meus proxectos, unha pequena contribución significaría MOITO. Por si só, o código aberto non paga as contas. Oxalá, coa túa axuda, continuar co meu traballo poida ser sostible, e non terei que ir buscar un traballo de verdade 😛.
Grazas por formar parte da comunidade C#!