Combien de fois avez-vous eu une situation où vous ajoutez une nouvelle valeur à une énumération, puis passez des heures à essayer de trouver tous les endroits où elle est utilisée, puis ajoutez un nouveau cas afin de ne pas obtenir une ArgumentOutOfRangeException au moment de l'exécution?
Idée
Si le seul problème est l'instruction switch et le suivi des nouveaux types, alors débarrassons-nous d'eux!
L'idée est de remplacer l'utilisation du commutateur par le modèle de visiteur.
Exemple 1
- API , , , .
DocumentType.cs:
public enum DocumentType
{
Invoice,
PrepaymentAccount
}
public interface IDocumentVisitor<out T>
{
T VisitInvoice();
T VisitPrepaymentAccount();
}
public static class DocumentTypeExt
{
public static T Accept<T>(this DocumentType self, IDocumentVisitor<T> visitor)
{
switch (self)
{
case DocumentType.Invoice:
return visitor.VisitInvoice();
case DocumentType.PrepaymentAccount:
return visitor.VisitPrepaymentAccount();
default:
throw new ArgumentOutOfRangeException(nameof(self), self, null);
}
}
}
, , .Net . .
visitor DatabaseSearchVisitor.cs:
public class DatabaseSearchVisitor : IDocumentVisitor<IDocument>
{
private ApiId _id;
private Database _db;
public DatabaseSearchVisitor(ApiId id, Database db)
{
_id = id;
_db = db;
}
public IDocument VisitInvoice() => _db.SearchInvoice(_id);
public IDocument VisitPrepaymentAccount() => _db.SearchPrepaymentAccount(_id);
}
:
public void UpdateStatus(ApiDoc doc)
{
var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);
var databaseDocument = doc.Type.Accept(searchVisitor);
databaseDocument.Status = doc.Status;
_db.SaveChanges();
}
2
, :
public enum PurseEventType
{
Increase,
Decrease,
Block,
Unlock
}
public sealed class PurseEvent
{
public PurseEventType Type { get; }
public string Json { get; }
public PurseEvent(PurseEventType type, string json)
{
Type = type;
Json = json;
}
}
. visitor:
public interface IPurseEventTypeVisitor<out T>
{
T VisitIncrease();
T VisitDecrease();
T VisitBlock();
T VisitUnlock();
}
public sealed class PurseEventTypeNotificationVisitor : IPurseEventTypeVisitor<Missing>
{
private readonly INotificationManager _notificationManager;
private readonly PurseEventParser _eventParser;
private readonly PurseEvent _event;
public PurseEventTypeNotificationVisitor(PurseEvent @event, PurseEventParser eventParser, INotificationManager notificationManager)
{
_notificationManager = notificationManager;
_event = @event;
_eventParser = eventParser;
}
public Missing VisitIncrease() => Missing.Value;
public Missing VisitDecrease() => Missing.Value;
public Missing VisitBlock()
{
var blockEvent = _eventParser.ParseBlock(_event);
_notificationManager.NotifyBlockPurseEvent(blockEvent);
return Missing.Value;
}
public Missing VisitUnlock()
{
var blockEvent = _eventParser.ParseUnlock(_event);
_notificationManager.NotifyUnlockPurseEvent(blockEvent);
return Missing.Value;
}
}
. Missing System.Reflection Unit. Result, , , .
:
public void SendNotification(PurseEvent @event)
{
var notificationVisitor = new PurseEventTypeNotificationVisitor(@event, _eventParser, _notificationManager);
@event.Type.Accept(notificationVisitor);
}
, , visitor . .
:
public static T Accept<TVisitor, T>(this DocumentType self, in TVisitor visitor)
where TVisitor : IDocumentVisitor<T>
{
switch (self)
{
case DocumentType.Invoice:
return visitor.VisitInvoice();
case DocumentType.PrepaymentAccount:
return visitor.VisitPrepaymentAccount();
default:
throw new ArgumentOutOfRangeException(nameof(self), self, null);
}
}
visitor , class struct.
, :
public void UpdateStatus(ApiDoc doc)
{
var searchVisitor = new DatabaseSearchVisitor(doc.Id, _db);
var databaseDocument = doc.Type.Accept<DatabaseSearchVisitor, IDocument>(searchVisitor);
databaseDocument.Status = doc.Status;
_db.SaveChanges();
}
generic, , .
in-place
, visitor — . match.
:
public static T Match<T>(this DocumentType self, Func<T> invoiceCase, Func<T> prepaymentAccountCase)
{
var visitor = new FuncVisitor<T>(invoiceCase, prepaymentCase);
return self.Accept<FuncVisitor<T>, T>(visitor);
}
FuncVisitor:
public readonly struct FuncVisitor<T> : IDocumentVisitor<T>
{
private readonly Func<T> _invoiceCase;
private readonly Func<T> _prepaymentAccountCase;
public FuncVisitor(Func<T> invoiceCase, Func<T> prepaymentAccountCase)
{
_invoiceCase = invoiceCase;
_prepaymentAccountCase = prepaymentAccountCase;
}
public T VisitInvoice() => _invoiceCase();
public T VisitPrepaymentAccount() => _prepaymentAccountCase();
}
match:
public void UpdateStatus(ApiDoc doc)
{
var databaseDocument = doc.Type.Match(
() => _db.SearchInvoice(doc.Id),
() => _db.SearchPrepaymentAccount(doc.Id)
);
databaseDocument.Status = doc.Status;
_db.SaveChanges();
}
enum :
- .
- .
, .
case switch.
, enum.