Per la felicità di Raffaeu ;-)
Eravamo rimasti qua:
image_thumb[2]image_thumb[1]
Il giochetto è più semplice del previsto, avete quasi sempre due attori:
  • Un entry point che tipicamente è una classe statica non generica che espone dei metodi generici;
  • Una classe che viene istanziata e ritornata dall’entry point;
Una cosa del tipo:
static class MyFluentEntryPoint
{
    public static FluentEngine Having( T value )
    {
        return new FluentEngine( value );
    }
}

e un engine così definito:

class FluentEngine
{
    readonly T value;

    public FluentEngine( T value )
    {
        this.value = value;
    }
}

Adesso per semplicità immaginiamo di voler essere in grado di fare 2 operazioni:

  • configurare l’engine appena creato;
  • mandare in esecuzione l’engine;
class FluentEngine
{
    readonly T value;

    public FluentEngine( T value )
    {
        this.value = value;
    }

    FluentEngine Configure()
    {
        return this;
    }

    FluentEngine Execute()
    {
        return this;
    }
}

Funzionare funziona ma incappate nel solito problema della “coding experience”:

image

Allora fate un secondo tentativo… fallimentare :-), almeno io ci sono incappato:

  • togliete il metodo Execute da FluentEngine;
  • create un ConfiguredFluentEngine:
class ConfiguredFluentEngine
{
    readonly T value;

    public ConfiguredFluentEngine( T value )
    {
        this.value = value;
    }

    public ConfiguredFluentEngine Execute()
    {
        return this;
    }
}

e cambiate la firma di Configure():

public ConfiguredFluentEngine Configure()
{
    return new ConfiguredFluentEngine( this.value );
}

funzionare funziona… avete risolto un problema e ve ne siete creati altri 2, figo:

  • se l’engine deve poter essere mandato in esecuzione anche senza la configurazione il metodo Excute lo dovete lasciare anche su FluentEngine pagando lo scotto di dover:
    • o duplicare l’implementazione di Execute;
    • o introdurre una terza classe per gestire l’esecuzione;
  • dovete preoccuparvi del mantenimento dello stato nel passaggio da una classe all’altra, e il nostro esempio è triviale in questo senso ma complicarsi la vita è moltissimo facilissimo;

Possiamo quindi asserire che non va bene :-) In realtà la soluzione è molto ma molto più semplice e l’abbiamo sotto gli occhi tutte le volte che usiamo una Fluent Interface fatta come si deve: interface… appunto :-)

Desiderata

Definiamo quello di cui abbiamo bisogno:

interface IConfiguredFluentEngine
{
    IFluentEngine Execute();
}

interface IFluentEngine
{
    IFluentEngine Execute();
    IConfiguredFluentEngine Configure();
}

e modifichiamo l’entry point di conseguenza:

static class MyFluentEntryPoint
{
    public static IFluentEngine Having( T value )
    {
        return new FluentEngine( value );
    }
}

A questo punto non ci resta che fare la cosa più banale di tutte: implementare tutte i contratti sulla stessa classe:

class FluentEngine : IFluentEngine, IConfiguredFluentEngine
{
    readonly T value;

    public FluentEngine( T value )
    {
        this.value = value;
    }

    public IConfiguredFluentEngine Configure()
    {
        return this;
    }

    public IFluentEngine Execute()
    {
        return this;
    }
}

garantendoci questo:

image

è ovvio che se complichiamo/estendiamo/necessitiamo di dare al developer una experience più corposa le cose si complicano e non di poco, ma il concetto resta sempre lo stesso: una singola classe che implementa contratti diversi, del resto la vostra necessità in questo caso è “semplicemente” ingannare l’intellisense :-)

Non è tutto… stay tuned.

.m