Wann würden Sie das Builder-Muster verwenden? [geschlossen]

531

Was sind einige gemeinsame , reale Welt Beispiele der Builder - Muster zu verwenden? Was kauft es dir? Warum nicht einfach ein Factory-Muster verwenden?

Charles Graham
quelle
Die stackoverflow.com/questions/35238292/… erwähnte einige APIs, die Builder-Muster verwenden
Alireza Fattahi
Die Antworten von Aaron und Tetha sind wirklich informativ. Hier ist der vollständige Artikel zu diesen Antworten.
Diablo

Antworten:

262

Der Hauptunterschied zwischen einem Builder und einer Factory IMHO besteht darin, dass ein Builder nützlich ist, wenn Sie viele Dinge tun müssen, um ein Objekt zu erstellen. Stellen Sie sich zum Beispiel ein DOM vor. Sie müssen viele Knoten und Attribute erstellen, um Ihr endgültiges Objekt zu erhalten. Eine Factory wird verwendet, wenn die Factory problemlos das gesamte Objekt innerhalb eines Methodenaufrufs erstellen kann.

Ein Beispiel für die Verwendung eines Builders ist das Erstellen eines XML-Dokuments. Ich habe dieses Modell beim Erstellen von HTML-Fragmenten verwendet. Ich habe beispielsweise einen Builder zum Erstellen eines bestimmten Tabellentyps und die folgenden Methoden (Parameter werden nicht angezeigt). ::

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

Dieser Builder würde dann den HTML-Code für mich ausspucken. Dies ist viel einfacher zu lesen als eine große prozedurale Methode durchzugehen.

Schauen Sie sich das Builder-Muster auf Wikipedia an .

JoshBerke
quelle
1021

Im Folgenden sind einige Gründe aufgeführt, die für die Verwendung des Musters und des Beispielcodes in Java sprechen. Es handelt sich jedoch um eine Implementierung des Builder-Musters, das von der Viererbande in Entwurfsmustern behandelt wird . Die Gründe, warum Sie es in Java verwenden würden, gelten auch für andere Programmiersprachen.

Wie Joshua Bloch in Effective Java, 2. Ausgabe, feststellt :

Das Builder-Muster ist eine gute Wahl beim Entwerfen von Klassen, deren Konstruktoren oder statische Fabriken mehr als eine Handvoll Parameter haben würden.

Wir sind alle irgendwann auf eine Klasse mit einer Liste von Konstruktoren gestoßen, in der jeder Zusatz einen neuen Optionsparameter hinzufügt:

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

Dies wird als Teleskopkonstruktormuster bezeichnet. Das Problem bei diesem Muster besteht darin, dass es schwierig wird, sich die erforderliche Reihenfolge der Parameter sowie den gewünschten Konstruktor in einer bestimmten Situation zu merken , sobald die Konstruktoren 4 oder 5 Parameter lang sind .

Eine Alternative zum Telescoping Constructor Pattern ist das JavaBean Pattern, bei dem Sie einen Konstruktor mit den obligatorischen Parametern aufrufen und anschließend alle optionalen Setter aufrufen:

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

Das Problem hierbei ist, dass sich das Objekt, da es über mehrere Aufrufe erstellt wurde, möglicherweise während seiner Erstellung in einem inkonsistenten Zustand befindet. Dies erfordert auch viel zusätzlichen Aufwand, um die Gewindesicherheit zu gewährleisten.

