分类: 技术分享

  • iPad Pro 2020 简单评测 (对比 iPad Pro 10.5 2017)

    iPadPro_0.jpg

    生日的时候老婆大人送了一台iPad,相比之前使用的iPad Pro 2017 (10.5 inch)感觉提升还是挺大的。

    首先的提升是屏幕,大了0.5英寸, 屏占比提升了不少。其实2017 iPad Pro还有个奇怪的CPU过热问题:CPU发热量比较大,然后屏幕在CPU附近的区域会被“烤”黄。这个现象在白色显示的时候比较明显。不知道为什么,觉得屏幕素质还是比先辈提升了一点点。

    iPadPro_2.jpg

    CPU和内存方面有了显著提升,但是对我而言上手实际感觉出来不了多少。A10X变成了A12Z,内存从4GB提升到了6GB。但是库克还是比较省材料啊,想来安卓的手机内存都有8GB了。

    ipad_pro_4.png

    重量上面,这两台机器其实是很相似的。10.5寸款是477 g,新款轻了6g。如果不戴套的话,手持感觉相当棒~一般来说如果我看Kindle的话就会把套子去掉。推荐买那种背面磁吸的手机壳,拆装很方便!

    iPadPro_case.jpg

    本次更新最大的提升点是摄像头,但是鄙人用iPad摄像头的机会是少之又少。稍许试了一下,相比17款,摄像头的拍摄效果有巨大提升,特别是景深传感器感觉是下一代iPhone的提前布局。

    总而言之,如果手持是10.5寸Pad设备的话,强烈建议换机。但如果用的2018款的iPad Pro,除非要用到摄像头,否则不建议再花钱啦~

  • std::future 与 std::promise 简单使用、简单原理

    C++11有了一些关于线程的模型,在此之前C++里可是各自为政的,各种线程库各种神奇用法。其中有两个好玩的东西就是std::promise<T>std::future<T>,下文书中形容它们像是个“虫洞”。

    • std::future是虫洞的出口:一个未来对象的“获取器”,在未来的某一刻它能返回一个有用的值,但现在还没有…
    • std::promise是虫洞的入口:我们要保证在未来的某一刻给一个值进去,但不是现在…

    Basic usage

    一般来说这俩是成对使用的,看个代码:

    #include <cassert>
    #include <chrono>
    #include <future>
    #include <iostream>
    #include <string>
    #include <thread>
    
    using namespace std::chrono_literals;
    
    int main()
    {
      std::promise<int> p1, p2;
      std::future<int> f1 = p1.get_future();
      std::future<int> f2 = p2.get_future();
      
      p1.set_value(42);
      assert(f1.get() == 42);
      
      std::thread t([&]() {
        std::this_thread::sleep_for(100ms);
        p2.set_value(43);
      });
      
      auto start_time = std::chrono::system_clock::now();
      assert(f2.get() == 43);
      std::chrono::duration<double, std::milli> elapsed = std::chrono::system_clock::now() - start_time;
      std::cout << "Waited " << elapsed.count() << " ms\n";
      t.join();
      return 0;
    }
    
    // output: Waited 100.253 ms                                                                                                                                                       
    

    一个future对象可以通过promise.get_future()方法创建出来。当我们有真正的值来填进promose对象的时候,就用promise.set_value(v)方法。同时,(一般是在另一个线程里)当准备要获取值的时候,就调用future.get()方法。get方法会保持阻塞状态直到一个有效值被填进去。

    值得一提的是,有一个特化的T = void。这货有啥用呢?可以用来阻塞等待某个并行任务完成:

    std::promise<void> ready_p;
    std::future<void> read_f = ready_p.get_future();
    
    std::thread thread_b([&]() {
        prep_work();
        ready_p.set_value(); // no arg
        main_work();
    });
    ready_f.wait();
    // now that thread B has completed
    

    A bit of details

    需要留意的是,promise也好future也罢,都是有动态内存分配(dynamic memory allocation)的开销的。
    std_promise_future_schema.PNG(图源为参考文献)

    留意图中的那个State对象,它基本上是一个shared_ptr——因为虫洞的两端(很可能是不同线程)都要用到这个共享对象(shared ownership)。所以创建std::promose/std::future的时候都是要申请新的堆空间。

    Reference

    本文全文参考自本书:
    <Mastering the C++17 STL: Make Full Use of the Standard Library Components in C++17>

  • C++17 std::variant 简单使用

    std::variant应当是用来替代union来使用的,后者从C时代就有了,但是它缺乏类型安全的保障。boost库很早之前就有了实现(其实从C++11开始,感觉boost不知道多少东西被借鉴走了)。
    std::variant的类长这个样子:

    template <class... Types>
    class variant;
    

    Usage

    使用起来也是比较方便的

    std::variant<int, double> v1;
    v1 = 1; // activate the "int" member
    assert(v1.index() == 0);
    assert(std::get<0>(v1) == 1);
    
    v1 = 3.14; // activate the "double" member
    assert(v1.index() == 1);
    assert(std::get<1>(v1) == 3.14);
    assert(std::get<double>(v1) == 3.14);
    
    assert(std::holds_alternative<int>(v1) == false);
    assert(std::holds_alternative<double>(v1) == true);
    
    assert(std::get_if<int>(&v1) == nullptr);
    assert(*std::get_if<double>(&v1) == 3.14);
    

    上面代码里的几个好用的constexpr函数:

    • std::get<>:可以传入index或是type。如果index或者类型不对的话,会跑出std::bad_variant_access异常;
    • std::get_if<>:这个是不抛异常的版本。注意参数和返回值类型都是指针——如果没有有效元素,那就会返回nullptr;
    • std::holds_alternative<Type>:判断一个Type在不在定义的variant参数类里面;

    Visiting variants

    如果要根据当前存储的类型来对variant对象做一些操作,第一会想到写一些if语句

    if (std::holds_alternative<int>(v1) == true) {
      // handle int value
    }
    else {
      // handle double value
    }
    

    但是如果variant的可选类型比较多的话,定义一个Visitor类会有助于按类型来执行操作,逻辑会显得清晰

    struct Visitor {
      double operator() (double d) { return d; }
      double operator() (int i) { return double(i); }
    };
    
    void show(std::variant<double, int> v) {
      std::cout >> std::visit(Visitor{}, v) << std::endl;
    }
    
    void test() {
      show(3.14);
      show(1);
    }
    
  • std::unique_ptr 关于智能指针的种种

    这个域名也是突发奇想买到的,作为unique-ptr.com,为了对得起这个名儿,那就说一下智能指针算了。

    前传。裸指针和auto_ptr

    裸指针就是高度自治(要自己管)的指针,在符合基本法(误)的前提下,使用裸指针的人可以为所欲为。裸指针的一个问题是,它有点不符合现代C++内存管理的指导思想,使用者必须对它的生命周期负责,否则有可能发生:

    • 内存泄漏(memory leak):如果new一大片内存但是由于代码复杂到头来忘了释放,就有可能耗尽内存资源;内存泄漏还有一种情况:如果申请内存做事情做了一半抛出了异常——代码跑到了exception的花括号里之后,原来申请的内存直接没办法拿到并且释放;
    • 悬挂指针(dangling reference):指针已经被删除了,但其内存仍然在使用中;另外还有双重删除(double delete)的问题,当程序的某部分要删除一个已经被删除的指针时,即可出现这种情况;还有一种,指针所指的内存已经失效了(比如出了作用域了),但是后面的代码还用着指针;

    应该是为了缓解这类问题,C++ 98标准搞出来了个智能指针:auto_ptr。相当于加了一个“管家”,在对象的内部保存那个裸指针,在管家析构的时候自动释放裸指针的内容,如果赋值的话那就把裸指针的所有权转交给新的管家。
    但是由于移动(move)语义是C++11才有的,auto_ptr在实现的时候,如果在两个智能指针对象之间作赋值(operator=)或者拷贝构造,它的参数都是非const类型的。为啥呢?因为它是会调用original_auto_ptr.release()方法拿到原始裸指针。
    这个有点无奈的操作是为了实现指针所指对象的“占有权”管理:两个auto_ptr不允许管理同一个目标裸指针。在“拷贝”的时候,相当于做了“主权移交”。

    Copying an auto_ptr copies the pointer and transfers ownership to the
    destination: both copy construction and copy assignment of auto_ptr
    modify their right hand arguments, and the “copy” is not equal to the
    original.
    https://en.cppreference.com/w/cpp/memory/auto_ptr

    这种操作并不符合常人对“拷贝“的理解,更像是”剪切“操作。且正是因为这个问题,切不可把auto_ptr放到stl容器里面,否则会得到各种编译错误。典型案例是这个:https://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers
    另外由于析构的时候是用delete,auto_ptr里也不能放指针数组(那种需要用delete[]).

    unique_ptr

    不同于auto_ptr,unique_ptr 的赋值运算符只接受典型地由 std::move 生成的右值。刚才说了,“右值”是C++11移动语义的产物,它比较好地解决了主权移交时的尴尬问题。这怎么说呢,一个智能指针的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程
    回顾看之前说的裸指针的两个问题:

    • 内存泄漏:由于析构函数里会delete管理的裸指针对象,所以在跑出对象作用域的时候,编译器会帮我们解决好问题,不用操心了;
    • 悬挂指针:缓解,但不能完全避免。

    一个例子是

    #include <iostream>
    #include <memory>
    #include <string>
        
    int main()
    {
      std::unique_ptr<std::string> str_ptr;
      {
          std::string tmp("bbbababa");
          str_ptr.reset(&tmp);
          std::cout << str_ptr.get() << std::endl;
          std::cout << *str_ptr << std::endl;
      }
      std::string tmp("xxxxxxx");
      std::cout << str_ptr.get() << std::endl;
      std::cout << *str_ptr << std::endl;
      return 0;
    }
    // Output:
    //   0x728562b4f060
    //   bbbababa
    //   0x728562b4f060
    //   xxxxxxx
    

    此外,数组的问题也通过特化std::unique_ptr<T[]> 得到了解决.
    可能有些同学会有性能方面的顾虑,觉得智能指针会比裸指针的操作性能差一点。坦白讲这个是有可能的,但是在实际使用的过程中,往往作profiling后会发现性能出现瓶颈的地方几乎不是智能指针。作为unique_ptr来讲,它不需要维护额外的状态,只是对象的创建和销毁要作一些特殊处理,平时使用的过程中完全可以当作裸指针来用。真正可能要担心性能问题的,是shared_ptr这一类允许共享所有权的智能指针。

  • 记一次gtest EXPECT_DEATH遭遇sigsegv的debug过程

    the crash

    实习生小哥上周突然发现单元测试在dev机器上跑不起来了,–verbose一看发现是gtest报错Failure death test:
    gtest_expect_death_failure.png

    现象其实是所有写了EXPECT_DEATH的单元测试都挂掉了,感觉问题并不简单~ 问了一下边上的几位最近代码有什么变动,一说是用了个新的计算库。

    gdb

    gdb一波,除看代码发现这个测试的原理是起一个子进程,然后把stderr写到一个临时文件里,子进程退出后,从临时文件里读取log信息,和预期值比对。发现第一现象是stderr没抓到,结果在找stderr为什么没抓到的邪路上走了很久,发现根本没产生stderr:/tmp下面写的文件,大小是0字节。

    然后继续gdb,由于是要跟踪子进程,要把选项打开:

    set detach-on-fork off
    set follow-fork-mode child

    run了一下,看到了一点端倪:其实在fork之后进程就sigsegv了,而且居然是在动态加载glibc里的chdir函数过程中挂的。查了一下,机器上的glibc已经至少一年没有动过了,因此基本排除是glibc自己有问题。

    然后开始各种翻来覆去的看,先看了几个local变量,发现一切都显得那么完美,根本没有任何内存值被搞烂的迹象。接着只能看挂掉的现场了,info registers看到寄存器的值如下:
    gdb_info_registers.png

    突然发现红框处的rsp地址有点太“整”了,google了一下,确认这个地址应该是个用户态栈地址的下界。然后查看一下当前的指令,发现是一条push %ebx,也就是说还准备压栈————挂掉的清晰一些了,其实是栈地址空间不够了,也就是传说中的stack overflow。这也是为什么gtest能够看到程序挂掉,但是拿不到预期的错误信息的原因。

    the stack overflow

    那么,为什么栈空间不够了呢?翻了一下frame 0里的$rsp值,发现在0x7fc0附近,和刚才看到顶上的$rsp=0x7000相差了4032,也就是估计这个程序栈总的大小在4kb。查了一下ulimit -s有32767,诶? 于是接着看代码,发现gtest-death-test.cc里的做法, ExecDeathTestSpawnChild()里写道

    • mmap一个stack,它的大小是getpagesize() // ==4KB
    • clone一个新的线程来跑测试例,用的这个新创建出来的stack

    真相大白了。解决方法是mmap的时候分配多一点的栈空间。至于为什么到了上周才触发,大概是因为.so又多了几个吧~

    后记

    当然,其实这个bug早就有fix了,只是我是后来通过搜”ExecDeathTestChildMain stack overflow”这个关键词才找到的。之前搜了半天EXPECT_DEATH没什么结果~
    相关的PR有:

    所以说thirdparty code还是要偶尔更新一下的呀~

  • Windows 10 HpyerV OpenWRT 软路由安装 + OpenClash 安装 全教程

    本教程详细说明如何在Windows10下经由Hyper-V技术安装OpenWRT软路由。物理机器配置如下:

    • Intel Celeron J1900
    • 4GB DDR3
    • 4口千兆网卡
      淘宝上这种配置的机器遍地都是,J1900对付软路由绰绰有余了,其实跑Win10浏览网页也不在话下~ 施工结果如下图:

    Win10_OpenWRT.JPG

    I. Hyper-V虚拟交换机配置

    安装前请确保你的 CPU 支持硬件虚拟化(Hyper-V)技术并且已通过BIOS启用

    启用Windows的Hyper-V功能

    1. 进入“控制面板”,然后依次点击 程序 – 程序和功能
    2. 点击「启用或关闭 Windows 功能」,打开 Windows 功能管理窗口,并勾选「Hyper-V」,点击“确定”
      win10_enable_hyperv.JPG

    配置虚拟交换机

    打开开始菜单,搜索或找到“Hyper-V管理器”,点击右侧栏里的“虚拟交换机管理器”链接。本人的物理机器有4个网口,配置的目标是网口1连入互联网,网口2网口3网口4连入下属设备。
    我们首先创建用于外部网络的虚拟交换机,这个交换机相当于你路由器的 WAN 接口,用于将路由器连接到外部网络:
    win10_hyperv_virtual_switch.JPG

    1. 点击“新建虚拟网络交换机”;
    2. 右侧给他起个名字,我是叫它ToExternal,或者随便叫什么“外部网络”也都行;
    3. 下面的“连接类型”选中“外部网络”,然后选择你的物理网口1对应的设备名称。注意这里需要勾选“允许管理操作系统共享此网络适配器”
    4. 点击“应用”
      而后需要增加一个内部网络适配器,是用于给虚拟化宿主机提供来自软路由的网络的。

    win10_hyperv_vswitch_internal.JPG

    接下来给余下的准备做路由器LAN口的网口2网口3网口4创建虚拟交换机。这边千万注意,选择“外部网络”并选定对应的物理网口后,不要勾选“允许管理操作系统共享此网络适配器”。这些网口,可以命名为LAN1, LAN2, LAN3之类。

    创建OpenWRT虚拟机

    虚拟机配置

    首先需要找一个x86_64的OpenWRT固件,这个网上多半可以搜索到。如果实在有点懒,可以找这里或者这里或者这里。请注意,部分固件是需要转换成Hyper-V能够识别的格式的,自行搜索下载“StarWind V2V Image Converter”这个软件转换成VHDX格式即可。

    下载完成后

    1. 打开 Hyper-V 管理器,点击「新建」-「虚拟机」,将会打开一个新的窗口。虚拟机的名称可以随意设置。
    2. 第二步会让选择虚拟机的代数,如果你下载的是uefi固件(一般名称里有uefi或gpt字样),可以选择第二代;否则谨慎起见选择第一代;
    3. 分配内存,个人分配了512MB固定内存,并开启动态内存允许分配到1GB。一般认为512M差不多了,甚至有说256都行了;
    4. 为虚拟机分配第一个网络适配器,选择之前创建的「内部网络」;这一步其实是向虚拟机增加了第一个网口,openwrt底下它会被默认分配为eth0;
    5. 为虚拟机创建硬盘,需要选择之前下载或者转换好的 OpenWRT 硬盘映像;

    完成初始化后,先不开机,点选右侧栏里的“设置”继续配置这个虚拟机:

    1. 点选“添加硬件”——网络适配器,将之前添加的所有外部网口依次加入,这边规定一下顺序,首先是那个通往外部互联网的网口1对应的,其次是剩下三个打算做LAN口的;注意这边添加网口的顺序影响到OpenWRT里eth的序号,按照这样设计,eth1对应的是我们接入互联网的WAN口,eth2-4对应的是未来的LAN口;
      win10_hyperv_openwrt_new_nic.JPG
    2. 如上图,点击所有添加的网口签名的“+”号——高级功能:勾选“启用MAC地址欺骗”。不开启此功能将导致之后软路由下的设备无法上网
      win0.JPG

    随后扩展硬盘

    1. 点击「IDE 控制器」下的「硬盘驱动器」,找到我们添加的 OpenWRT 虚拟硬盘,然后点击「编辑」;
    2. 在新的窗口中选择「扩展」,然后设置新的硬盘容量,推荐至少1GB
      tempsnip.jpg

    至此OpenWRT的配置基本告一段落,选择右侧栏里的“启动”即可启动虚拟机。启动后点击右侧栏的“连接”按钮可以进入虚拟机的shell,正常情况下进入虚拟机按下回车按钮,应该有OpenWRT图样。
    注意:OpenWRT默认的接入地址为192.168.1.1,如果光猫的路由器也是相同地址的话,会引起一些问题。这两者里有一个要改掉,OpenWRT这边可以通过修改/etc/config/network里lan口的内容实现(图中更改到了192.168.99.1):
    openwrt_change_lan_ip.jpg

    上图修改完之后,使用/etc/init.d/network restart命令可以重启网络服务。

    内部网络配置

    打开网络适配器设置,经由以下两种途径之一:

    • 依次点击 开始菜单 – 设置 – 网络和 Internet – 更改适配器选项;或,
    • 打开「网络和共享中心」,点击「更改适配器设置」

    找到名称为「vEthernet(内部网络)」的网络适配器(这边的“内部网路”是刚才给那个内部网路起的名字,比如我的叫Internal),然后点击「更改此连接的设置」以打开设置页面。
    点选「Internet 协议版本 4(TCP/IPv4)」,然后点击「属性」,然后按照下图配置:
    win10_internal_v_net.JPG

    请注意,这边192.168.X.1, 192.168.X.2X取决于刚才说的OpenWRT的内部IP地址。如果默认是192.168.1.???, 如果像我改过的话那就是192.168.99.???.

    点击「确定」保存所有设置。

    OpenWRT配置

    完成之前的步骤后,打开浏览器,并在地址栏中输入 192.168.1.1 (我是192.168.99.1)以打开 OpenWRT 管理页面. 默认的管理后台密码是:koolshare

    1. 依次点击侧边栏的 网络 – 接口;
    2. 你可以看到一个绿色的网络接口名称为「br-lan」。如果有两个多余的红色 WAN 和 WAN6 接口,请点击「删除」来删除它们;
    3. 修改LAN接口:在绿色的 br-lan 接口上点击「修改」,在新的页面中点击「物理设置」,然后将「接口」部分中选中eth0, eth2, eth3, eth4.如上文所述,这四个分别对应我们的内部网路、网口2,3,4。设置完成后点击下方的「保存并应用」,稍后页面将会自动跳转;
      openwrt-brlan.JPG
    4. 创建WAN接口:返回到接口管理页面后,点击「添加新接口」在新的页面中,为此接口名称设置为 WAN 或者你喜欢的名称,然后根据你的实际情况选择接口协议,如 PPPoE 或 DHCP 客户端。在「包括以下接口」部分勾选 eth1。
      openwrt-wan.JPG

    完成后点击「保存并应用」,进入详细设置。在「基本设置」中配合你的外部网络连接方式,如输入你的 PPPoE 账号等。我这边直接是宽带光猫拨号,所以很简单的用了“DHCP客户端”。然后点击「防火墙设置」,将新的接口防火墙区域设置为 WAN。WAN 区域使用红色作为标志。
    openwrt-wan-firewall.JPG
    全部完成后,点击「保存并应用」,将会返回到概览页面。正常情况下,能够连接互联网了:
    openwrt-interfaces.JPG

    修改后台密码

    维持默认的后台密码比较危险,可以用依次点击 系统 – 管理权,在“主机密码”里更换密码。

    OpenClash安装

    呵呵,其实这里是噱头。真正的安装步骤在https://github.com/vernesong/OpenClash 项目的README已经说的很清楚啦。下载地址在图中的”Release”底下也有。由于本网站的是“对大陆访客友好”的带有ICP备案的页面,所以这边只授人以渔。
    openclash-install.JPG
    下载其实可以直接在OpenWRT的命令行下面用wget命令操作,比如

    wget https://github.com/vernesong/OpenClash/releases/download/v0.37.2-beta/luci-app-openclash_0.37.2-beta_all.ipk

    随后使用opkg install luci-app-openclash_0.37.2-beta_all.ipk安装。如果有不懂的话,下面参考链接5里也有介绍。

    参考

    1. 如何通过 Hyper-V 部署 OpenWRT 软路由
    2. openwrt下如何修改默认ip地址
    3. Hyper-V 部署LEDE X64固件全教程
    4. 利用win10自带虚拟机hyper-v搭建软路由教程
    5. 合理的家庭网关方案 OpenClash, 并使用 SmartDNS 加速
  • 【5G已凉】 中国联通香港 Cuniq HK ONE 大灣區 大湾区 共享計劃 漫游评测

    ❌2021/05更新:由于官方已经推出5G合约计划,鄙人目前合约期内的现有套餐已经在5月初被取消5G权限,不论Android还是iOS都只能连上4G网络(尽管QCI还是挺高的,信号好的地方约140Mbps).经验教训就是,对方找你续约的给优惠的时候,务必三思🤡

    ⚠iPhone12用户提醒:截至20201117,由于联通香港并未提供IPCC文件更新,iPhone12系列亲测没有5G数据选项,无法在大陆地区连入5G网络。

    近日寻得正规渠道购入正规大湾区月费计划卡一张.
    GreatBayArea_Cuniq_Plan.JPG

    首先是选择的套餐,这次选择的是图片中间的5GB+6GB流量套餐,超过流量后仍有限速128kbps(非漫游为384kbps)的续命流量以至于不会完全断网。至于通话什么的本人是完全不在乎,目前是纯粹用来上网的。个人觉得相比Google Fi,他的优势有:

    • 资费便宜。与之对比的Fi 是$20 /mo + $10/GB for data,或者$70 /mo无限流量;
    • 接入归属地。由于是归属地接入的政策,所以Fi接入的IP地址是美国,而此卡接入是HK。遥远的地方自然附带延迟了;
    • 稳定性。由于是需要上台签合约的,所以个人觉得稳定性靠谱。Fi最近偶尔在群里有听到翻车的;

    开通激活体验

    此卡需要实名制并且签立合约,合约期为24个月。如果介意合约或是实名制的话,有一种稍贵的“月神卡”可以备选,不过性价比就稍差一些。即便身处内地,也是有办法完成任务的,无非是一些手续费罢了。鄙人收到卡后,卖方会通过网络发送合约的影印件,然后使用电子签名PDF回传文档。随后会有+852的电话过来再次核实个人资料,然后没多久就开卡了。激活并不需要到香港旅游一圈,体验很好。

    网速

    大陆漫游的是中国联通的网络。一般而言绝大多数网站是会有全球CDN的,与本地卡的网络延迟差别体验不大到。延迟最严重的是京东金融app,网页要几秒钟才能加载开。像是支付宝、微信之类的全然无问题。中国联通对于这类漫游卡的限速策略似乎很宽松,能够体验到与本地卡一样的网速(未经数据核实)。
    Cuniq_mainland_roaming_speedtest.jpg

    关于5G

    国内一般的4G套餐是能够使用5G网络的,默认限速300Mbps。但是很遗憾的是,至少今时(2020.4),这张Cuniq HK的卡是无法注册5G网络的,尚不知道未来会变成如何。

    2020.5: 香港联通目前准备开5G套餐了,作为试运行阶段,现有的4G套餐(大湾区之类)在内地部分地区(北京,广东)已有报告可以使用5G网络。题主目前还没试过,待更新测速结果~

    2020.6:杭州测试已经能连上5G网络,网速测试结果还是可以的

    cuniq_hk_5G.jpg

  • Python Facade模式 门面模式

    门面模式(Facade Design Pattern, 一称外观模式)属于结构型设计模式。结构型设计模式描述如何将对象和类组合成更大的结构。除了门面模式外,还有适配器模式、桥接模式、装饰器模式,他们都属于结构型设计模式。
    W3sDesign_Facade_Design_Pattern_UML.jpg
    这个模式有3个主要部分:

    • 门面(Facade):将一组复杂倒置系统封装起来,从而为外部世界提供一个舒适的外观;
    • 子系统(Subsystem):一些不同的子系统(上图中的Class1, Class2, Class3…),这些子系统让整个系统混杂在一起,难以使用;
    • 客户端(Client):通过门面提供的简单易用的接口,与门面进行交互,而避免接触复杂的各种子系统.

    案例

    假设你要在家中举办一场婚礼,必须预订一家酒店,与餐饮人员交代菜品、与布置人员敲定场地布置细节^%&*……头大的方法是自己安排这一切,或者可以去找一个婚礼管家,让他/她为你安排这一切。

    class EventManager:
      def arrange(self):
        self.hotelier = Hotelier()
        self.hotelier.bookHotel()
    
        self.florist = Florist()
        self.florist.setFlowerRequirements()
    
        self.caterer = Caterer()
        self.caterer.SetCuisine()
    
        self.musician = Musician()
        self.musician.setMusicType()
    

    上述Hotelier(), Florist(), Caterer(), Misicial()就是各种复杂的子系统。然后,对于你自己来说,只要这样就能轻松搞定:

    class You:
      def askEventMeneger(self):
        em = EventManager()
        em.arrange()
    
    you = You()
    you.askEventManager()
    

    最少知识原则

    最少知识原则指导我们减少对象之间的交互,它意味着:

    • 在设计系统时,对于创建的每个对象,都应该考察与之交互的类的数量以及交互的方式;
    • 避免许多紧密耦合的类;
    • 如果对系统中的任何一部分进行修改都可能导致系统其他部分被无意改变,这意味着系统退化,应该坚决避免。

    参考文献

    《Python设计模式(第二版)》by Chetan Giridhar

  • Typecho sitemap 插件 无法跳转sitemap.xml的解决办法

    大概跟很多人一样,我也用了bayunjiang的插件来生成sitemap。但是启用插件后,会发现https://unique-ptr.com/sitemap.xml 会提示404无法跳转。
    查证后发现这个跟伪静态是否开启有关,如果后台没有开启伪静态,那么需要跳转到https://unique-ptr.com/index.php/sitemap.xml 才能访问。最终的解决方案当然是开启伪静态,参考这篇文章,在后台设置一下就可以了。

    typecho_fake_static.png

  • pyprof2calltree — Python 性能分析 可视化

    性能分析用cProfile

    python -m cProfile -o output.perf your_script.py --your args
    

    然后可以安装pyprof2calltree

    pyprof2calltree -i output.perf -k
    

    记得系统里要装qcallgrind(windows)或者kcallgrind,不然会打不开生成好的log
    pyprof2calltree.jpg