En liten DSL för att beställa kaffe


Läste idag en trevlig artikel om att bygga egna DSL’er med C# och tänkte bolla idéerna med dig via bloggen. Observera att artikeln bara är fullständigt tillgänglig för prenumeranter (vilket jag är).

Artikeln gick ut på att visa en teknik för att skapa en intern DSL (inbyggd i språket) för att möjliggöra en ny syntax som är “enklare” att läsa för en ovan programmera, eller kanske bara för att göra det lite annorlunda.

Tekniken som används kallas för “member chaining” och går ut på att exponera egenskaper som returnerar inte den datatyp som egentligen förväntas utan istället en instans av det aktuella objektet som egenskapen finns på.

Ett exempel kan vara på sin plats i hur det skulle kunna se ut från början, i det förslaget att det handlar om att skapa ett objekt som är en beställning av en kaffe latte. Objektet skulle kunna tänkas se ut så här i en enkel form som använder automatiska egenskaper:

public class KaffeLatte

{

    public bool Dubbel { get; set; }

    public bool Vanilj { get; set; }

    public bool Fettfri { get; set; }

}

Och en beställning skulle kunna se ut som följande med hjälp av “object-initializers” i C#:

KaffeLatte latte = new KaffeLatte

{

    Dubbel = false,

    Fettfri = false,

    Vanilj = true

};

Ett alternativ som då beskrivs i artikeln är att istället skapa klassen på följande sätt:

public class KatteLatte

{

    public static KatteLatte beställ

    {

        get

        {

            return new KatteLatte();

        }

    }

 

    public KatteLatte dubbel

    {

        get

        {

            this.Dubbel = true;

            return this;

        }

    }

 

    public KatteLatte vanilj

    {

        get

        {

            this.Vanilj = true;

            return this;

        }

    }

 

    public KatteLatte fettfri

    {

        get

        {

            this.Vanilj = true;

            return this;

        }

    }

 

    public bool Vanilj { get; private set; }

    public bool Dubbel { get; private set; }

    public bool Fettfri { get; private set; }

}

Och då kan instansieringen istället skrivas på det här lite mer lättlästa sättet:

KatteLatte latte = KatteLatte.beställ

    .dubbel

    .fettfri

    .vanilj;

Skälet till att använda den statiska “factory-metoden” beställ är helt enkelt för att skapa själva instansen. Observera sedan också att flera av “get”-egenskaperna börjar med små bokstäver för att göra läsningen lite tydligare. I artikeln fortsätter sedan författaren att bygga reglerverk och nyttja både egenskaper och metoder, men det blir nog till för dig att köpa tidningen om du vill läsa vidare!

Jag tyckte att det var ett ganska snyggt sätt att försöka underlätta för läsaren, vad tycker du?

Comments (8)
  1. Björn says:

    Jag håller inte med. Att en get-property faktiskt förändrar instansvariabeln är helt oväntat beteende och ingenting en utvecklare förväntar sig. Att skilja på själva bool och objekt-propertyn med versaler / gemena känns inte heller så intuitivt.

    Bättre i såfall att sätta egenskaperna som parametrar till factory-metoden, eller göra olika factory-metoder.

    Men visst, det är "snyggt"…

  2. JohanLindfors says:

    Björn: Jag kan hålla med om att beteendet är "oväntat" för den vana utvecklaren som vet att detta är en egenskap osv… Men för någon som inte är så djup så är detta ett ganska tydligt sätt att tilldela variabler med en teknik som egentligen kanske inte är avsedd för det!

    Artikeln tar faktiskt upp det också…

  3. Henrik says:

    Kan hålla med om att koden kanske blir mer lättläst för en ovan utvecklare, men hur ofta läser en sådan kod som den behöver förstå? Som ni är inne på ovan kan jag tycka det är bättre att man skriver kod som beter sig på ett väntat sätt… Men, det beror väl i slutändan på vilket syfte (eller målgrupp) man har. Kul att du tog upp exemplet iaf!

  4. JohanLindfors says:

    Henrik: Tack för dina kommentarer. Om avsikten är att skapa en DSL för beställandet av kaffe latte internt i en applikation så kan åtminstone jag leva med att den bakomliggande koden inte beter sig som jag är van vid.

    Det som är kul med det här exemplet är också at det rör upp en del kommentarer, och det tycker jag är kul 🙂

  5. Johan L says:

    Jag kan se likheter med Linq då man skriver "ungefär" på det viset, jag syftar på Where och OrderBy tex. Har för mig att både Jimmy Nilsson (och Martin Fowler) pratar om det i form av "fluent interface"…är det samma sak?

    Hur som helst, på rätt sätt kan koden bli både snygg och lättläst…bra tycket jag!

  6. Johan L says:

    Såg nu att om man följde din länk så fanns svaret där…det är fluent interface…men då hade jag ju rätt! Kul

  7. jdanforth says:

    Jag börjar bli sjukt kär i fluent-liknande gränssnitt på klasser. Men man får passa sig lite vart man använder det. Ett lysande bra exempel (tycker jag) på vart det kan vara bra är Fluent NHibernate som kan ersätta de XML-filer som man traditionellt använder för mappning. En stor fördel är att man kan göra rename-refactoring på ett säkrare sätt 🙂

  8. Jag skulle nog välja en klassisk builder-syntax för att skapa en kaffelatte, och använda ; alltså

    CaffeLatteBuilder.CreateLatte().WithSugar().WithVanilla().Build()

    C# har med sina lambdas galet läckert stöd för snygga fluent interfaces. Jag skrev ett par blogposter om det där för ett tag sedan (länkar nedan). Med Func och Action går det att göra en massa tuffa saker. Det finns projekt ute in the wild som använder lambdas aggressivt; bl.a. Machine.Specifications (MSpec).

    http://thomaslundstrom.blogspot.com/2008/11/fluent-interfaces-by-lambdas-in-c.html

    http://thomaslundstrom.blogspot.com/2008/11/on-migrations-apis-in-net.html

Comments are closed.

Skip to main content