Die bessere Alternative ist die Verwendung des Builder-Musters.

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

    public Pizza build() {
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

Beachten Sie, dass Pizza unveränderlich ist und dass sich alle Parameterwerte an einem einzigen Ort befinden . Da die Setter-Methoden des Builders das Builder-Objekt zurückgeben, können sie verkettet werden .

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

Dies führt zu Code, der leicht zu schreiben und sehr leicht zu lesen und zu verstehen ist. In diesem Beispiel kann die Erstellungsmethode geändert werden , um Parameter zu überprüfen, nachdem sie vom Builder in das Pizza-Objekt kopiert wurden, und eine IllegalStateException auszulösen, wenn ein ungültiger Parameterwert angegeben wurde. Dieses Muster ist flexibel und es ist einfach, in Zukunft weitere Parameter hinzuzufügen. Es ist wirklich nur nützlich, wenn Sie mehr als 4 oder 5 Parameter für einen Konstruktor haben. Das heißt, es könnte sich in erster Linie lohnen, wenn Sie den Verdacht haben, dass Sie in Zukunft weitere Parameter hinzufügen werden.

Ich habe mich stark mit diesem Thema aus dem Buch Effective Java, 2nd Edition von Joshua Bloch befasst. Um mehr über dieses Muster und andere effektive Java-Praktiken zu erfahren, empfehle ich es dringend.

Aaron
quelle
24
Anders als der ursprüngliche GOF-Builder, oder? Weil es keine Director Class gibt. Scheint mir fast wie ein anderes Muster, aber ich stimme zu, dass es sehr nützlich ist.
Lino Rosa
194
Wäre es für dieses Beispiel nicht schöner, die booleschen Parameter zu entfernen und sagen zu könnennew Pizza.Builder(12).cheese().pepperoni().bacon().build();
Fabian Steeg
46
Dies ähnelt eher einer fließenden Schnittstelle als einem Builder-Muster .
Robert Harvey
21
@Fabian Steeg, ich denke, die Leute reagieren überreagiert auf die besser aussehenden Booleschen Setter. Denken Sie daran, dass diese Setter keine Laufzeitänderungen zulassen: Pizza.Builder(12).cheese().pepperoni().bacon().build();Sie müssten Ihren Code neu kompilieren oder haben unnötige Logik, wenn Sie nur einige Peperoni benötigen Pizza. Zumindest sollten Sie auch parametrisierte Versionen bereitstellen, wie sie ursprünglich von @Kamikaze Mercenary vorgeschlagen wurden. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();. Andererseits testen wir niemals Unit-Tests, oder?
Egallardo
23
@ JasonC Richtig, und was nützt eine unveränderliche Pizza überhaupt?
Maarten Bodewes
325

Betrachten Sie ein Restaurant. Die Erstellung des "heutigen Essens" ist ein Fabrikmuster, da Sie der Küche sagen, "Holen Sie mir das heutige Essen" und die Küche (Fabrik) anhand versteckter Kriterien entscheidet, welches Objekt generiert werden soll.

Der Builder wird angezeigt, wenn Sie eine benutzerdefinierte Pizza bestellen. In diesem Fall sagt der Kellner dem Koch (Baumeister): "Ich brauche eine Pizza; fügen Sie Käse, Zwiebeln und Speck hinzu!" Auf diese Weise macht der Builder die Attribute verfügbar, die das generierte Objekt haben sollte, verbirgt jedoch, wie sie festgelegt werden.

Tetha
quelle
Nitin erweiterte die Küchenanalogie um eine weitere Antwort auf diese Frage .
Pops
19

Die .NET StringBuilder-Klasse ist ein hervorragendes Beispiel für ein Builder-Muster. Es wird meistens verwendet, um eine Zeichenfolge in einer Reihe von Schritten zu erstellen. Das Endergebnis, das Sie bei ToString () erhalten, ist immer eine Zeichenfolge, aber die Erstellung dieser Zeichenfolge hängt davon ab, welche Funktionen in der StringBuilder-Klasse verwendet wurden. Zusammenfassend besteht die Grundidee darin, komplexe Objekte zu erstellen und die Implementierungsdetails der Erstellung zu verbergen.


quelle
9
Ich denke nicht, dass das das Builder-Muster ist. Der StringBuilder ist nur eine weitere Implementierung einer Zeichenarray-Klasse (dh einer Zeichenfolge), berücksichtigt jedoch die Leistung und die Speicherverwaltung, da Zeichenfolgen unveränderlich sind.
Charles Graham
23
Es ist absolut das Builder-Muster, ebenso wie die StringBuilder-Klasse in Java. Beachten Sie, wie die append () -Methode dieser beiden Klassen StringBuilder selbst zurückgibt, sodass man sie verketten kann, b.append(...).append(...)bevor man sie schließlich aufruft toString(). Zitat: infoq.com/articles/internal-dsls-java
pohl
3
@pohl Ya Ich denke nicht, dass das wirklich ein Builder-Muster ist, ich würde sagen, dass das eher eine fließende Oberfläche ist.
Didier A.
"Beachten Sie, wie die append () -Methode dieser beiden Klassen StringBuilder selbst zurückgibt", das ist nicht das Builder-Muster, sondern nur eine fließende Schnittstelle. Es ist nur so oft, dass ein Builder AUCH eine fließende Oberfläche verwendet. Ein Builder muss keine fließende Oberfläche haben.
Bytedev
Beachten Sie jedoch, dass StringBuilder von Natur aus nicht synchronisiert ist, im Gegensatz zum synchronisierten StringBuffer.
Alan Deep
11

Für ein Multithread-Problem mussten wir für jeden Thread ein komplexes Objekt erstellen. Das Objekt stellte die zu verarbeitenden Daten dar und kann sich je nach Benutzereingabe ändern.

Könnten wir stattdessen eine Fabrik benutzen? Ja

Warum haben wir nicht? Builder macht wohl mehr Sinn.

Fabriken werden zum Erstellen verschiedener Objekttypen verwendet, die denselben Basistyp haben (dieselbe Schnittstelle oder Basisklasse implementieren).

Builder erstellen immer wieder denselben Objekttyp, aber die Konstruktion ist dynamisch, sodass sie zur Laufzeit geändert werden kann.

Cameron MacFarland
quelle
9

Sie verwenden es, wenn Sie viele Optionen haben, mit denen Sie sich befassen müssen. Denken Sie an Dinge wie jmock:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

Es fühlt sich viel natürlicher an und ist ... möglich.

Es gibt auch XML-Gebäude, String-Gebäude und viele andere Dinge. Stellen Sie java.util.Mapsich vor, Sie hätten als Baumeister gearbeitet. Sie könnten solche Dinge tun:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);
Dustin
quelle
3
Ich habe vergessen, die "if" -Karte zu lesen, die ein Builder-Muster implementiert hat, und war überrascht, das Konstrukt dort zu sehen. :)
Sethu
3
:) Das tut mir leid. In vielen Sprachen ist es üblich, sich selbst zurückzugeben, anstatt nichtig zu sein. Sie können es in Java tun, aber es ist nicht sehr häufig.
Dustin
7
Das Kartenbeispiel ist einfach ein Beispiel für die Methodenverkettung.
Nogridbag
@nogridbag Es wäre tatsächlich näher an der Methodenkaskadierung. Obwohl die Verkettung so verwendet wird, dass eine Kaskadierung simuliert wird, handelt es sich offensichtlich um eine Verkettung, aber semantisch verhält es sich wie eine Kaskadierung.
Didier A.
9

