热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于c++:谈-C17-里的-Builder-模式

曾经写了一篇谈C++17里的Factory模式,起初又顺便肝了一篇谈C++17里的Singleton模式。看来是得要整一大堆了,对于懒惰的人来说这很麻烦。我不晓得是不是要打算会写残缺个GoF的集体了解以及新的的实现,缓缓看吧,做了就做了。

曾经写了一篇 谈 C++17 里的 Factory 模式 ,起初又顺便肝了一篇 谈 C++17 里的 Singleton 模式 。看来是得要整一大堆了,对于懒惰的人来说这很麻烦。我不晓得是不是要打算会写残缺个 GoF 的集体了解以及新的的实现,缓缓看吧,做了就做了。

回顾下构建者模式,并应答做类库时遇到的构建者模板类应如何实作的问题。

Prologue

实际上,就我集体而言,真正地使用 builder pattern,反而是在 Java 开发经验中。流式接口也是如此。

Builder 模式就是为了分步骤结构一个对象用的,看图:

FROM: HERE

尽管很多时候咱们都只关怀 new 了对象后怎么操作它,然而有的时候有的场景里的确咱们只会关怀怎么 new 这个对象。这时候就是 Builder 了。

Builder Pattern

实践

Builder 模式是 Creational Patterns 中的一种。在 谈 C++17 里的 Factory 模式 中,咱们曾经介绍过创立型模式了,所以本文不再赘述了。

构建者模式的用意,就在于让你能够分步骤地构建简单对象,它容许你应用雷同(类似)的创立diamanté生产出不同类型和模式的对象。

对于 Builder 模式来说,一个重要的标记,只管这并不是规定但却往往约定俗成,就是以一个 .build() 调用作为完结。例如:

auto shape = Builder()
  .choose(Shape.Rect)   // choose a factory
  .setColor(COLOR.RED)
  .setBorderWidth(1)
  .setFill(COLOR.GRAY)
  .build();
canva.place(shape, Position.Default);

Builder 模式并非必须得要采纳流式接口。

反而在很多时候咱们须要和交互对象协商一个抉择,并将这个决定设置到 Builder 结构者中。直到全副协商实现之后,才应用 builder.build() 构建出最终产品实例。

如同示例代码中给出的设想,咱们还能够糅合 Builder 和 Factory 模式(以及 Proxy 模式或者其它),让一个根本性的 Builder 去调用 concreted 的 FactoryBuilder 来构建多种产品。因为这往往须要较大篇幅的代码能力呈现出风貌,故而不再开展了。

C++ 实现

留神上面的示例都较长。

根本的

上面是一个规范的、根本的 builder pattern 案例。这个案例通过 email 的四个组成元素的分步结构来展现 builder pattern 的典型实现办法。

namespace hicc::dp::builder::basic {

  class email_builder;
  class email {
    public:
    ~email() {}
    friend class email_builder; // the builder can access email's privates
    static email_builder builder();

    std::string to_string() const {
      std::stringstream ss;
      ss <<"   from: " <<_from
        <<"\n     to: " <<_to
        <<"\nsubject: " <<_subject
        <<"\n   body: " <<_body;
      return ss.str();
    }

    explicit email(std::string const &from, std::string const &to, std::string const &subject, std::string const &body)
      : _from(from)
        , _to(to)
        , _subject(subject)
        , _body(body) {}
    email(email &&o) {
      _from = o._from, _to = o._to, _subject = o._subject, _body = o._body;
    }
    email clone(email &&o) {
      email n{o._from, o._to, o._subject, o._body};
      return n;
    }

    private:
    email() = default; // restrict construction to builder
    std::string _from{}, _to{}, _subject{}, _body{};
  };

  class email_builder {
    public:
    email_builder &from(const std::string &from) {
      _email->_from = from;
      return *this;
    }

    email_builder &to(const std::string &to) {
      _email->_to = to;
      return *this;
    }

    email_builder &subject(const std::string &subject) {
      _email->_subject = subject;
      return *this;
    }

    email_builder &body(const std::string &body) {
      _email->_body = body;
      return *this;
    }

