My favorite design pattern?

Romain Berthon

#JobHacker

@RomainTrm

romainberthon.blog

Before we begin


    public record SomeType(
        int? Property1,
        int Property2);
            

Context(s)

Billing

Bill generation

Payment method

string Print(...)

A poor design

    
    public record PaymentMethod(
        bool IsBankCheck,
        string? BankCheckNumber,
        bool IsCreditCard,
        DateTime? ExpirationDate,
        bool IsCash);

A poor design


  public static string Print(PaymentMethod paymentMethod)
  {
      if (paymentMethod.IsBankCheck 
          && string.IsNullOrEmpty(paymentMethod.BankCheckNumber))
          return $"Bank check n°{paymentMethod.BankCheckNumber}";

      if (paymentMethod.IsCreditCard 
          && paymentMethod.ExpirationDate.HasValue)
          return "Credit card (valid until " +
                 $"{paymentMethod.ExpirationDate.Value:MM/yy})";

      if (paymentMethod.IsCash)
          return "Cash";

      throw new InvalidOperationException();
  }

A better design

    
    public interface IPaymentMethod { }
    
    public record BankCheck(string BankCheckNumber) 
    	: IPaymentMethod;
        
    public record CreditCard(DateTime ExpirationDate) 
    	: IPaymentMethod;
        
    public record Cash : IPaymentMethod;

A better design


  public static string Print(IPaymentMethod paymentMethod)
  {
      return paymentMethod switch
      {
          BankCheck bankCheck 
              => $"Bank check n°{bankCheck.BankCheckNumber}",
              
          CreditCard creditCard 
              => "Credit card (valid until " +
                 $"{creditCard.ExpirationDate:MM/yy})",
                 
          Cash => "Cash",
          
          _ => throw new InvalidOperationException()
      };
  }

A new payment method

     
    public interface IPaymentMethod { }
public record BankCheck(string BankCheckNumber) : IPaymentMethod; public record CreditCard(DateTime ExpirationDate) : IPaymentMethod; public record Cash : IPaymentMethod;
public record PayPaulAccount(string email) : IPaymentMethod;

A new payment method


  public static string Print(IPaymentMethod paymentMethod)
  {
      return paymentMethod switch
      {
BankCheck bankCheck => $"Bank check n°{bankCheck.BankCheckNumber}", CreditCard creditCard => "Credit card (valid until " + $"{creditCard.ExpirationDate:MM/yy})", Cash => "Cash",
_ => throw new InvalidOperationException() }; }

Visitor pattern

    
    public interface IPaymentMethod
    {
        T Accept<T>(IPaymentMethodVisitor<T> visitor);
    }

    public interface IPaymentMethodVisitor<out T>
    {
        T Handle(BankCheck bankCheck);
        T Handle(CreditCard creditCard);
        T Handle(Cash _);
    }

Visitor pattern

    
    public record BankCheck(string BankCheckNumber) 
        : IPaymentMethod
    {
        public T Accept<T>(IPaymentMethodVisitor<T> visitor) 
            => visitor.Handle(this);
    }

    public record CreditCard(DateTime ExpirationDate) 
        : IPaymentMethod
    {
        public T Accept<T>(IPaymentMethodVisitor<T> visitor) 
            => visitor.Handle(this);
    }

    // ...
    // You got it ! 

Visitor pattern

    
  public static string Print(IPaymentMethod paymentMethod) 
      => paymentMethod.Accept(new PrintPaymentMethod());
 
 
  public class PrintPaymentMethod : IPaymentMethodVisitor<string>
  {
      public string Handle(BankCheck bankCheck) 
          => $"Bank check n°{bankCheck.BankCheckNumber}";
          
      public string Handle(CreditCard creditCard) 
          => "Credit card (valid until " +
             $"{creditCard.ExpirationDate:MM/yy})";
               
      public string Handle(Cash _) => "Cash";
  }

Visitor pattern: its true power

    
    public record PayPaulAccount(string Email) 
        : IPaymentMethod
    {
        public T Accept<T>(IPaymentMethodVisitor<T> visitor) 
            => visitor.Handle(this);
    }

    public interface IPaymentMethodVisitor<out T>
    {
T Handle(BankCheck bankCheck); T Handle(CreditCard creditCard); T Handle(Cash _);
}
    
    public record PayPaulAccount(string Email) 
        : IPaymentMethod
    {
        public T Accept<T>(IPaymentMethodVisitor<T> visitor) 
            => visitor.Handle(this);
    }

    public interface IPaymentMethodVisitor<out T>
    {
T Handle(BankCheck bankCheck); T Handle(CreditCard creditCard); T Handle(Cash _);
T Handle(PayPaulAccount payPaulAccount); }

Visitor pattern: its true power

    
  public class PrintPaymentMethod : IPaymentMethodVisitor<string>
  {
public string Handle(BankCheck bankCheck) => $"Bank check n°{bankCheck.BankCheckNumber}"; public string Handle(CreditCard creditCard) => "Credit card (valid until " + $"{creditCard.ExpirationDate:MM/yy})"; public string Handle(Cash _) => "Cash";
}

Visitor pattern: its true power

    
  public class PrintPaymentMethod : IPaymentMethodVisitor<string>
  {
public string Handle(BankCheck bankCheck) => $"Bank check n°{bankCheck.BankCheckNumber}"; public string Handle(CreditCard creditCard) => "Credit card (valid until " + $"{creditCard.ExpirationDate:MM/yy})"; public string Handle(Cash _) => "Cash";
public string Handle(PayPaulAccount payPaulAccount) => $"PayPaul ({payPaulAccount.Email})"; }

Visitor pattern: its true power

Thank you!

Romain Berthon

#JobHacker

@RomainTrm

romainberthon.blog

My favorite design pattern?

By Romain Berthon

My favorite design pattern?

  • 98