《C++20设计模式》学习笔记——第10章外观模式


外观模式是一种为复杂子系统提供简单对外接口的方法。这里所说的“系统”可以是一组组件,也可以是具有相当复杂API的单个组件。

以房子为例,当用户购买房子时,通常关心房子的外观和内部结构;相比之下,可能不太关心内部系统:电气系统、保温系统以及下水道设施等。这些部件同样重要,但我们也只是希望它们能够无终止地“正常工作”。

1. 幻方生成器

本节考虑一个简单的例子:制造幻方的过程。幻方是一个矩阵,其中任意行、列或者任意对角线的数字加起来的和相同。
如果我们要自己生成幻方,可以将其想像为三个不同的子系统的相互作用:

  • Generator(发生器):生成指定个数的随机数字序列的组件。
  • Splitter(分离器):接受矩形矩阵,并输出一组表示矩阵中所有行、列和对角线的列表的组件;
  • Verifier(验证器):检查列表中的每个行、列和对角线的元素的和是否相等。

(注意:用这种方法生成幻方当然是不切实际的,只是为了介绍外观模式而举的例子)

Generator通过特定的算法,生成一个包含指定数量随机数的序列,并以向量的形式返回。为保持代码简介,这里使用的是rand()函数。为生成幻方,我们调用N次generate()函数,生成N行数字序列,最后得到一个NxN的方阵。

//发生器
struct Generator
{
    virtual std::vector<int> generate(const int count) const
    {
        std::vector<int> result(count);
        std::generate(result.begin(), result.end(), [&](){return 1 + std::rand()%9;});  //伪随机:每次运行结果相同!!
        return result;
    }
};

组件Splitter接受已生成的二维矩阵,并使用它生成表示矩阵的所有行、列和对角线的唯一元素。
实现代码相当冗长,此处省略。

//分离器
struct Splitter
{
    std::vector<std::vector<int> > split(std::vector<std::vector<int> > array) const
    {
        //implimentation omitted
        return {};
    }
};

最后一个组件Verifier检查上述各行、列、对角线所有元素的和是否像等。

//验证器
struct Verifier
{
    bool verify(std::vector<std::vector<int> > array) const
    {
        if(array.empty()) 
        {
            std::cout << "[verifier]: array is empty!" << std::endl; 
            return false;
        }
        auto expected = std::accumulate(array[0].begin(), array[0].end(), 0);
        return std::all_of(array.begin(), array.end(), [=](auto& inner)
        {
            return std::accumulate(inner.begin(), inner.end(),0) == expected;
        });
    }
};

现在我们有了三个不同的子系统,它们将协同工作,以生成随机幻方。但是如果我们将这些类提供给客户,客户将很难正确使用它们。于是我们可以创建一个外观类,本质上是一个隐藏了内部所有实现细节并对外只提供一个简单接口的包装类。

//幻方外观类
struct MagicSquareGenerator
{
    std::vector<std::vector<int> > generate(int size)
    {
        Generator g;
        Splitter s;
        Verifier v;
        std::vector<std::vector<int> > square;
        int count = 0;
        const uint64_t MAX_COUNTS = 1e6; 
        bool verified = false;
        do {
            square.clear();
            for(int i = 0; i<size; ++i)
            {
                square.emplace_back(g.generate(size));
            } 
            for(auto item : square)
            {
                for(auto value : item)
                {
                    std::cout << value << " ";
                }
                std::cout << std::endl;
            }
            count++;
            auto splited = s.split(square);
            verified = v.verify(splited); 
        }
        while(!verified && count<MAX_COUNTS);

        if(count < MAX_COUNTS) 
        {
            std::cout << "count = " << count << std::endl;
            return square;
        }
        else
        {
            std::cout << "over max counts, no result!!" << std::endl;
            return {{}};
        }
    }
};

为了允许高级用户使用附加功能自定义和扩展外观类的行为,我们将每个子系统作为模板参数传入MagicSquareGenerator:

//允许高级用户提供各个组件
template <typename G = Generator,
    typename S = Splitter,
    typename V = Verifier>
struct MagicSquareGenerator2
{
    std::vector<std::vector<int> > generate(int size)
    {
        G g;
        S s;
        V v;
        //rest of code as before;
    }
};

如果愿意,我们可以进一步添加限制,要求参数G、S和V从相应的类继承。
现在,可以创建一个UniqueGenerator,以确保生成的集合中的所有数字都是唯一的:

struct UniqueGenerator : Generator
{
    std::vector<int> generate(const int count) const override
    {
        std::vector<int> result;
        do
        {
            result = Generator::generate(count);
        } while(std::set<int>(result.begin(), result.end()).size() != result.size());
        return result;
    }
};  

这个例子表明,可以将不同子系统之间的复杂的交互隐藏在外观类的后面,还可以灵活控制外观类内部子系统的可配置性,以便用户可以在需要时定制外观类的内部操作。

2. 构建贸易终端

本小节以终端的构建为例子:终端在后头包含了缓冲区和视窗两个部分;具体的介绍并不完整,代码省略。
简而言之,与用户直接进行交互的终端是一种外观模式,它是内部相当复杂的缓冲区和视窗的简化表示形式。

3. 总结

外观模式是一种为复杂子系统提供简单对外接口的方法。这里所说的“系统”可以是一组组件,也可以是具有相当复杂API的单个组件。它使用户更容易使用我们开发的程序,同时也允许高级用户针对特定需求利用外观模式进行附加功能的开发和调整。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值