    operator std::unique_ptr &&() {
      return std::move(_email); // notice the move
    }

    auto build() {
      return std::move(_email); // not a best solution since concise is our primary intent
    }

    email_builder()
      : _email(std::make_unique("", "", "", "")) {}

    private:
    std::unique_ptr _email;
  };

  inline email_builder email::builder() { return email_builder(); }
  inline std::ostream &operator<<(std::ostream &os, const email &email) {
    os <

而它的测试代码局部也呈现出了典型的流式调用格调。

示例代码提供了一种编码构造上的刻板伎俩,即通过 model class::builder() 取得构建者,在最初一步时以 builder.build() 来取得最终的 model class 实例对象。有时候刻板伎俩是最佳的抉择。确实,稍后咱们会看到一个 design pattern 其实现办法是能够多种多样的。然而放弃编码构造的相似性,将会有利于使用者在探视接口 API 时,尤其是通过 namespace 层级探视可用的接口时,无需额定文档地取得接口应用办法。

所以,代码本人可能阐明所有,这是你回避正文的正确伎俩。

额定提醒

为了因应 Modern C++ 格调,示例代码应用了 unique_ptr 来帮忙治理示例。为什么不应用 shared_ptr 呢?因为 shared_ptr 相对来说更惨重,它须要额定治理一套援用计数机制,所以间接应用 unique_ptr 而只在必要时(例如须要在多个容器中托管时)才思考应用 shared_ptr。

那采纳下面的固定范式,但我须要的是 shared_ptr 该怎么办呢,我可能把 unique_ptr 转换成 shared_ptr 语义吗?

这一点,并不是问题,挪动语义容许间接传送 u 到 s:

std::unique_ptr unique = std::make_unique("test");
std::shared_ptr shared = std::move(unique);

甚至于:

std::shared_ptr shared = std::make_unique("test");

所以在 build() 时你能够决定是否做显式的返回类型申明:

auto obj = builder.build(); // 失去 unique_ptr
std::shared_ptr o = builder.build(); // 隐含一个挪动操作

嵌入的

后面的示例中采纳了拆散的两个独立类的形式,这样显得类的构造以及依赖关系更清晰,但可能略微有点净化,因为在名字空间中会有一个产品的 builder 类的额定的存在。而一个命名为 models 的 namespace 中是不应该有非 Model 的其它货色——helpers 也好,utilities 也好——的存在的。因而,特地是在 metaprogramming 中,更偏向于将 builder class 间接嵌入 product class 中:

namespace hicc::dp::builder::embed {

  class email {
    public:
    class builder_impl {
      public:
      builder_impl &from(const std::string &from) {
        _email._from = from;
        return *this;
      }
      // ...
      auto build() {
        return _email;
      }

      private:
      std::unique_ptr _email;
    };

    static builder_impl builder(){
      return builder_impl{}; 
    }

    public:
    //...

    private:
    email() = default; // restrict construction to builder
    std::string _from, _to, _subject, _body;
  };

} // namespace hicc::dp::builder::embed

void test_builder_embed() {
  using namespace hicc::dp::builder::embed;
  // @formatter:off
  auto mail = email::builder()
    .from("me@mail.com")
    .to("you@mail.com")
    .subject("About Design Patterns")
    .body("There is a plan to write a book about cxx17 design patterns. It's good?")
    .build();
  std::cout <

使用者简直没有订正的必要。

它的额定益处在于没有前向参考的额定申明的必要,也无需 friend class 的申明的必要,能够省去不少脑力。

简单的

然而,builder pattern 并不是非得要有一个 build() 办法来做临门一脚,也并不是非得要采纳流式接口不可。上面这个案例也经常呈现在相应的 tutor 中,但咱们进行了革新。

首先给出产品类局部:

namespace hicc::dp::builder::complex {

  namespace basis {
    class wheel {
      public:
      int size;
    };

    class engine {
      public:
      int horsepower;
    };

    class body {
      public:
      std::string shape;
    };

    class car {
      public:
      wheel *wheels[4];
      engine *engine;
      body *body;

      void specifications() {
        std::cout <<"body:" horsepower <size <<"'" <

它没什么好说的。

然而它的 builder 会比较复杂,因为这里决定有两种预制的 builder(Jeep 和 Nissan)别离制作不同规格的 Car。所以咱们须要一个抽象类的 builder class,以及一个构建样板类 director,实际上你也能够不用拆散样板类,充分利用多态性也是能够的:

namespace hicc::dp::builder::complex {

  class builder {
    public:
    virtual basis::wheel *get_wheel() = 0;
    virtual basis::engine *get_engine() = 0;
    virtual basis::body *get_body() = 0;
  };

  class director {
    public:
    void set_builder(builder *b) { _builder = b; }

    basis::car *get_car() {
      basis::car *car = new basis::car();

      car->body = _builder->get_body();

      car->engine = _builder->get_engine();

      car->wheels[0] = _builder->get_wheel();
      car->wheels[1] = _builder->get_wheel();
      car->wheels[2] = _builder->get_wheel();
      car->wheels[3] = _builder->get_wheel();

      return car;
    }

    private:
    builder *_builder;
  };

} // namespace hicc::dp::builder::complex

样板类决定了构建 Car 的规范样板。

如果你的确采纳了在抽象类 builder class 中间接实现 get_car() 的代码逻辑,并且使其 virtual 化(这并不是必须的)的话,那么这套做法实际上也援用了模板办法模式(Template Method Pattern)。

模板办法模式(Template Method Pattern)在超类中定义了一个算法的框架, 容许子类在不批改构造的状况下重写算法的特定步骤。

接下来,是具体实现两个 builder 类了:

namespace hicc::dp::builder::complex {

  class jeep_builder : public builder {
    public:
    basis::wheel *get_wheel() {
      basis::wheel *wheel = new basis::wheel();
      wheel->size = 22;
      return wheel;
    }

    basis::engine *get_engine() {
      basis::engine *engine = new basis::engine();
      engine->horsepower = 400;
      return engine;
    }

    basis::body *get_body() {
      basis::body *body = new basis::body();
      body->shape = "SUV";
      return body;
    }
  };

  class nissan_builder : public builder {
    public:
    basis::wheel *get_wheel() {
      basis::wheel *wheel = new basis::wheel();
      wheel->size = 16;
      return wheel;
    }

    basis::engine *get_engine() {
      basis::engine *engine = new basis::engine();
      engine->horsepower = 85;
      return engine;
    }

    basis::body *get_body() {
      basis::body *body = new basis::body();
      body->shape = "hatchback";
      return body;
    }
  };

} // namespace hicc::dp::builder::complex

以及,它的测试代码:

void test_builder_complex() {
  using namespace hicc::dp::builder::complex;

  basis::car *car; // Final product

  /* A director who controls the process */
  director d;

  /* Concrete builders */
  jeep_builder jb;
  nissan_builder nb;

  /* Build a Jeep */
  std::cout <<"Jeep" <specifications();

  std::cout <specifications();
}

留神 Car 由很多部件组合,每个部件也可能有很简单的构建步骤。

优化

当然啰,这个示例仅仅只是示例。在真实世界里,这个示例的实现能够将 jeep_builder 和 nissan_builder 抽出一个公共的基类:

class managed_builder : public builder {
  public:
  basis::wheel *get_wheel() {
    basis::wheel *wheel = new basis::wheel();
    wheel->size = wheel_size;
    return wheel;
  }

  basis::engine *get_engine() {
    basis::engine *engine = new basis::engine();
    engine->horsepower = engine_horsepower;
    return engine;
  }

  basis::body *get_body() {
    basis::body *body = new basis::body();
    body->shape = body_shape;
    return body;
  }

  managed_builder(int ws, int hp, const char *s = "SUV")
    : wheel_size(ws), engine_horsepower(hp), body_shape(s) {}
  int wheel_size;
  int engine_horsepower;
  std::string_view body_shape;
};

岂但有利于打消反复代码片段,而且更能应答未来的扩大,万一想要 BMW 呢。

进一步地泛型化

其实也能够应用模板类的形式:

template
class generic_builder : public builder {
  public:
  basis::wheel *get_wheel() {
    basis::wheel *wheel = new basis::wheel();
    wheel->size = wheel_size;
    return wheel;
  }

  basis::engine *get_engine() {
    basis::engine *engine = new basis::engine();
    engine->horsepower = engine_horsepower;
    return engine;
  }

  basis::body *get_body() {
    basis::body *body = new basis::body();
    body->shape = body_shape;
    return body;
  }
};

constexpr const char suv_str[] = {"SUV"};
constexpr const char hatchback_str[] = {"hatchback"};

class jeep_builder : public generic_builder<22, 400, suv_str> {
  public:
  jeep_builder()
    : generic_builder<22, 400, suv_str>() {}
};

class nissan_builder : public generic_builder<16, 85, hatchback_str> {
  public:
  nissan_builder()
    : generic_builder<16, 85, hatchback_str>() {}
};

这里应用了 constexpr const char suv_str[] 这种技巧,它使得咱们可能设法在模板参数中间接传递字符串的字面量,于是下面的代码就残缺地模板化了。

如果你曾经开始应用 C++20 了,那么 std::basic_fixed_string 可能让你取得间接传递字符串字面量的能力:

template
class generic_builder : public builder {
  // ...
};

class jeep_builder : public generic_builder<22, 400, "SUV"> {
  public:
};

class nissan_builder : public generic_builder<16, 85, "hatchback"> {
  public:
};

如果感兴趣残缺源代码,能够去查阅相干源码 dp-builder.cc。

元编程中的 Builder Pattern

刚刚咱们提前讲述了泛型化一个 builder 的工作,但那只是做了一点初阶的重构而已。而当在模板类体系中须要应用 Builder Pattern 时,状况有一点点变动,特地是当对 builder 的专用代码向上抽出为一个繁多的基类时,咱们须要 CRTP 技术的染指。

CRTP

CRTP 是一种 C++ 习用法,它比 C++11 出世的早得多。在 Visual C++ 年代,ATL,WTL 以及大量的 MFC 均大规模地应用了这种技术,起初的 ProfUIS 也如此。

简略地说,CRTP 的目标在于实现编译期的多态绑定,实现办法是向基类的模板参数中传入派生类类名,于是基类就可能借助 static_cast(*this*) 语法来取得派生类的“多态”的操作能力了:

template 
class base{
  public:
  void do_sth(){
    static_cast(*this*)->show();
  }
  void show(){hicc_debug("base::show");}
};

template 
class derived: public base {
  public:
  T t{};
  void show(){
    hicc_debug("t: %s", hicc::to_string(t).c_str());
  }
};

可继承的 builder pattern

了解 CRTP 技术之后,这里仅仅给出一个示意性的片段:

namespace hicc::dp::builder::meta {
    class builder_base {
    public:
        builder_base &set_a() {
            return (*this);
        }

        builder_base& on_set_b(){
            return (*this);
        }
    };

    template
    class builder : public builder_base {
    public:
        derived_t &set_a() {
            return *static_cast(this);
        }
        derived_t &set_b() {
            return *static_cast(this);
        }

        std::unique_ptr t{}; // the temporary object for builder constructing...

        // ... more
    };

    template
    class jeep_builder : public builder, T> {
    public:
        jeep_builder &set_a() {
            return *this;
        }
    };
} // namespace hicc::dp::builder::meta

void test_builder_meta() {
    using namespace hicc::dp::builder::meta;
    jeep_builder b{};
    b.set_a();
}

在代码中,return *static_case(this) 能够保障总是返回 derived_t& 参考,这就可能保障从派生类中发动的链式调用 jeep_builder().set_a() 可能正确地调用派生类的重载版本(也是一个笼罩式、擦除式的版本),所以不应用 virtual function 的状况下仍可能正确(模仿)多态。

Epilogue

多数的个性有赖于 cxx17 以上的语法反对,但不是必需品。


推荐阅读
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
author-avatar
洪锐林
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有