【问题标题】:Using fluent interface with builder pattern使用带有构建器模式的流利界面
【发布时间】:2019-11-24 19:25:00
【问题描述】:

我试图通过在下面创建人员构建器对象来理解流利的构建器模式。我已经编写了我想使用的代码,但是在实现它时遇到了问题。我的问题如下:

  1. 调用HavingJob() 时,这应该会创建一个新作业,然后可以仅使用适用于作业的方法对其进行配置,并最终将其添加到人员的Jobs 集合中。感觉应该返回它,以便可以在其上调用其他流畅的作业方法。暂时不知道如何实现,允许在该级别及更高级别进行链接。
  2. 在实现IJobBuilder 方法时,我无法访问他们在HavingJob() 方法中创建的特定作业,因为我需要返回IJobBuilder 以将流利的方法限制为仅与以下相关的方法工作。 HavingJob() 的诀窍是什么,以便那些特定的作业方法可以在特定作业上运行,同时仍然允许链接?
  3. 一旦我走上了以IJobBuilder 结尾的流畅路径,我就不能再调用Build()HavingJob() 来添加其他作业。这个问题的答案是拥有一个继承自PersonBuilderIJobBuilder 的单独实现吗?
    public class Person
    {
        public string Name { get; set; }
        public List<Job> Jobs { get; set; }
        public List<Phone> Phones { get; set; }
    }

    public class Phone
    {
        public string Number { get; set; }
        public string Usage { get; set; }
    }

    public class Job
    {
        public string CompanyName { get; set; }
        public int Salary { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var p = PersonBuilder
                .Create()
                    .WithName("My Name")
                    .HavingPhone("222-222-2222")
                        .WithUsage("CELL")
                    .HavingJob()
                        .WithCompanyName("First Company")
                        .WithSalary(100)
                    .HavingJob()
                        .WithCompanyName("Second Company")
                        .WithSalary(200)
                .Build();

            Console.WriteLine(JsonConvert.SerializeObject(p));
        }
    }

    public class PersonBuilder : IJobBuilder
    {
        protected Person Person;
        public PersonBuilder() { Person = new Person(); }
        public static PersonBuilder Create() => new PersonBuilder();
        public PersonBuilder WithName(string name)
        {
            Person.Name = name;
            return this;
        }

        public PersonBuilder HavingPhone(string phoneNumber)
        {
            // Need instance of phone
            return this;
        }

        public PersonBuilder WithUsage(string phoneUsage)
        {
            // Need instance of phone
            return this;
        }

        public IJobBuilder HavingJob()
        {
            // Need to create a job here and return it so that IJobBuilder methods work on specific instance right?
            return this;
        }

        public Person Build() => Person;

        public IJobBuilder WithCompanyName(string companyName)
        {
            // How do I set the company name if I don't have the job instance here
            job.CompanyName = companyName;
            return this;
        }

        public IJobBuilder WithSalary(int amount)
        {
            // How do I set the salary if I don't have a specific job instance here
            job.Salary = amount;
            return this;
        }
    }

    public interface IJobBuilder
    {
        IJobBuilder WithCompanyName(string companyName);
        IJobBuilder WithSalary(int salary);
    }

【问题讨论】:

    标签: c# .net-core fluent


    【解决方案1】:

    单一职责原则 (SRP) 和关注点分离 (SoC)

    一个 Job Builder 应该负责构建一个 Job

    public interface IJobBuilder {
        IJobBuilder WithCompanyName(string companyName);
        IJobBuilder WithSalary(int salary);
    }
    
    public class JobBuilder : IJobBuilder {
        private readonly Job job;
    
        public JobBuilder() {
            job = new Job();
        }
    
        public IJobBuilder WithCompanyName(string companyName) {
            job.CompanyName = companyName;
            return this;
        }
    
        public IJobBuilder WithSalary(int amount) {
            job.Salary = amount;
            return this;
        }
    
        internal Job Build() => job;
    }
    

    Person Builder 应该负责构建 Person。

    public class PersonBuilder {
        protected Person Person;
        
        private PersonBuilder() { Person = new Person(); }
    
        public static PersonBuilder Create() => new PersonBuilder();
    
        public PersonBuilder WithName(string name) {
            Person.Name = name;
            return this;
        }
    
        public PersonBuilder HavingJob(Action<IJobBuilder> configure) {
            var builder = new JobBuilder();
            configure(builder);
            Person.Jobs.Add(builder.Build());
            return this;
        }
    
        public Person Build() => Person;
    
    }
    

    在上述构建器中,它将作业的构建委托给其负责的构建器。

    这导致以下重构

    class Program {
        static void Main(string[] args) {
            var p = PersonBuilder
                .Create()
                    .WithName("My Name")
                    .HavingJob(builder => builder
                        .WithCompanyName("First Company")
                        .WithSalary(100)
                    )
                    .HavingJob(builder => builder
                        .WithCompanyName("Second Company")
                        .WithSalary(200)
                    )
                .Build();
    
            Console.WriteLine(JsonConvert.SerializeObject(p));
        }
    }
    

    【讨论】:

    • 感谢您的快速响应,这确实可行。这种方法是否总是需要为任何不是 person 值类型的类型创建一个完全独立的构建器。获取一个简单的电话号码列表,甚至是一组昵称(我已经编辑了我的原始帖子)。我只想通过设置电话号码使用的选项将电话号码添加到列表中。你会创建一个昵称生成器和电话号码生成器来限制流畅的 API 方法选项吗?我在一个集合上苦苦挣扎,其中每个项目都可以有自己的一组特定的流体方法调用。
    • @Geekn 在类似手机的情况下,您始终可以在函数PersonBuilder HavingPhone(string number, string usage) { .. }中传递所有详细信息
    • 似乎我已经看到了一些使用泛型和继承或流式接口与分面构建器的方法。
    • 我当然可以,但我稍微扩展了这个例子来展示我正在努力解决的真正问题。 HasPhone.WithCountryCode().CanUseForMarketing() 等...
    • 你如何在一个项目集合上设计流体,其中每个项目都有自己的方法,这基本上是我问题的症结所在。我也看到了相互继承的建设者。或者有的每个路径都有一个接口。
    猜你喜欢
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-19
    相关资源
    最近更新 更多