Skip to content

Instantly share code, notes, and snippets.

@kallebysantos
Created February 15, 2024 09:22
Show Gist options
  • Save kallebysantos/01ca26b6e6ea12ee496a26531011dc56 to your computer and use it in GitHub Desktop.
Save kallebysantos/01ca26b6e6ea12ee496a26531011dc56 to your computer and use it in GitHub Desktop.
State pattern implementation in C#

pattern-matching.cs

public static class PatternMatching
{
    public static U Match<U, T1, T2>(Func<T1, U> match1, Func<T2, U> match2, T1? value1, T2? value2)
    {
        if (value1 is not null)
            return match1(value1);

        if (value2 is not null)
            return match2(value2);

        throw new InvalidOperationException("Match should have least 1 valid value");
    }
    public static void Match<T1, T2>(Action<T1> match1, Action<T2> match2, T1? value1, T2? value2)
    {
        if (value1 is not null)
            match1(value1);

        if (value2 is not null)
            match2(value2);
    }
}

order.cs

namespace StatePattern.Order;

public interface IOrderType;
public record struct Sell() : IOrderType;
public record struct Buy() : IOrderType;

public abstract record IOrderState;
public record Open() : IOrderState;
public record Closed() : IOrderState;
public record Unchecked() : IOrderState;

public readonly record struct IOrderTransition<T> where T : IOrderType
{
    public Order<T, Open>? OrderOpen { get; init; } = null;
    public Order<T, Closed>? OrderClosed { get; init; } = null;

    public IOrderTransition(Order<T, Open> order)
    {
        OrderOpen = order;
    }

    public IOrderTransition(Order<T, Closed> order)
    {
        OrderClosed = order;
    }

    public U Match<U>(Func<Order<T, Open>, U> open, Func<Order<T, Closed>, U> closed)
        => PatternMatching.Match(open, closed, OrderOpen, OrderClosed);

    public void Match(Action<Order<T, Open>> open, Action<Order<T, Closed>> closed)
        => PatternMatching.Match(open, closed, OrderOpen, OrderClosed);
}


public record Order<TType, TState>(
    Guid Id,
    double Price,
    uint PendingShares
)
    where TType : IOrderType
    where TState : IOrderState;

public record Order
{
    internal static Order<T, Unchecked> SubtractPendingShares<T>(Order<T, Open> order, uint quantity)
    where T : IOrderType
    {
        ArgumentOutOfRangeException.ThrowIfGreaterThan(quantity, order.PendingShares);

        return new(
            Id: order.Id,
            Price: order.Price,
            PendingShares: order.PendingShares - quantity
        );
    }
}

public static class UncheckedOrder
{
    public static IOrderTransition<T> Check<T>(this Order<T, Unchecked> order)
    where T : IOrderType
        => order.PendingShares == 0
            ? new(new Order<T, Closed>(
                Id: order.Id,
                Price: order.Price,
                PendingShares: 0
            ))
            : new(new Order<T, Open>(
                Id: order.Id,
                Price: order.Price,
                PendingShares: order.PendingShares
            ));
}

public static class BuyOrder
{
    public static Order<Buy, Unchecked> BuyAssets(this Order<Buy, Open> order, uint quantity)
    {
        Console.WriteLine("Some buy order logic");
        // Fazer incremento do Investidor e caso sucesso prosseguir

        return Order.SubtractPendingShares(order, quantity);
    }
}

public static class SellOrder
{
    public static Order<Sell, Unchecked> SellAssets(this Order<Sell, Open> order, uint quantity)
    {
        Console.WriteLine("Some sell order logic");
        // Fazer decremento do Investidor e caso sucesso prosseguir

        return Order.SubtractPendingShares(order, quantity);
    }
}

program.cs

using StatePattern.Order;

var order = new Order<Buy, Open>(Id: Guid.NewGuid(), Price: 10.5d, PendingShares: 15);
var order2 = new Order<Sell, Open>(Id: Guid.NewGuid(), Price: 10.5d, PendingShares: 10);


order.BuyAssets(10)
    .Check()
    .Match(
        open: order => Console.WriteLine($"Kept Open BUY: {order}"),
        closed: order => Console.WriteLine($"Closed BUY: {order}")
    );

order2.SellAssets(10)
    .Check()
    .Match(
        open: order => Console.WriteLine($"Kept Open SELL: {order}"),
        closed: order => Console.WriteLine($"Closed SELL: {order}")
    );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment