//Rozdział 20.
//Pusty obiekt

public interface ILog
{
    void Info(string msg);
    void Warn(string msg);
}

public class BankAccount
{
    private ILog log;
    private int balance;

    public BankAccount(ILog log)
    {
        this.log = log;
    }
    // tutaj inne składowe
}

public void Deposit(int amount)
{
    balance += amount;
    log.Info($"Wpłacono {amount} PLN, saldo wynosi teraz {balance}");
}

class ConsoleLog : ILog
{
    public void Info(string msg)
    {
        WriteLine(msg);
    }
    public void Warn(string msg)
    {
        WriteLine("OSTRZEŻENIE:" + msg);
    }
}

// Podejście natrętne

public abstract class ILog
{
    void Info(string msg) {}
    void Warn(string msg) {}
}

// Pusty obiekt

public BankAccount(ILog log)
{
    this.log = log;
}

public sealed class NullLog : ILog
{
    public void Info(string msg) { }
    public void Warn(string msg) { }
}

//Ulepszenia projektu

public void Deposit(int amount)
{
    balance += amount;
    log?.Info($"Wpłacono {amount} PLN. Saldo wynosi teraz {balance}");
    //^^ sprawdź tutaj, czy nie ma wartości null
}

// Wirtualny pośrednik Pustego obiektu

class OptionalLog: ILog
{
    private ILog impl;
    private static ILog NoLogging = null;

    public OptionalLog(ILog impl)
    {
        this.impl = impl;
    }
    public void Info(string msg)
    {
        impl?.Info(msg);
    }
    //i podobne sprawdzenia dla innych składowych
}

public BankAccount(ILog log)
{
    this.log = new OptionalLog(log);
}

var account = new BankAccount(null);
account.Withdraw(int.MaxValue); //nie będzie wyjątku
//Dynamiczny Pusty obiekt

public class Null<T> : DynamicObject where T:class
{
    public override bool TryInvokeMember(InvokeMemberBinder
    binder, object[] args, out object result)
    {
        var name = binder.Name;
        result = Activator.CreateInstance(binder.ReturnType);
        return true;
    }
}

public static T Instance
{
    get
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Wymagany typ, który jest interfejsem");
        return new Null<T>().ActLike<T>();
    }
}
var log = Null<ILog>.Instance;
var ba = new BankAccount(log);
ba.Deposit(100);
ba.Withdraw(200);