Beim Durchlaufen des Microsoft MVC-Frameworks habe ich mir Gedanken über das Builder-Muster gemacht. Ich bin auf das Muster in der ControllerBuilder-Klasse gestoßen. Diese Klasse gibt die Controller-Factory-Klasse zurück, die dann zum Erstellen einer konkreten Steuerung verwendet wird.

Der Vorteil, den ich bei der Verwendung des Builder-Musters sehe, besteht darin, dass Sie eine eigene Factory erstellen und in das Framework einbinden können.

@Tetha, es kann ein Restaurant (Framework) geben, das von einem Italiener geführt wird und Pizza serviert. Um Pizza zuzubereiten, verwendet der Italiener (Object Builder) Owen (Factory) mit einer Pizzabasis (Basisklasse).

Jetzt übernimmt der Inder das Restaurant vom Italiener. Indische Restaurant (Framework) Server Dosa anstelle von Pizza. Um dosa vorzubereiten, verwendet der Inder (Objektbauer) die Bratpfanne (Fabrik) mit einer Maida (Basisklasse).

Wenn Sie sich das Szenario ansehen, ist das Essen anders, die Art und Weise, wie das Essen zubereitet wird, ist anders, aber im selben Restaurant (unter demselben Rahmen). Das Restaurant sollte so gebaut sein, dass es chinesische, mexikanische oder jede andere Küche unterstützt. Mit dem Object Builder im Framework können Sie die gewünschte Küche einbinden. zum Beispiel

class RestaurantObjectBuilder
{
   IFactory _factory = new DefaultFoodFactory();

