[java] متى ستستخدم نموذج باني؟



Answers

فيما يلي بعض الأسباب التي تدور حول استخدام النمط ورمز المثال في Java ، إلا أنه تطبيق لنمط البناء الذي تغطيه عصابة الأربعة في أنماط التصميم . تنطبق الأسباب التي قد تستخدمها في Java أيضًا على لغات البرمجة الأخرى أيضًا.

كما يقول جوشوا بلوخ في جاوة الفعالة ، الطبعة الثانية :

نمط البناء هو اختيار جيد عند تصميم الطبقات التي سيكون لدى منشآتها أو مصانعها الثابتة أكثر من حفنة من المعلمات.

لقد واجهنا جميعًا مرحلة ما مع قائمة بالمصنّعين حيث تضيف كل إضافة معلمة خيار جديدة:

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

وهذا ما يسمى نمط منشئ تصغير. تكمن المشكلة في هذا النمط في أنه بمجرد أن تكون أدوات الإنشاء عبارة عن 4 أو 5 معلمات ، يصبح من الصعب تذكر الترتيب المطلوب للمعلمات بالإضافة إلى ما هو المنشئ الخاص الذي قد تريده في حالة معينة.

إن أحد البدائل التي تحتاجها لنموذج منشئ التلسكوب هو نمط JavaBean حيث تقوم باستدعاء منشئ يحتوي على المعلمات الإلزامية ثم يتصل بعد ذلك بأية مستفيدين اختياريين بعد:

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

المشكلة هنا أنه نظرًا لأن الكائن يتم إنشاؤه عبر عدة مكالمات ، فقد يكون في حالة غير متناسقة من خلال بنائه. هذا يتطلب أيضا الكثير من الجهد الإضافي لضمان سلامة الخيط.

البديل الأفضل هو استخدام Pattern Builder Pattern.

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;
  }
}

لاحظ أن البيتزا غير قابلة للتغيير وأن قيم المعلمات جميعها موجودة في مكان واحد . لأن أساليب seter منشئ بإرجاع الكائن Builder أنها تكون قادرة على أن تكون بالسلاسل .

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

هذه النتائج في التعليمات البرمجية التي من السهل الكتابة وسهلة القراءة والفهم. في هذا المثال ، يمكن تعديل أسلوب البناء للتحقق من المعلمات بعد أن تم نسخها من المنشئ إلى كائن Pizza ورمي IllegalStateException إذا تم توفير قيمة معلمة غير صالحة. هذا النمط مرن ومن السهل إضافة المزيد من المعلمات إليه في المستقبل. من المفيد حقًا فقط إذا كنت ستحصل على أكثر من 4 أو 5 معلمات لمنشئ. ومع ذلك ، قد يكون من المفيد في المقام الأول أن تشك في إضافة المزيد من المعلمات في المستقبل.

لقد اقترضت بشكل كبير من هذا الموضوع من كتاب Effective Java، 2nd Edition by Joshua Bloch. لمعرفة المزيد عن هذا النمط وممارسات جافا الفعالة الأخرى ، أوصي به بشدة.

Question

ما هي بعض الأمثلة الشائعة في العالم الحقيقي لاستخدام نمط البناء؟ ماذا يشتري لك؟ لماذا لا تستخدم فقط نمط مصنع؟




استخدمت بانيًا في مكتبة مراسلة محلية. كان مركز المكتبة يتلقى البيانات من السلك ، وقام بتجميعها باستخدام مثيل Builder ، ثم بعد أن قرر Builder أن لديه كل ما يلزمه لإنشاء مثيل رسالة ، كان Builder.GetMessage () يقوم بإنشاء مثيل رسالة باستخدام البيانات التي تم جمعها من الأسلاك.




ميزة أخرى للبناء هي أنه إذا كان لديك مصنع ، لا يزال هناك بعض اقتران في رمز لك ، لأن المصنع للعمل ، فإنه يجب أن تعرف كل الأشياء التي يمكن أن تخلقها . إذا قمت بإضافة كائن آخر يمكن إنشاؤه ، فسيتعين عليك تعديل فئة المصنع لتضمينه. يحدث هذا في مصنع Abstract كذلك.

مع المنشئ ، من ناحية أخرى ، عليك فقط إنشاء منشئ إسمنت جديد لهذه الفئة الجديدة. سيبقى فصل المخرج كما هو ، لأنه يتلقى المنشئ في المُنشئ.

أيضا ، هناك العديد من النكهات من باني. كاميكازي المرتزقة يعطي واحد آخر.




اطلع على InnerBuilder ، وهو ملحق IntelliJ IDEA يضيف إجراء 'Builder' إلى قائمة الإنشاء (Alt + Insert) التي تولد فئة منشئ داخلي كما هو موضح في Effective Java

https://github.com/analytically/innerbuilder




يمكنك استخدامه عندما يكون لديك الكثير من الخيارات للتعامل معها. فكر في أشياء مثل jmock:

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

إنه شعور طبيعي أكثر و ... ممكن.

هناك أيضا بناء xml ، وبناء سلسلة والعديد من الأشياء الأخرى. تخيل لو java.util.Map وضعت كمنشئ. يمكنك فعل أشياء مثل هذه:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);



تعتبر فئة .NET StringBuilder مثالاً رائعًا لنمط البناء. يتم استخدامه في الغالب لإنشاء سلسلة في سلسلة من الخطوات. تكون النتيجة النهائية التي تحصل عليها في إجراء ToString () دائمًا سلسلة ولكن يختلف إنشاء تلك السلسلة وفقًا لما تم استخدامه من وظائف في الفئة StringBuilder. خلاصة القول ، فإن الفكرة الأساسية هي بناء الأجسام المعقدة وإخفاء تفاصيل التنفيذ حول كيفية بناءها.




/// <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);
    }
}





Related