Перейти к содержанию

BuilderVariants.cs

Код подключён напрямую из исходного файла в кодировке UTF-8, поэтому эта страница всегда показывает его актуальную версию.

← К конспекту Скачать исходный файл

BuilderVariants.cs
using System;
using System.Collections.Generic;

namespace DesignPatterns.Creational.Builder.Variants
{
    // Вариант 1: классический Builder. Один Director выполняет одинаковые шаги,
    // а разные строители получают несвязанные продукты.
    public interface IHouseBuilder
    {
        void Reset();
        void BuildWalls();
        void BuildRoof();
        void BuildGarage();
    }

    public sealed class House
    {
        private readonly List<string> _parts = new();
        public IReadOnlyList<string> Parts => _parts.AsReadOnly();
        public void Add(string part) => _parts.Add(part);
    }

    public sealed class HouseBuilder : IHouseBuilder
    {
        private House _house = new();

        public void Reset() => _house = new House();
        public void BuildWalls() => _house.Add("Кирпичные стены");
        public void BuildRoof() => _house.Add("Металлическая крыша");
        public void BuildGarage() => _house.Add("Гараж");

        public House GetResult()
        {
            House result = _house;
            Reset();
            return result;
        }
    }

    public sealed class ConstructionPlan
    {
        private readonly List<string> _steps = new();
        public IReadOnlyList<string> Steps => _steps.AsReadOnly();
        public void Add(string step) => _steps.Add(step);
    }

    public sealed class ConstructionPlanBuilder : IHouseBuilder
    {
        private ConstructionPlan _plan = new();

        public void Reset() => _plan = new ConstructionPlan();
        public void BuildWalls() => _plan.Add("Раздел: расчёт стен");
        public void BuildRoof() => _plan.Add("Раздел: расчёт крыши");
        public void BuildGarage() => _plan.Add("Раздел: расчёт гаража");

        public ConstructionPlan GetResult()
        {
            ConstructionPlan result = _plan;
            Reset();
            return result;
        }
    }

    public sealed class HouseDirector
    {
        public void BuildMinimal(IHouseBuilder builder)
        {
            builder.Reset();
            builder.BuildWalls();
            builder.BuildRoof();
        }

        public void BuildWithGarage(IHouseBuilder builder)
        {
            BuildMinimal(builder);
            builder.BuildGarage();
        }
    }

    // Вариант 2: методы расширения дают fluent-синтаксис существующему
    // изменяемому продукту. Продукт при этом не скрыт до завершения сборки.
    public sealed class MailDraft
    {
        public string From { get; set; } = string.Empty;
        public string To { get; set; } = string.Empty;
        public string Subject { get; set; } = string.Empty;
    }

    public static class MailDraftBuilderExtensions
    {
        public static MailDraft FromAddress(this MailDraft draft, string address)
        {
            draft.From = address;
            return draft;
        }

        public static MailDraft ToAddress(this MailDraft draft, string address)
        {
            draft.To = address;
            return draft;
        }

        public static MailDraft WithSubject(this MailDraft draft, string subject)
        {
            draft.Subject = subject;
            return draft;
        }
    }

    // Вариант 3: строго типизированный (staged/step) Builder. Build появляется
    // только после обязательных шагов To и Subject.
    public interface IRecipientStage
    {
        ISubjectStage To(string address);
    }

    public interface ISubjectStage
    {
        IOptionalStage Subject(string subject);
    }

    public interface IOptionalStage
    {
        IOptionalStage Body(string body);
        StagedEmail Build();
    }

    public sealed class StagedEmail
    {
        internal StagedEmail(string to, string subject, string body)
        {
            To = to;
            Subject = subject;
            Body = body;
        }

        public string To { get; }
        public string Subject { get; }
        public string Body { get; }
    }

    public sealed class StagedEmailBuilder : IRecipientStage, ISubjectStage, IOptionalStage
    {
        private string _to = string.Empty;
        private string _subject = string.Empty;
        private string _body = string.Empty;

        private StagedEmailBuilder()
        {
        }

        public static IRecipientStage Create() => new StagedEmailBuilder();

        public ISubjectStage To(string address)
        {
            _to = address;
            return this;
        }

        public IOptionalStage Subject(string subject)
        {
            _subject = subject;
            return this;
        }

        public IOptionalStage Body(string body)
        {
            _body = body;
            return this;
        }

        public StagedEmail Build() => new(_to, _subject, _body);
    }

    // Вариант 4: вложенный Builder создаёт неизменяемый продукт.
    public sealed class ImmutableRequest
    {
        private ImmutableRequest(Builder builder)
        {
            Url = builder.Url;
            Timeout = builder.Timeout;
            UseCache = builder.UseCache;
        }

        public string Url { get; }
        public TimeSpan Timeout { get; }
        public bool UseCache { get; }

        public static Builder Create() => new();

        public sealed class Builder
        {
            internal string Url { get; private set; } = string.Empty;
            internal TimeSpan Timeout { get; private set; } = TimeSpan.FromSeconds(30);
            internal bool UseCache { get; private set; }

            public Builder To(string url)
            {
                Url = url;
                return this;
            }

            public Builder WithTimeout(TimeSpan timeout)
            {
                Timeout = timeout;
                return this;
            }

            public Builder Cached()
            {
                UseCache = true;
                return this;
            }

            public ImmutableRequest Build()
            {
                if (string.IsNullOrWhiteSpace(Url))
                    throw new InvalidOperationException("URL обязателен.");

                return new ImmutableRequest(this);
            }
        }
    }

    public static class VariantsDemo
    {
        public static void Run()
        {
            var director = new HouseDirector();
            var houseBuilder = new HouseBuilder();
            var planBuilder = new ConstructionPlanBuilder();
            director.BuildWithGarage(houseBuilder);
            director.BuildWithGarage(planBuilder);

            MailDraft draft = new MailDraft()
                .FromAddress("author@example.com")
                .ToAddress("reader@example.com")
                .WithSubject("Builder");

            StagedEmail email = StagedEmailBuilder.Create()
                .To("reader@example.com")
                .Subject("Обязательные шаги проверяет компилятор")
                .Body("Build недоступен до вызовов To и Subject")
                .Build();

            ImmutableRequest request = ImmutableRequest.Create()
                .To("https://example.com")
                .WithTimeout(TimeSpan.FromSeconds(5))
                .Cached()
                .Build();

            Console.WriteLine(houseBuilder.GetResult().Parts.Count);
            Console.WriteLine(planBuilder.GetResult().Steps.Count);
            Console.WriteLine(draft.Subject);
            Console.WriteLine(email.Subject);
            Console.WriteLine(request.Url);
        }
    }
}