   //This can be used when you want to plugin the 
   public void SetFoodFactory(IFactory customFactory)
   {
        _factory = customFactory;
   }

   public IFactory GetFoodFactory()
   {
      return _factory;
   }
}
Nitin
quelle
7

Ich mochte das Builder-Muster immer nicht als etwas Unhandliches, Aufdringliches und sehr oft von weniger erfahrenen Programmierern missbraucht. Es ist ein Muster, das nur dann Sinn macht, wenn Sie das Objekt aus einigen Daten zusammensetzen müssen, für die ein Schritt nach der Initialisierung erforderlich ist (dh wenn alle Daten erfasst wurden - tun Sie etwas damit). Stattdessen werden in 99% der Fälle Builder einfach zum Initialisieren der Klassenmitglieder verwendet.

In solchen Fällen ist es weitaus besser, einfach withXyz(...)Typensetter innerhalb der Klasse zu deklarieren und sie dazu zu bringen, einen Verweis auf sich selbst zurückzugeben.

Bedenken Sie:

public class Complex {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first=first; 
    }

    ... 

    public Complex withFirst(String first){
       this.first=first;
       return this; 
    }

    public Complex withSecond(String second){
       this.second=second;
       return this; 
    }

    public Complex withThird(String third){
       this.third=third;
       return this; 
    }

}


Complex complex = new Complex()
     .withFirst("first value")
     .withSecond("second value")
     .withThird("third value");

Jetzt haben wir eine ordentliche Einzelklasse, die ihre eigene Initialisierung verwaltet und fast die gleiche Arbeit wie der Builder erledigt, nur dass sie weitaus eleganter ist.

Pavel Lechev
quelle
Ich habe gerade beschlossen, mein komplexes XML-Dokument als JSON zu erstellen. Erstens, woher weiß ich, dass die Klasse 'Complex' überhaupt ein XML-fähiges Produkt liefern kann, und wie würde ich es ändern, um ein JSONable-Objekt zu erzeugen? Schnelle Antwort: Ich kann nicht, weil ich Builder verwenden muss. Und wir schließen den Kreis ...
David Barker
1
Insgesamt bs, Builder wurde entwickelt, um unveränderliche Objekte zu bauen und mit der Fähigkeit, die Art des Bauens in Zukunft zu ändern, ohne die Produktklasse zu berühren
Mickey Tin
6
Hmmm? Haben Sie irgendwo in der Antwort gelesen, wofür Builder entwickelt wurde? Dies ist eine alternative Sichtweise zu der Frage oben "Wann würden Sie das Builder-Muster verwenden?", Die auf der Erfahrung unzähligen Missbrauchs des Musters basiert, bei der etwas viel Einfacheres die Arbeit besser macht. Alle Muster sind nützlich, wenn Sie wissen, wann und wie Sie sie verwenden müssen - das ist der springende Punkt bei der Dokumentation der Muster! Wenn das Muster überbeansprucht oder schlimmer - missbraucht - wird, wird es im Kontext Ihres Codes zu einem Anti-Pattern. Ach ...
Pavel Lechev
6

Ein weiterer Vorteil des Builders besteht darin, dass bei einer Factory immer noch eine gewisse Kopplung in Ihrem Code vorhanden ist, da die Factory alle Objekte kennen muss, die sie möglicherweise erstellen kann, damit sie funktioniert . Wenn Sie ein anderes Objekt hinzufügen, das erstellt werden könnte, müssen Sie die Factory-Klasse ändern, um ihn einzuschließen. Dies geschieht auch in der Abstract Factory.

Mit dem Builder hingegen müssen Sie nur einen neuen konkreten Builder für diese neue Klasse erstellen. Die Director-Klasse bleibt gleich, da sie den Builder im Konstruktor empfängt.

Es gibt auch viele Arten von Buildern. Kamikaze Mercenary`s gibt noch einen.

Lino Rosa
quelle
6
/// <summary>
/// Builder
/// </summary>
public interface IWebRequestBuilder
{
    IWebRequestBuilder BuildHost(string host);

    IWebRequestBuilder BuildPort(int port);

    IWebRequestBuilder BuildPath(string path);

    IWebRequestBuilder BuildQuery(string query);

    IWebRequestBuilder BuildScheme(string scheme);

    IWebRequestBuilder BuildTimeout(int timeout);

    WebRequest Build();
}

/// <summary>
/// ConcreteBuilder #1
/// </summary>
public class HttpWebRequestBuilder : IWebRequestBuilder
{
    private string _host;

    private string _path = string.Empty;

    private string _query = string.Empty;

    private string _scheme = "http";

    private int _port = 80;

    private int _timeout = -1;

    public IWebRequestBuilder BuildHost(string host)
    {
        _host = host;
        return this;
    }

    public IWebRequestBuilder BuildPort(int port)
    {
        _port = port;
        return this;
    }

    public IWebRequestBuilder BuildPath(string path)
    {
        _path = path;
        return this;
    }

    public IWebRequestBuilder BuildQuery(string query)
    {
        _query = query;
        return this;
    }

    public IWebRequestBuilder BuildScheme(string scheme)
    {
        _scheme = scheme;
        return this;
    }

    public IWebRequestBuilder BuildTimeout(int timeout)
    {
        _timeout = timeout;
        return this;
    }

    protected virtual void BeforeBuild(HttpWebRequest httpWebRequest) {
    }

    public WebRequest Build()
    {
        var uri = _scheme + "://" + _host + ":" + _port + "/" + _path + "?" + _query;

        var httpWebRequest = WebRequest.CreateHttp(uri);

        httpWebRequest.Timeout = _timeout;

        BeforeBuild(httpWebRequest);

        return httpWebRequest;
    }
}

/// <summary>
/// ConcreteBuilder #2
/// </summary>
public class ProxyHttpWebRequestBuilder : HttpWebRequestBuilder
{
    private string _proxy = null;

    public ProxyHttpWebRequestBuilder(string proxy)
    {
        _proxy = proxy;
    }

    protected override void BeforeBuild(HttpWebRequest httpWebRequest)
    {
        httpWebRequest.Proxy = new WebProxy(_proxy);
    }
}

/// <summary>
/// Director
/// </summary>
public class SearchRequest
{

    private IWebRequestBuilder _requestBuilder;

    public SearchRequest(IWebRequestBuilder requestBuilder)
    {
        _requestBuilder = requestBuilder;
    }

    public WebRequest Construct(string searchQuery)
    {
        return _requestBuilder
        .BuildHost("ajax.googleapis.com")
        .BuildPort(80)
        .BuildPath("ajax/services/search/web")
        .BuildQuery("v=1.0&q=" + HttpUtility.UrlEncode(searchQuery))
        .BuildScheme("http")
        .BuildTimeout(-1)
        .Build();
    }

    public string GetResults(string searchQuery) {
        var request = Construct(searchQuery);
        var resp = request.GetResponse();

        using (StreamReader stream = new StreamReader(resp.GetResponseStream()))
        {
            return stream.ReadToEnd();
        }
    }
}

class Program
{
    /// <summary>
    /// Inside both requests the same SearchRequest.Construct(string) method is used.
    /// But finally different HttpWebRequest objects are built.
    /// </summary>
    static void Main(string[] args)
    {
        var request1 = new SearchRequest(new HttpWebRequestBuilder());
        var results1 = request1.GetResults("IBM");
        Console.WriteLine(results1);

        var request2 = new SearchRequest(new ProxyHttpWebRequestBuilder("localhost:80"));
        var results2 = request2.GetResults("IBM");
        Console.WriteLine(results2);
    }
}
Raman Zhylich
quelle
1
Sie können Ihre Antworten auf zwei Arten verbessern: 1) Machen Sie es SSCCE. 2) Erklären Sie, wie dies die Frage beantwortet.
James.garriss
3

Ich habe Builder in einer eigenen Messaging-Bibliothek verwendet. Der Bibliothekskern empfing Daten von der Leitung und sammelte sie mit der Builder-Instanz. Nachdem Builder entschieden hatte, dass alles zum Erstellen einer Nachrichteninstanz erforderlich ist, erstellte Builder.GetMessage () eine Nachrichteninstanz unter Verwendung der von der Builder-Instanz gesammelten Daten Draht.

Waschmaschine
quelle
3

Schauen Sie sich InnerBuilder an, ein IntelliJ IDEA-Plugin, das dem Menü Generieren (Alt + Einfügen) eine Aktion 'Builder' hinzufügt, die eine innere Builder-Klasse generiert, wie in Effective Java beschrieben

https://github.com/analytically/innerbuilder

analytisch
quelle
2

Als ich den Standard-XMLGregorianCalendar für mein XML verwenden wollte, um das Marshalling von DateTime in Java zu beanstanden, hörte ich viele Kommentare darüber, wie schwer und umständlich es war, es zu verwenden. Ich habe versucht, die XML-Felder in den xs: datetime-Strukturen zu steuern, um Zeitzone, Millisekunden usw. zu verwalten.

Deshalb habe ich ein Dienstprogramm zum Erstellen eines XMLGregorian-Kalenders aus einem GregorianCalendar oder java.util.Date entwickelt.

Aufgrund meines Arbeitsplatzes darf ich es nicht ohne rechtliche Genehmigung online teilen. Hier ist jedoch ein Beispiel dafür, wie ein Kunde es verwendet. Es abstrahiert die Details und filtert einige der Implementierungen von XMLGregorianCalendar, die für xs: datetime weniger verwendet werden.

XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();

Zugegeben, dieses Muster ist eher ein Filter, da es Felder im xmlCalendar als undefiniert festlegt, damit sie ausgeschlossen werden. Es "erstellt" es dennoch. Ich habe dem Builder problemlos andere Optionen hinzugefügt, um eine xs: date- und xs: time-Struktur zu erstellen und bei Bedarf auch Zeitzonen-Offsets zu bearbeiten.

Wenn Sie jemals Code gesehen haben, der XMLGregorianCalendar erstellt und verwendet, werden Sie sehen, wie dies die Manipulation erheblich vereinfacht.

John Brown
quelle
0

Ein gutes Beispiel aus der Praxis ist das Testen Ihrer Klassen. Sie verwenden Sut-Builder (System Under Test).

Beispiel:

Klasse:

public class CustomAuthenticationService
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationService(ICloudService cloudService, IDatabaseService databaseService)
    {
        _cloudService = cloudService;
        _databaseService = databaseService;
    }

    public bool IsAuthorized(User user)
    {            
        //Implementation Details
        return true;

}

Prüfung:

    [Test]
    public void Given_a_User_With_Permission_When_Verifying_If_Authorized_Then_Authorize_It_Returning_True()
    {
        CustomAuthenticationService sut = new CustomAuthenticationServiceBuilder();
        User userWithAuthorization = null;

        var result = sut.IsAuthorized(userWithAuthorization);

        Assert.That(result, Is.True);
    }

Sut Builder:

public class CustomAuthenticationServiceBuilder
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationServiceBuilder()
    {
        _cloudService = new AwsService();
        _databaseService = new SqlServerService();
    }

    public CustomAuthenticationServiceBuilder WithAzureService(AzureService azureService)
    {
        _cloudService = azureService;

        return this;
    }

    public CustomAuthenticationServiceBuilder WithOracleService(OracleService oracleService)
    {
        _databaseService = oracleService;

        return this;
    }

    public CustomAuthenticationService Build()
    {
        return new CustomAuthenticationService(_cloudService, _databaseService);
    }

    public static implicit operator CustomAuthenticationService (CustomAuthenticationServiceBuilder builder)
    {
        return builder.Build();
    }
}
Rafael Miceli
quelle
Warum brauchen Sie in diesem Fall einen Builder, anstatt Ihrer CustomAuthenticationServiceKlasse nur Setter hinzuzufügen ?
Laur Ivan
Das ist eine gute Frage @LaurIvan! Vielleicht war mein Beispiel etwas schlecht, aber stellen Sie sich vor, Sie können die CustomAuthenticationService-Klasse nicht ändern. Der Builder wäre eine attraktive Möglichkeit, das Lesen Ihrer Komponententests zu verbessern. Und ich denke, Get Getter und Setter werden Ihre Felder freilegen, die nur für Ihre Tests verwendet werden. Wenn Sie jetzt mehr über den Sut Builder erfahren möchten, lesen Sie den Test Data Builder , der ziemlich gleich ist, nur für Suts.
Rafael Miceli