精华内容
下载资源
问答
  • Widget模式

    千次阅读 2021-01-17 13:40:10
    Widget模式Widget模式是指借用Web Widget思想将页面分解成组件,针对部件开发,最终组合成完整的页面,Web Widget指的是一块可以在任意页面中执行的代码块,Widget模式不属于一般定义的23种设计模式的范畴,而通常将...

    Widget模式

    Widget模式是指借用Web Widget思想将页面分解成组件,针对部件开发,最终组合成完整的页面,Web Widget指的是一块可以在任意页面中执行的代码块,Widget模式不属于一般定义的23种设计模式的范畴,而通常将其看作广义上的架构型设计模式。

    描述

    模块化开发使页面的功能细化,逐一实现每个功能模块来完成系统需求,这是一种很好的编程实践,在简单模板模式实现的模板引擎的帮助下可以非常方便的完成这个实例,这将更适合多人团队开发,降低相互之间因为功能或者视图创建的耦合影响概率,组件的多样化也能够组建更加丰富的页面,同样也会让组件的复用率提高。

    实现

    // dom.js

    F.module("./dom", function() {

    return {

    g: function(id) {

    return document.getElementById(id);

    },

    html: function(id, html) {

    if (!html) return this.g(id).innerHTML;

    else this.g(id).innerHTML = html;

    }

    }

    });

    // template.js

    F.module("./template", function() {

    /***

    * 模板引擎,处理数据的编译模板入口

    * @param str 模块容器id或者模板字符串

    * @param data 渲染数据

    **/

    var _TplEngine = function(str, data) {

    // 如果数据是数组

    if (data instanceof Array) {

    // 缓存渲染模板结果

    var html = "";

    // 数据索引

    var i = 0;

    // 数据长度

    var len = data.length;

    // 遍历数据

    for (; i < len; i++) {

    // 缓存模板渲染结果,也可以写成

    // html += arguments.callee(str, data[i]) ;

    html += _getTpl(str)(data[i]);

    }

    // 返回模板渲染最终结果

    return html;

    } else {

    // 返回模板渲染结果

    return _getTpl(str)(data);

    }

    };

    /***

    * 获取模板

    * @param str 模板容器id,或者模板字符串

    **/

    var _getTpl = function(str) {

    // 获取元素

    var ele = document.getElementById(str);

    // 如果元素存在

    if (ele) {

    // 如果是input或者textarea表单元素,则获取该元素的value值,否则获取元素的内容

    var html = /^(textarea | input)$/i.test(ele.nodeName) ? ele.value : ele.innerHTML;

    // 编译模板

    return _compileTpl(html);

    } else {

    // 编译模板

    return _compileTpl(str);

    }

    };

    // 处理模板

    var _dealTpl = function(str) {

    // 左分隔符

    var _left = "{%";

    // 右分隔符

    var _right = "%}";

    // 显示转化为字符串

    return String(str)

    // 转义标签内的{%if(a<b)%}

    ->
    展开全文
  • 设计模式知识连载(47)---Widget模式: (Web Widget指的是一块可以在任意页面中执行的代码块)Widget模式是指借用Web Widget思想将页面分解成部件,针对部件开发,最终组合成完整的页面。 <hr>
    <body>
    
    
    <h3>设计模式知识连载(47)---Widget模式:</h3>
    <div>
        <p>
            (Web Widget指的是一块可以在任意页面中执行的代码块)Widget模式是指借用Web Widget思想将页面分解成部件,针对部件开发,最终组合成完整的页面。
        </p>
    </div>
    
    
    <hr>
    
    <!--===============模板种类开始===========-->
    <!-- 页面元素内容 -->
    <div id='demo_tag' class="template">
        <div id="tag_cloud">
            {% for(var i = 0; i < tagCloud.length; i++){
                var ctx = tagCloud[i];
            %}
            <a href="#" class="tag_item {% if(ctx['is_selected']) { %}selected{% } %}" title="{%=ctx['title']%}">
                {% =ctx['text'] %}
            </a>    
            {% } %}
        </div>
    </div>
    
    <!-- 表单元素内的内容 -->
    <textarea id="demo_textarea" class="template">
        <div id="tag_cloud">
            {%  for(var i = 0; i < tagCloud.length; i++) {
                var ctx = tagCloud[i]; %}
            <a href="#" class="tag_item 
                {% if(ctx['is_selected']){ %}
                selected
                {% } %} " title="{%=ctx['title']%}">
                {% =ctx[text] %}
            </a>
            {% } %}
        </div>
    </textarea>
    
    <!-- srcpt模板内容 -->
    <script type="text/template" id="demo_script">
        <div id="tag_cloud">
            {% for(var i = 0; i < tagCloud.length; i++){ 
                var ctx = tagCloud[i] ; %}
            <a href="#" class="tag_item 
                {% if(ctx['is_selected']){ %}
                selected
                {% } %}" title="{%=ctx['title']%}">{%=ctx['text']%}
            </a>
            {% } %}
        </div>
    </script>
    
    <!-- 自定义模板 -->
    <!--===============模板种类结束===========-->
    
    
    
    <script type="text/javascript">
    
        // 模拟数据
        var data = {
            tagCloud : [
                {is_selected : true, title : '这是一本设计模式书', text : '设计模式'},
                {is_selected : false, title : '这是一本HTML', text : 'HTML'},
                {is_selected : null, title : '这是一本CSS', text : 'CSS'},
                {is_selected : '', title : '这是一本javascript', text : 'javascript'},
            ]
        }
    
    
    
        /**
        *   案例一:视图模块化,方式一:初始
        */
    
        /*===============实现原理开始===========*/
        /**** 
        *   模板:
        *   <a href = '#' class = 'data-lang{%if(is_selected){%}selected{%}%}' value = '{%=value%}'>{%=text%}</a>
        ****/   
        /**** 
        *   数据:
        *   {is_selected : true, value : 'zh', text : 'zh-text'}
        ****/
        /**** 
        *   输出结果:
        *   <a href = '#' class = 'data-lang selected' value = 'zh'>zh-text</a>
        ****/
        /*===============实现原理结束===========*/
    
        /*===============模板引擎模块开始===========*/
        // 模板引擎模块
        F.module('lib/template', function() {
    
            /***
            *   模板引擎,处理数据的编译模板入口
            *   @param  str     模块容器id或者模板字符串
            *   @param  data    渲染数据
            **/ 
            var _TplEngine = function(str, data) {
                // 如果数据是数组
                if(data instanceof Array) {
                    // 缓存渲染模板结果
                    var html = '' ;
                    // 数据索引
                    var i = 0 ;
                    // 数据长度
                    var len = data.length ;
                    // 遍历数据
                    for(; i < len; i++) {
                        // 缓存模板渲染结果,也可以写成
                        // html += arguments.callee(str, data[i]) ;
                        html += _getTpl(str) (data[i]) ;
                    }
                    // 返回模板渲染最终结果
                    return html ;
                }else {
                    // 返回模板渲染结果
                    return _getTpl(str)(data) ;
                }
            } ;
            /***
            *   获取模板
            *   @param  str 模板容器id,或者模板字符串
            **/
            var _getTpl = function(str) {
                // 获取元素
                var ele = document.getElementById(str) ;
                // 如果元素存在
                if(ele) {
                    // 如果是input或者textarea表单元素,则获取该元素的value值,否则获取元素的内容
                    var html = /^(textarea | input)$/i.test(ele.nodeName) ? ele.value : ele.innerHTML ;
                    // 编译模板
                    return _compileTpl(html) ;
                }else {
                    // 编译模板
                    return _compileTpl(str) ;
                }
            } ;
            // 处理模板
            var _dealTpl = function(str) {
                // 左分隔符
                var _left = '{%' ;
                // 右分隔符
                var _right = '%}' ;
                // 显示转化为字符串
                return String(str)
                    // 转义标签内的<如:<div>{%if(a&lt;b)%}</div> -> <div>{%if(a<b)%}</div>
                    .replace(/&lt;/g, '<')
                    // 转义标签内的>
                    .replace(/&gt;/g, '>')
                    // 过滤回车符,制表符,回车符
                    .replace(/[\r\t\n]/g, '')
                    // 替换内容
                    .replace(new RegExp(_left + '=(.*?)' + _right, 'g'), "',typeof($1) === 'undefined' ? '' : $1, '") 
                    // 替换左分隔符
                    .replace(new RegExp(_left, 'g'), "');")
                    // 替换右分隔符
                    .replace(new RegExp(_right, 'g'), "template_array.push('") ;
    
            } ;
            /***
            *   编译执行
            *   @param  str 模板数据
            **/ 
            var _compileTpl = function(str) {
                // 编译函数体
                var fnBody = "var template_array=[];\nvar fn=(function(data){\nvar template_key='';\nfor(key in data){\ntemplate_key +=(''+key+'=data[\"'+key+'\"];');\n}\neval(template_key);\ntemplate_array.push('"+_dealTpl(str)+"');\ntemplate_key=null;\n})(templateData);\nfn=null;\nreturn template_array.join('') ;" ;
                // 编译函数
                return new Function('templateData',  fnBody) ;
            } ;
    
            // 返回
            return _TplEngine ;
        }) ;
    
        /*######fnBody分析开始######*/
        // "// 声明template_array模板容器组
        // var template_array = [] ; \n
        // // 闭包,模板容器组添加成员
        // var fn = (function(data) { \n
        //  // 渲染数据变量的执行函数体
        //  var template_key = '' ; \n
        //  // 遍历渲染数据
        //  for(key in data) { \n
        //      // 为渲染数据变量的执行函数体添加赋值语句
        //      template_key += ('' + key + '=data[\"'+ key +'\"] ;') ; \n 
        //  } \n
        //  // 执行渲染数据变量函数
        //  eval(template_key) ; \n
        //  // 为模板容器组添加成员(注意,此时渲染数据将替换容器中的变量)
        //  template_array.push('"+ _dealTpl(str) +"') ; \n
        //  //释放渲染数据变量函数
        //  template_key = null ; \n
        // // 为闭包传入数据   
        // })(templateData) ; \n
        // // 释放闭包
        // fn = null ; \n
        // // 返回渲染后的模板容器组,并拼接成字符串
        // return template_array.join('') ;"
        /*######fnBody分析结束######*/
    
        // 使用时,只需引用模板引擎模块依赖就可以
        F.module(['lib/template', 'lib/dom'], function(template, dom) {
            // 服务器端获取到data数据逻辑
            // 创建组件视图逻辑
            var str = template('demo_script', data) ;
            dom.html('test', str) ;
            // 组件其他交互逻辑
        }) ;
        /*===============模板引擎模块结束===========*/
    
        /*===============模板种类结束===========*/
        // 自定义模板
        var demo_tpl = [
            '<div id="tag_cloud">',
            '{% for(var i = 0; i < tagCloud.length; i++){', 
                ' var ctx = tagCloud[i]; %}',
                '<a href="#" class="tag_item {% if(ctx["is_selected"]){ %}',
                'selected',
                '{% } %}" title="{%=ctx["title"]%}">',
                '{%=ctx["text"]%}',
                '</a>',
            '{% } %}',
            '</div>'
            ].join('') ;
        /*===============模板种类结束===========*/
    
    
    </script>    
    
    
    
    
    </body>
    
    展开全文
  • 在android UI设计,几乎所有的widget和布局类都依靠这两个类。 组合模式,Composite Pattern,是一个非常巧妙的模式。几乎所有的面向对象系统都应用到了组合模式。 通过本文将让你学会软件开发中的”何为树形结构”...
  • 在android UI设计,几乎所有的widget和布局类都依靠这两个类。 组合模式,Composite Pattern,是一个非常巧妙的模式。几乎所有的面向对象系统都应用到了组合模式。 1.意图 将对象View和ViewGroup组合成树形结构以...
  • 观察者模式,又称发布订阅模式,是一种行为设计模式——你可以定义一种订阅机制,可在对象事件发生时通知多个 观察 该对象的其他对象。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监...

    观察者模式,又称发布订阅模式,是一种行为设计模式——你可以定义一种订阅机制,可在对象事件发生时通知多个 观察 该对象的其他对象。

    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

    这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

    从定义中,不难发现,观察者被观察者 / 发布者 是这个模式中最重要的组成元素。

    微信的公众号可以被视为生活中最典型的观察者模式的例子。如果你订阅了「Flutter社区」,每当 Flutter 社区发布文章时,就会给你及其他订阅者推送这个消息,这其中你就是 观察者,公众号「Flutter社区」就是 被观察者 (Observable) 或发布者 (Subject)

    观察者模式经常被应用在这类事件处理系统中,从概念上理解,被观察者也经常被称作是 事件流 (stream of events) 或者说是 事件流的来源 (stream source of events),而观察者相当于 事件接收器 (sinks of events)

    同时,观察者模式也是实现 响应式编程 的基础,RxDart、EventBus 等库都是观察者模式下的产物。

    面向对象

    面向对象中,观察者和和发布者 (被观察者) 分别对应两个类 (Observer 和 Subject) 的对象。

    观察者模式 UML 图,图源维基百科

    发布类 (Subject) 中通常会有提供给每个对象订阅或取消订阅发布者事件流的 订阅机制,包括:

    1. 一个用于存储订阅者对象引用的列表成员变量;

    2. 几个用于添加或删除该列表中订阅者的公有方法。

    // 被观察者
    class Subject {
      List<Observer> _observers;
      Subject([List<Observer> observers]) {
        _observers = observers ?? [];
      }
    
      // 注册观察者
      void registerObserver(Observer observer) {
        _observers.add(observer);
      }
      
      // 解注册观察者
      void unregisterObserver(Observer observer) {
        _observers.remove(observer)
      }
    
      // 通知观察者
      void notifyobservers(Notification notification) {
        for (var observer in _observers) {
          observer.notify(notification);
        }
      }
    }
    

    此时,每当事件发生,它只需遍历订阅者并调用其对象的特定通知方法即可 (如上面代码中的 notifyobservers 方法) 。

    实际应用中,一个发布者通常会对应多个订阅者,且发布者与订阅者应当遵循面向对象的开发设计原则,因此:

    1. 为了避免耦合,订阅者们必须实现同样的接口;

    2. 发布者仅通过该接口与订阅者交互,接口方法可以声明参数, 这样发布者在发出通知时就能传递一些上下文数据 (如下面代码中的 notification 对象) 。

    // 观察者
    class Observer {
      String name;
      
      Observer(this.name);
    
      void notify(Notification notification) {
        print("[${notification.timestamp.toIso8601String()}] Hey $name, ${notification.message}!");
      }
    }
    

    这样,我们可以得出如下这样用 Dart 语言实现的观察者模式了,下面是一个简单的应用:

    // 具体的被观察者 CoffeeMaker
    // 每当 Coffee 制作完成发出通知给观察者。
    class CoffeeMaker extends Subject {
      CoffeeMaker([List<Observer> observers]) : super(observers);
      
      void brew() {
        print("Brewing the coffee...");
        notifyobservers(Notification.forNow("coffee's done"));
      }
    }
    
    void main() {
      var me = Observer("Tyler");
      var mrCoffee = CoffeeMaker(List.from([me]));
      var myWife = Observer("Kate");
      mrCoffee.registerObserver(myWife);
      mrCoffee.brew();
    }
    

    这里的 CoffeeMaker 继承自 Subject,作为一个具体的发布类,brew() 方法是其内部,每当咖啡制作完成后,用于通知其他各个观察者的方法。上面代码中,我们在 mrCoffee 这台咖啡机上注册了 myWife 这一个观察者,mrCoffee.brew(); 触发后,myWife 内部的 notify  方法就会被调用。

    观察者模式很好的实现了他们两者之间发布订阅的关系,在实际应用中,被观察者正在处理的事件很可能是异步的,而作为观察者不必显示的去阻塞等待事件的完成,而是由被观察者通知,当事件完成后,再将事件主动地「推」给关心这个事件的观察者。与之相对的,有一类观察者也会使用后台线程时刻轮询地监听着其关心的主题事件,这个话题我们暂不展开。

    观察者模式使用不慎的话,也很容易出现传说中的 失效监听器 问题,导致内存泄漏,因为在基本实现中,被观察者依然持有观察者的强引用,如果事件中途,被观察者已经不存在时或不再关心此事件,就会导致观察者无法被回收,因此,我们在这种情况下应当在被观察中做好取消订阅的机制,及时释放无用的资源。

    Dart

    Stream 可以被看作是 Dart 语言原生支持的观察者模式的典型模型之一,它本身是 Dart:async 包中一个用于异步操作的类,响应式编程库 RxDart 也是基于 Stream 封装而成的。

    从概念上讲,我们可以将 Stream 看做是一个可以连接两端的传送带,作为开发者,我们可以在传送带的一端放入数据,Stream 就会将这些数据传送到另一端。

    和现实中的情况类似,如果传送带的另一端没有人接受数据,这些数据就会被程序丢弃,因此,我们通常会在传送到尾端安排一个接收数据的对象,在响应式编程中,它被称为数据的观察者。

    如果说上文 Dart 面向对象中,观察者和被观察者两者的关系是在尽量保持低耦合的情况下而形成的,相对独立。那么在响应式编程中,它们的关系就是变得更加紧密的 上游与下游 的关系。

    因为 Stream,顾名思义,就是「流」的含义,被观察者在流的入口产生事件,观察者则在流的出口等待数据或事件的到来。

    在这套流程里,观察者的 订阅 与被观察者的 事件发布 等一系列操作都直接在 Stream 或者说是框架内部完成的。

    Dart 中,我们可以使用 StreamController 来创建流:

    var controller = new StreamController<int>();
    
    controller.add(1); // 将数据放入流中
    

    如上面代码所示,创建 StreamController 时必须指定泛型类型来定义可以加入 Stream 的数据对象,上面的 controller 可以接受 int 类型的数据,我们使用它的 add 方法就可以将数据放入到它的传送带中。

    如果我们直接运行上面的两行代码,最终并不会不到任何结果,因为我们还没有为传送带设置接收数据的对象:

    var controller = new StreamController<int>();
    
    controller.stream.listen((item) => print(item)); // 数据观察者函数
    
    controller.add(1);
    controller.add(2);
    controller.add(3);
    

    上面的代码中,我们通过调用 StreamController 内部的 stream 对象的 listen 方法,就可以为 controller 对象添加监听这个 Stream 事件的观察者,这个方法接受一个回调函数,这个回调函数又接受一个我们在 new StreamController<int>() 泛型中声明的数据对象作为参数。

    这时,每当我们再次通过 add 方法将数据放入传送带后,就会通知观察者,调用这个函数,并将传递的数据打印出来:

    1
    2
    3
    

    另外,我们也可以使观察者在某个时间段后停止监听 Stream 中传递的数据,在上面代码中的 listen 函数会返回一个  StreamSubscription 类型的订阅对象,当我们调用它的 .cancel() 后就会释放这个观察者,不再接收数据:

    var controller = new StreamController<String>();
    
    StreamSubscription subscription = controller.stream.listen((item) => print(item));
    
    controller.add(1);
    controller.add(2);
    controller.add(3);
    
    await Future.delayed(Duration(milliseconds: 500));
    
    subscription.cancel();
    

    Flutter

    ChangeNotifier

    ChangeNotifier 大概是 Flutter 中实现观察者模式最典型的例子了,它实现自 Listenable,内部维护一个 _listeners 列表用来存放观察者,并实现了 addListenerremoveListener 等方法来完成其内部的订阅机制:

    class ChangeNotifier implements Listenable {
      LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
    
      @protected
      bool get hasListeners {
        return _listeners!.isNotEmpty;
      }
      
      @override
      void addListener(VoidCallback listener) {
        _listeners!.add(_ListenerEntry(listener));
      }
    
      @override
      void removeListener(VoidCallback listener) {
        for (final _ListenerEntry entry in _listeners!) {
          if (entry.listener == listener) {
            entry.unlink();
            return;
          }
        }
      }
    
      @mustCallSuper
      void dispose() {
        _listeners = null;
      }
    
      @protected
      @visibleForTesting
      void notifyListeners() {
        if (_listeners!.isEmpty)
          return;
        final List<_ListenerEntry> localListeners = List<_ListenerEntry>.from(_listeners!);
        for (final _ListenerEntry entry in localListeners) {
          try {
            if (entry.list != null)
              entry.listener();
          } catch (exception, stack) {
            // ...
          }
        }
      }
    }
    

    在实际使用时,我们只需要继承 ChangeNotifier 便能具备这种订阅机制,如下这个 CartModel 类:

    class CartModel extends ChangeNotifier {
      final List<Item> _items = [];
    
      UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
    
      int get totalPrice => _items.length * 42;
    
      void add(Item item) {
        _items.add(item);
        notifyListeners();
      }
    
      void removeAll() {
        _items.clear();
        notifyListeners();
      }
    }
    

    CartModel 内部维护一个 _items 数组,addremoveAll 方法时提供给外部操作该数组的接口,每当 _items 改变则会调用 notifyListeners() 通知它的所有观察者。

    ChangeNotifier 作为 flutter:foundation 中最基础的类,不依赖其他任何上层的类,测试起来也非常简单,我们可以针对 CartModel 做一个简单的单元测试:

    test('adding item increases total cost', () {
      final cart = CartModel();
      final startingPrice = cart.totalPrice;
      cart.addListener(() {
        expect(cart.totalPrice, greaterThan(startingPrice));
      });
      cart.add(Item('Dash'));
    });
    

    这里,当我们调用 cart.add(Item('Dash')); 后,就是会触发观察者函数的调用,实现一种由数据的改变驱动事件执行的机制。

    Flutter 应用中最传统的状态管理方案是使用有状态 widget 的 setState 的方法,这种方式暴露出来的问题是,大型应用中的 widget 树会非常复杂,每当状态更新调用 setState 时,则会牵一发而动全身,重建所有子树,使性能大打折扣。

    那么,当将 ChangeNotifier 观察者模式应用在状态管理方案中时,便能解决这个问题。设想让每一个最小组件充当观察者,观察应用的状态,每当状态改变时即驱动该局部小组件更新,是不是就能达到这种目的。我们常用 provider 库就应用了这个原理。

    provider 内部提供了一个 ChangeNotifierProvider widget,可以向其子组件暴露一个 ChangeNotifier 实例 (被观察者) :

    void main() {
      runApp(
        ChangeNotifierProvider(
          create: (context) => CartModel(),
          child: const MyApp(),
        ),
      );
    }
    

    在子组件中,只需要使用 Consumer widget 注册观察者组件,就能接收到 CartModel 内部数据更新的通知:

    return Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text("Total price: ${cart.totalPrice}");
      },
    );
    

    这里,使用 Consumer 必须指定要观察的 ChangeNotifier 类型,我们要访问 CartModel 那么就写上 Consumer<CartModel>,builder 最为 Consumer 唯一一个必要参数,用来构建展示在页面中的子组件。

    ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当调用 CartModel 的  notifyListeners() 方法时,所有相关的 Consumer widget 的 builder 方法都会被调用。) ,重建子树,达到局部更新状态的目的。

    Navigator

    路由是在 Flutter 应用中常去讨论的话题,在整个应用运行过程中,路由操作也都需要被时刻关注着,它是我们了解用户行为的一种有效的方式。Flutter 提供了一套很方便的观察者模式的模型帮助我们实现这个功要求。

    Flutter 中每个 Navigator 对象都接受一个 NavigatorObserver 对象的数组,在实际开发过程中,我们可以通过根组件 MaterialApp (或 CupertinoPageRoute)  的 navigatorObservers 属性传递给根 Navigator 组件,用于观察根 Navigator 的路由行为,这一组 NavigatorObserver 对象就是一系列的路由观察者。

     Widget build(BuildContext context) {
        return new MaterialApp(
          navigatorObservers: [new MyNavigatorObserver()],
          home: new Scaffold(
            body: new MyPage(),
          ),
        );
      }
    

    路由观察者们统一继承自 RouteObserver,范型类型为 PageRoute,这时,它就能监听 CupertinoPageRoute 和 MaterialPageRoute 两种类型的路由了:

    class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {
    
      // 监听导航器的 push 操作
      @override
      void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
        super.didPush(route, previousRoute);
        if (previousRoute is PageRoute && route is PageRoute) {
          print('${previousRoute.settings.name} => ${route.settings.name}');
        }
      }
    
      // 监听导航器的 replace 操作
      @override
      void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
        super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
        if (newRoute is PageRoute) {
          print('${oldRoute.settings.name} => ${oldRoute.settings.name}');
        }
      }
    
      // 监听导航器的 pop 操作
      @override
      void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
        super.didPop(route, previousRoute);
        if (previousRoute is PageRoute && route is PageRoute) {
          print('${route.settings.name} => ${previousRoute.settings.name}');
        }
      }
    }
    

    在我们做实际路由操作,调用 Navigatorpoppush 等方法时,就会按照惯例遍历调用这些观察者对象对应的方法:

     Future<T> push<T extends Object>(Route<T> route) {
      // ...
      for (NavigatorObserver observer in widget.observers)
        observer.didPush(route, oldRoute);
     // ...
    }
    

    这样,观察者模式在 Flutter 路由中又完成了这个非常重要的任务。

    本文小结

    本文内容到这里就结束了,观察者模式的场景例子数不胜数,在实际开发中,我们也会经常需要使用到,但我们要记住的是设计模式的运用并不是套用模版,而是要根据实际场景找到最合适的解决方案。

    对于行为型模式来说,观察者模式将被观察者与观察者这两件事物抽象出来,实现了代码上的解藕,在实际场景中,观察者可能是关心某种状态的组件,监听某个事件的监听器等等,整体的设计也会变得更加直观,希望大家能在以后的开发中多多使用。

    拓展阅读

    • Flutter开发之旅从南到北》—— 第8章 路由管理 & 第9章 状态管理

    • 观察者模式 wikipedia
      en.wikipedia.org/wiki/Observer_pattern

    • Design Patterns in Dart
      scottt2.github.io/design-patterns-in-dart/observer/

    • Dart | 什么是Stream
      juejin.cn/post/6844903686737494023

    • 简单的应用状态管理
      flutter.cn/docs/development/data-and-backend/state-mgmt/simple

    关于本系列文章

    Flutter / Dart 设计模式从南到北 (简称 Flutter 设计模式) 系列内容由 CFUG 社区成员、《Flutter 开发之旅从南到北》作者、小米工程师杨加康撰写并发布在 Flutter 社区公众号和 flutter.cn 网站的社区教程栏目。

    本系列预计两周发布一篇,着重向开发者介绍 Flutter 应用开发中常见的设计模式以及开发方式,旨在推进 Flutter / Dart 语言特性的普及,以及帮助开发者更高效地开发出高质量、可维护的 Flutter 应用。

    展开全文
  • Qt与设计模式(全)

    千次阅读 多人点赞 2019-10-27 17:18:14
    为什么要学习设计模式?创建型模式工厂模式(Factory Pattern)抽象工厂模式(Abstract Factory Pattern)单例模式(Singleton Pattern)建造者模式(Builder Pattern)原型模式(Prototype Pattern)结构型模式...

    • 前言

    声明

    本文只代表作者的观点和立场,在实际的实践过程中会受到主观意识引导,难免会和实际工作的方法论不相符合,出现错误与纰漏,还望各位读者阅读本文时多多海涵和不吝赐教。

    为什么要学习设计模式?

    先分析一下设计模式在项目流程中的作用,一个正常的公司项目大体流程一般为如下:

    1. 业务需求
      从项目的产生,无论是甲方还是乙方,军工还是民用,项目的来源都不是凭空捏造的,必定有其遇到的问题。一般产品经理会跟客户沟通拟定项目的需求。甲方相对于专业的开发人员来说,通常为不懂技术或了解不多,一般产品经理会抽象甲方的业务逻辑形成文稿。最终和家甲方商讨形成《需求说明书》移交项目经理进行项目评估。
    2. 项目评估
      通常项目经理针对《需求说明书》针对公司现有的人员和资源进行项目评估,其中还有项目架构师的参与(通产项目经理和架构师为同一人),最终确定项目的大体方向,比如效率高采用什么框架和语言实现,涉及的人员和时间周期,最终产生《项目计划》《技术服务合同》等。同时和甲方签订合同。
    3. 需求分析
      技术层面的需求分析,按照《项目计划》项目经理、技术经理、架构师(通常三个角色为一人)相关技术人员同时展开项目架构相关会议,从整体架构和各个模块推导形成完整的敲定《项目方案》,通常为多个实现方案,取其最优方案作为优先执行。这里涉及软件的整体框架和模块组合,其中运用大量的软件设计模式进行相关性结构保证软件开发原则,例如:OA软件系统开发设计九大原则。
    4. 开发产品
      最为广大搬砖工最关心的一环,上面的需求分析不会对每个class的关系和结构进行细分,一个好的程序员应该具备的素质,模块任务分配到手,首先需要抽象业务需求,我这个模块是干嘛的,在整个系统中有什么作用。思考完这些问题,你会发现一些隐藏的坑,可能载现目前体会不到,随着代码的不断堆积,你会发现代码越来越难敲,有时候甚至想推掉重新来过,载这里亲身体会,特别是存在界面和数据业务上,如果没有采用MVC模式,你的数据传递和界面没有分离,那么基本告别迭代。有人说大公司模块细分你学不到什么东西,可能我没有多大的体会,只有自己愿不愿意去跟深层次的了解场景罢了。多想想你所涉及的模块内的场景,有些东西是在需求分析中所不能体现的,比如你的同事用了原型模式,让自己的代码可拆卸组合,而你没有分离,假设存在V2版本,谁的开发效率会更高,同事准时下班,而你还在苦逼的重构代码,还有运用合理的设计模式将会对代码质量有飞跃式提升,例如界面N个贴图Qlable,那么工厂模式就用起来啊,你在你的QWidget中定义一堆Qlabel,明眼人一看代码:“代码真菜”。
    5. 交付产品
      交付产品可能遇到的问题,甲方:“你这个界面可不可以加个类似某音的商城跳转按钮?就加个按钮而已没那么难吧”。你:“阿玛尼”。如果你是国际大厂没的说了签新合同,可惜,有多少人进了大厂?设计模式让你的代码解耦,更容易更改。
    6. 后期维护
      有多少人看自己一年前的代码都不忍心看?何况后期维护很有可能不只一年后期维护也牵扯代码查找与更改,这便是体现设计模式的优越性。
    7. 个人跳槽与项目移交
      设计模式相当于业内的代码潜规则,好的代码体现在移交项目注释和设计模式上面,你所写的代码就是你的脸,接手的工程师看到代码言简意赅,设计模式可以减少写代码的行数,通过潜规则更清楚表述自己的代码,看不懂我的代码?That is impossible!你怕是要去学学软件设计模式了。
    8. 业务终止
      到这里一般都是项目完全脱离。

    综上所诉,软件设计模式带来的好处就是,让你的代码有前瞻性,有限的程度上符合日益变更的需求,少些废代码,理解容易(业内潜规则),甩锅与装逼(这当然是说笑了)!!!

    • 创建型模式

    创建者模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

    工厂模式(Factory Pattern)

    我的第一个项目就是实现贴图的QListWidget其中不免含有item中元素的各种操作,比如单击事件双击和事件移动到上面会出现气泡什么的,下面以QQ群成员列表为例
    QQ群成员列表
    这里看到成员2816个人,每人为每一项也就存在2816个项,每一项有三个元素,分别头像QLable,名称QLabel ,身份QLabel,不妨把Item作为工厂生产的产品,那么我们通过QString(头像路径),QString(成员名称),QString(身份标示)为一组生成不同产品(这里的不同指的是界面显示,而非class类的不同),下面我用伪代码说明。

    typedef struct ItemMess
    {
    	QString ImgPath;
    	QString name;
    	QString idImgPath;
    }
    
    QVector<ItemMess> MessContainer=
    {
    	{"Path::一去二三里的头像","西安-一去二三里","Path::群主的图片"};
    	{"Path::北京-青春的头像","北京-青春","Path::管理员的图片"};
    	{"Path::上海-叶海的头像","上海-叶海","Path::管理员的图片"};
    	......;
    }  //这里定义一些初始化变量,然而在现实的需求中,肯定是来源数据库查询或者文件。
    
    class Factory
    {
    public:
    	Factory(){}
    	QItemList generateItem(Vector<ItemMess>  _itemMess)
    	{
    		QItemList itemlist;
    		foreach( auto var:_itemMess)
    		{		
                QItem  pointer = new QItem()                                           //构造新项
    	   		pointer.setHeadImg(QLabel::loadImg(var.ImgPath));    //设置新项的头像图片
    			pointer.setName(QLabel::loadText(var.name));				//设置新项的昵称
    			pointer.setIdImg( QLabel::loadImg(var.name));				//设置新项的身份图片
    			
    			itemlist.append(pointer);	//把新项添加到列表中
    		}
    	}
    }
    
    int main()
    {
     	Factory f;
      	QListWidget w;
      	w.setItemList( f.generateItem(MessContainer));
      	w.show();
      	return 0;
    }
    

    上面 QItemList Factory::generateItem(Vector _itemMess) 把输入数据作为当前QItemList 生成的依赖信息,生成最终的产品QItemList 然后返回数据项设置到QListWidget。其中Factory 扮演的角色如下:

    提交清单
    执行生产
    分拣信息,生成零件和组装成品
    产品清单
    Factory
    生产线
    交付产品

    抽象工厂模式(Abstract Factory Pattern)

    开始剖析抽象工厂模式,不得不提到实际项目中肯定会遇到的几点矛盾:
    1.刚开始框架铺开,实现代码的前期,会遇到class实现的功能不明确。
    2.实现代码存在多种泛化。
    3.既定接口实现class,通常用在C++插件上(常说的面向接口编程)

    实现抽象工厂的难点:使用场景有限,需要考虑的class兼容性,纯虚函数限制,继承抽象工厂模式的class tree都需要实现相关的方法(无论是继承树的哪一层级都需要重新实现),推荐如果出现第3种矛盾可以采用,如果想要更加深入的了解抽象工厂模式,建议查看我的另外一篇文章QtPlugin(C++跨平台插件开发),特别是代码的多种泛化,千万千万最好别用它,不然你会绞死在自己未定义的错误上面(当然)。当然有更好的使用抽象工厂模式也欢迎一起探讨,对于IT新人可能不太容易理解抽象工厂模式,于是斟酌了一下,就多写一些话,如果看不太明白的话这这个设计模式可以直接跳过,同时抽象工厂模式的不同场景,所以很多存在相似的地方,说明得详细一点也无伤大雅。

    • 接口抽象

    #ifndef QABSTRACTBUTTON_H
    #define QABSTRACTBUTTON_H
    
    #include <QtGui/qicon.h>
    #include <QtGui/qkeysequence.h>
    #include <QtWidgets/qwidget.h>
    
    QT_BEGIN_NAMESPACE
    class QButtonGroup;
    class QAbstractButtonPrivate;
    
    class Q_WIDGETS_EXPORT QAbstractButton : public QWidget
    {
        Q_OBJECT
        
    	more .....
    	
    protected:
        void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE = 0;
        virtual bool hitButton(const QPoint &pos) const;
        virtual void checkStateSet();
        virtual void nextCheckState();
        
    	more .....
    };
    QT_END_NAMESPACE
    #endif // QABSTRACTBUTTON_H
    

    直接上Qt自带的QAbstractButton文件可能稍微好理解一些,所有泛化的button class都继承QAbstractButton。相信在Qt官方组编写Button怎么实现不同类型的按钮也思考了这些问题,但是尽量会思考得完整一些。因为抽象嘛,到时候继承抽象类得实现一堆代码,能够略过抽象落到实现方法上是最好的。

    QAbstractButton::paintEvent = 0
    QPushButton::paintEvent = overload
    QToolButton::paintEvent = overload
    QCheckBox::paintEvent = overload
    otherButton::paintEvent = overload

    上面是抽象类抽象方法的实现结构图,按照道理来说,我们并不知到我们要实现什么button的界面绘制效果(这里接触过Qt的同僚应该知道,点击了不同类型button在界面上的显示是不一样的),我们预留一个(当然可以多个)接口作为不同button自己的内部实现paintEvent()。这就是抽象到实例化方法,而QAbstractButton就是一个抽象工厂。

    • 面向接口编程

    前段时间有一个伙计我现在实现了实例化的class,要怎么去用上抽象工厂模式呢,我的回答是知道实例没必要再反着推抽象了。如果是为了统一接口反过来用过面向接口编程这种抽象工厂也是没有错的。面向接口编程为了切合实际,我们首先先假定一个场景。场景如下:
      我们想要做一款摄像头识别文字然后显示到任意的显示设备上。为了扩展摄像头的不同型号和和显示设备的不同型号,首先我们肯定过不会先去实现摄像头和显示设备的内部处理采集到的数据流代码。

    
    enum DeviceType    //设备类型枚举标记
    {
        DT_Camera = 0,
        DT_Display
    };
    
    class AbstractDevice  //通常接口会是叫做 class InterfaceDevice  这里定义成抽象工厂的名称实则是一样的
    {
    public:
        virtual bool open() = 0;
        virtual bool close() = 0;
        virtual QByteArray readAll() = 0;
        virtual DeviceType getDeviceType() = 0;
        QString deviceName(){return m_StrDeviceName;}
    private:
        QString m_StrDeviceName;  //不一定抽象工厂里面不能定义其他实际变量,因为经常继承可以会用到
    };
    /*定义了一个抽象的设备类型,假设我们不知道我们的设备到底是什么样的设备(包括摄像头和显示设备),而枚举类型的标记中没有
    DT_Camera = 0,
    DT_Display
    整个DeviceType都为空,
    */
    
    class AbstractCamera:public AbstractDevice //抽象摄像头类型
    {
    public:
       DeviceType getDeviceType(){return DeviceType::DT_Camera;} //标记设备为摄像头
    private:
    
    };
    
    class AbstractDisplay:public AbstractDevice
    {
    public:
        DeviceType getDeviceType(){return DeviceType::DT_Display;}
    };
    
    
    class Camera_1080P:public AbstractCamera //最终实现1080p的摄像头
    {
    public:
        Camera_1080P(){}
        bool open(){}
        bool close(){}
        QByteArray readAll(){}
    
    };
    
    class Display_LED:public AbstractDevice //最终是实现LED的显示屏
    {
    public:
        Display_LED(){}
        bool open(){}
        bool close(){}
        QByteArray readAll(){}
    };
    

    为了加深映像我代码中用两次抽象class继承,你会发现载上面的class中除了Camera_1080P和Display_LED我都没有实现构造函数,因为接口的编写都是抽象的,不允许实际的构造。而继承到最终的实际设备中,我们实现了所有的抽象方法,从而产生了置顶而下的构造。对外的接口:
     bool AbstractDevice::open();
     bool AbstractDevice::close();
     QByteArray AbstractDevice::readAll();
     DeviceType AbstractDevice::getDeviceType();
     QString AbstractDevice::deviceName();
    那么疑问来了,我为什么采用了这种方式去抽象有继承实现?不得不提到的一个子类可以转父类型,然后通过父类的接口进行子类的调用,这也称作代码的闭包,相信都有所耳闻微服务框架,那么C++有没有呢?肯定是有的。那就是插件机制,通过把每一个class都封装成dll,然后通过顶层预留接口进行操作dll中的代码。调用流程图如下:

    调用接口
    内部分类处理
    调用摄像头
    调用显示设备
    调用实例
    调用实例
    Service Core
    AbstractDevice
    AbstractOther
    AbstractCamera
    AbstractDisplay
    Camera_1080P
    Display_LED

    屏蔽编译器和C++的实现机制,上层结构如果用底层的汇编来杠那就没意思了,更为详细的dll封装移步QtPlugin(C++跨平台插件开发)或者百度CTK框架,你会学习到C++的微服务框架。

    单例模式(Singleton Pattern)

    这个模式应该是广大同僚用的最多的。通过屏蔽对外的构造函数实现,场景不由分说,作用于当前程序只能存在一个class实例,经常用在管理器(批量的new/delete class)之上。

    • 懒汉单例

    // ClassManager.h
    
    class ClassManager: public QObject
    {
    public :
    		static const ClassManager* getInstance();
    		void ClassRegister(const QObject * pointer); //其他公有供调用的方法
    private:
    	ClassManager(){}
    	~ClassManager(){}
    	static ClassManager* This;
    }
    
    //ClassManager.cpp
    
    #include "ClassManager.h"
    static ClassManager* ClassManager::This = nullptr;//在调用getInstance()时构造
    
    static const ClassManager* getInstance()
    {
    	if(!This)  This = new ClassManager;
    	else return This;
    }
    
    void ClassRegister(const QObject * pointer)
    {
    	//你的一些操作
    }
    

    懒汉单例可以用在Widget类型的组件,因为new Widget class 需要在 Application之后

    • 饿汉单例

    // ClassManager.h
    
    class ClassManager: public QObject
    {
    public :
    		static const ClassManager* getInstance();
    		void ClassRegister(const QObject * pointer); //其他公有供调用的方法
    private:
    	ClassManager(){}
    	~ClassManager(){}
    	static ClassManager* This;
    }
    
    //ClassManager.cpp
    
    #include "ClassManager.h"
    static ClassManager* ClassManager::This = new ClassManager();  //直接构造
    
    static const ClassManager* getInstance()
    {
    	if(!This)  This = new ClassManager
    	else return This
    }
    
    void ClassRegister(const QObject * pointer)
    {
    	//你的一些操作
    }
    

    懒汉和饿汉只是进不进行class构造而已。

    建造者模式(Builder Pattern)

    看了N多教程解释这个专业名词,感觉举例子都太抽象了,比如什么肯德基套餐,实际代码呢,就是实单一的class 然后通过实例化形成一个包含多单一class的复合class,在Qt中存在这样的例子,比如界面 QComBobox ,为什么点击后会出现下拉列表?这个下拉列表是一个QListWidget,而把一或者多个对象组合成一个复合对象的过程,叫做建造过程,把这种方法论叫做建造者模式。所有的设计模式都是方法论!!!
    在项目中建造者模式用的比较多,我有一个自定义实现的QFileSelectBox组件类似CMake-GUI 的FileSelect具有编辑时检索本地文件并且补全到下拉列表的功能,代码在Github上(这里不方便贴代码,因为代码实在是太多了)这里是传送门,建议认真学习设计模式的看官能够下载然后跑一跑,认真的分析代码的实现。
    QFileSelectBox文件
    下面我实现最终的代码,拥有注释部分是建造者模式中构造的单一Class

    class FileSelectBox : public QWidget
    {
        Q_OBJECT
    public:
        enum FileSelectType
        {
            SELECT_ALL = 0,
            SELECT_DIRS = 1,
            SELECT_FILES = 2,
        };
    
        explicit FileSelectBox(QWidget *parent = nullptr);
        FileSelectBox(FileSelectType type,QWidget *parent = nullptr);
        ~FileSelectBox();
        const QLineEdit* lineEdit();
        const QPushButton* pushButton();
        const QTableView* tableView();
        const QDialog* fileDialog();
        void setLineEditText(QString Url);
        void setPushButtonText(QString DisplayTest);
    
    private:
        FileSelectLine* m_pLineEdit = nullptr;   //本质上是一个继承QLineEdit重构类型,为了实现的一些Signed
        selectPopList* m_pTableView = nullptr;   //本质上是一个QTableView,作为下拉列表的显示项
        QPushButton* m_pPushButton = nullptr;    //本质上是一个QPushButton,作为点击的按钮
        QStandardItemModel* m_pItemModel = nullptr;  //Item作为填充QTableView的数据项
        
        FileSelectType m_eSelectFileType = SELECT_ALL;
        QFileDialog* m_pFileDialog = nullptr;
        int m_iItemWidth,m_iItemHeight = 30;
        QPoint m_MovePos;
        QPoint m_AfterPos;
    
        virtual void resizeEvent(QResizeEvent* event);
        virtual void keyPressEvent(QKeyEvent *event);
        virtual void moveEvent(QMoveEvent* event);
        virtual void paintEvent(QPaintEvent* event);
        virtual void showEvent(QShowEvent *event);
        virtual void hideEvent(QHideEvent *event);
        virtual void closeEvent(QCloseEvent *event);
    
        virtual void focusInEvent(QFocusEvent *event);
        virtual void focusOutEvent(QFocusEvent *event);
        void initWidget();
    
    private slots:
        void showPopList(QStringList strList);
        void selectFile(QString path);
        void showSelectFileDialog();
        void hidePopList();
        void enterKeyAddText();
    
    signals:
    
    public slots:
    
    };
    

    最后所有的类组合成为 FileSelectBox。这便是建造者模式

    原型模式(Prototype Pattern)

    原型模式重点在于重载 operator = (),实现拷贝构造实现快速生成一个当前类的副本目标,什么?没听过?那就放弃看这篇文章。先去看看C++的基础。
    经常用在class拷贝,貌似好像没有怎么实现过,因为都是传Class地址。这个模式实现的可能就只有Qt中的QString class还有带有拷贝构造的容器类型,有其他的class或者实现的场景欢迎在评论区留言。
    题外话:拷贝构造会生成新的class对象,如果在class传递中默认使用了class的拷贝构造(例如QString),那么程序将会在调用函数时溢出。虽然是题外话,但是仍然是应该打星号的重点。

    • 结构型模式

    结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

    适配器模式(Adapter Pattern)

    假定一个场景:我们都知道QWidget类树族中Hide()和show()是隐藏和显示QWidget,基于这个方法做了一个QWidget界面显示和隐藏的管理类,原先的界面管理器结构,如下:

    QWidgetManager
    hide
    show

    上面的结构可以控制所有Widget子类Hide和show。现在因为业务需求,引入了新的界面管理器(假设来自其他GUI界面框架)。
    要求实现兼容两套界面管理器,如下(conceal:隐藏,spread:展示):

    QWidgetManager
    hide
    show
    InterfaceManager
    conceal
    spread

    思维,构造QWidget的两个可兼容新管理类的接口函数
    QWidget::conceal()
    QWidget::spread()
    但是我们总不能去更改QWidget源代码对吧,那么
    QWidget::conceal() => Interface::conceal()
    QWidget::spread() => Interface::::spread()

    • 继承实现

    class Interface:public QWidget
    {
    public:
        explicit interface(QWidget* parent = nullptr):QWidget(parent){}
        ~interface(){}
    
        void conceal(){hide();}
        void spread(){show();}
    };
    

    我所构造的 Interface 就是一个适配器,适配的 QWidget 类,作为 InterfaceManaegr 和 QWidgetManager 中间媒介。
    我通过 Interface 类型转换到父类型 QWidget 注册到 QWidgetManager 以供 hide() 和 show() 的调用,我也可以通过Interface注册到InterfaceManager 以供 conceal() 和 spread() 的调用。
    你想要所有继承QWidget 的类要在 InterfaceManager中能够调用 conceal() 和 spread() ,你得更改继承QWidget为Interface。并且单根多重继承会提示。

    • 重定义类套壳实现

    class Interface
    {
    public:
        Interface(QWidget* instance = nullptr)
        {
            m_pAdapterWidget = instance;
        }
        ~Interface(){}
    
        void setAdapterWidget(QWidget* instance){m_pAdapterWidget = instance;}
        QWidget * adapterWidget(){return m_pAdapterWidget;}
    
        void conceal(){m_pAdapterWidget->hide();}
        void spread(){m_pAdapterWidget->show();}
        void hide(){m_pAdapterWidget->hide();}
        void show(){m_pAdapterWidget->show();}
    
    private:
        QWidget * m_pAdapterWidget = nullptr;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        Interface intfc(new QWidget);
        intfc.spread();
    
        return a.exec();
    }
    
    
    

    通过Interface(QWidget*) 转换成新的 class;
    套壳实现 Interface 只能通过调用 QWidget * adapterWidget() 获取私有的 m_pAdapterWidget 设置到QWidgetManager,
    而直接兼容新的InterfaceManager。

    • 多重继承的套壳实现

    class Interface
    {
    public:
        Interface(QWidget* instance = nullptr,QWidget* parent = nullptr)
        {
            if(parent) instance->setParent(parent);
            if(instance) m_pAdapterWidget = instance;
        }
        ~Interface(){}
    
        void setAdapterWidget(QWidget* instance){m_pAdapterWidget = instance;}
        QWidget * adapterWidget(){return m_pAdapterWidget;}
    
        void conceal(){if(m_pAdapterWidget) m_pAdapterWidget->hide();}
        void spread(){if(m_pAdapterWidget) m_pAdapterWidget->show();}
    
    private:
        QWidget * m_pAdapterWidget = nullptr;
    };
    
    class myButton :public Interface,public QWidget
    {
    public:
        myButton(QWidget* parent = nullptr):
        QWidget(parent),
        Interface(nullptr,parent)
        {
            setAdapterWidget(this);
        }
        ~myButton(){}
    };
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        myButton button;
        button.spread();
    
        return a.exec();
    }
    

    个人认为多重构继承的套壳实现看起来有鸡肋。
    三种构造的方式的糙话体现:

    • 继承实现:我爸爸原来是个两脚的插头(QWidget),发现我不是亲生的,我爸爸居然成了我爷爷(QWidget),并且(QWidget)告诉我的新爸爸有三角插头(Interface)同时是爷爷的儿子,于是我成了三角插头,我成功的通过意外继承插上了三孔插板;
    • 套壳实现:我现在是个两脚插头(QWidget),我有个朋友他是个三角插头(Interface),我通过我的朋友插上三孔插板;
    • 多重构继承的套壳实现:我的爸爸一个两脚的插头,我的妈妈是个三角插头,好了我现在继承了父辈的所有传统,我顺理成章的能够插三孔插板;

    根据上面的解析,我觉得最能让人接受的是套壳实现,你别管我爸爸妈妈爷爷奶奶,我就找一个朋友帮我插三孔插板。

    桥接模式(Bridge Pattern)

    上面讲了适配器模式,可能会有些疑问 QWidgetManager 和 InterfaceManager 是干嘛的?桥接模式整好分拣一下这一块,顺带,整好最近开发的项目中用到了此模式。

    过滤器模式(Filter、Criteria Pattern)

    这里将会提到QSS和QSS选择器,在QtEvent中也运用了此设计模式进行事件分类与执行。

    组合模式(Composite Pattern)

    装饰器模式(Decorator Pattern)

    外观模式(Facade Pattern)

    享元模式(Flyweight Pattern)

    代理模式(Proxy Pattern)

    • 行为型模式

    行为型模式:这些设计模式特别关注对象之间的通信。

    责任链模式(Chain of Responsibility Pattern)

    命令模式(Command Pattern)

    解释器模式(Interpreter Pattern)

    迭代器模式(Iterator Pattern)

    中介者模式(Mediator Pattern)

    备忘录模式(Memento Pattern)

    观察者模式(Observer Pattern)

    状态模式(State Pattern)

    空对象模式(Null Object Pattern)

    策略模式(Strategy Pattern)

    模板模式(Template Pattern)

    访问者模式(Visitor Pattern)

    • J2EE 模式

    这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。这些模式其实在编程语言中都有所体现,并不是Java所有物,Java中的 MVC 可能同样的设计模式在 Qt中体现为 MVD。

    • 其他设计模式

    容器与管理类型

    整过Qt的人都知道有个QDesigner,有和工厂模式看起来一些类似,但是本质上却有差距,QDesigner生成的QML文件更倾向容器和管理类型,为什么这么说呢,因为delete ui;会帮你回收载ui文件中定义的所有class;

    展开全文
  • Qt与设计模式(全) 通过Qt状态机框架、设计模式(状态模式)实现状态转换 QtMVC 文章目录前言Widget设计思路Widget相关结构总结 前言 Widget设计思路 模仿qt的结构设计和思路 Widget相关结构 #mermaid-svg-Obej8PzC74...
  • BLoC(Business Logic Component)设计模式是一个新鲜词汇,其实是在Rx系列思想普及之后的必然趋势,即响应式编程。 其核心在于:将变量的变化看作一种流,并把Widget的状态绑定在流上,从而当变量改变时候,Widget...
  • Android 常用设计模式

    千次阅读 2017-03-04 13:01:24
    相反它是一篇让你快速了解各种设计模式的文章,你可以将这些设计模式应用到其他教程中,并研究如何改善你自己的代码。 创建模式 “如果我需要一个特别复杂的对象,我如何获得它?” – Future You ...
  • 因此需要适当的引入一些设计模式,将界面、数据与逻辑解耦。这里首先提一下MVP。 什么是MVP? 网络上文章很多。。。。。就不重复了。 Flutter上的MVP实践 这里直接拿代码说话,先说说MVP...
  • 设计模式-导读&助记

    2021-05-25 17:47:20
    你确定你真的对记住了所有的设计模式了吗? 各个设计模式的详细介绍都已经完成,但是不经常用总会忘,所以我想用 一句话 总结设计模式,思考模式的真正意图,再用 一点提示 来思考代码如何实现 写在前面 我整理的...
  • 【| 导语】在软工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的,...
  • 建造者模式(Builder Pattern)也称生成器模式,它属于创建型模式
  • 前言:本文为学习《大话设计模式》的总结。其它参考链接有: https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/behavioral.html 总结:行为型模式共11种,策略模式、模板方法模式、观察者...
  • #self.listWidget.resize(300,120) self.listWidget.addItem('item1') self.listWidget.addItem('item2') self.listWidget.addItem('item3') self.listWidget.addItem('item4') self.listWidget.addItem('item5') ...
  • Android常用8种设计模式(三)

    千次阅读 2016-12-03 14:39:15
    常用8种设计模式最后三个:适配器模式、合成模式、访问者模式 -----文章博客园整理而来,尊重原创 对于android开发者来说起,适配器模式简直太熟悉不过,有很多应用可以说是天天在直接或者间接的用到适配器模式,比如...
  • 《JavaScript设计模式》初次笔记——wsdchong

    万次阅读 多人点赞 2020-06-17 14:39:31
    《JavaScript设计模式》初次笔记 ...讲的内容有三:设计模式、JavaScript设计模式、其他(模块化的JavaScript设计模式、jQuery设计模式、jQuery插件设计模式)。 学习目的:尝试性地了解JavaScript设计模式,方
  • flutter - 设计模式-单例模式

    千次阅读 2019-10-21 23:26:22
    单例模式 效果: 单例模式.无论创建多少个实例,都只返回同一个实例. 只暴露你想暴露的公共成员和方法.(此处公共成员为:instanceVar,公共方法为instanceFun) 使用场景: dart-单例 准确来说,此单例模式是使用...
  • Android:Android涉及到的设计模式

    千次阅读 2016-05-08 22:59:17
    在android UI设计,几乎所有的widget和布局类都依靠这两个类。 组合模式,Composite Pattern,是一个非常巧妙的模式。几乎所有的面向对象系统都应用到了组合模式。将对象View和ViewGroup组合成树形结构以表示”部分...
  • 用于测试可访问性设计模式的游乐场。 可以方便地添加新示例并向同事显示关联的标记。 需要使用您自己的示例重建测试视图。 指示 选项 1:查看现有示例 在浏览器中打开index.html 。 选项 2:使用您自己的示例构建...
  • 设计模式: Mediator 中介者模式 文章目录设计模式: Mediator 中介者模式简介参考完整示例代码正文场景模式结构代码实现:文字对话框Widgets 窗口组件DialogDirector 对话框导向器测试结语模式特点 简介 目的 ...
  • Android MVP设计模式的理解

    千次阅读 2016-05-19 18:06:09
    做Android开发久了会有一种感觉,那就是业务逻辑总是伴随大量UI更新操作,最后我们的代码中UI更新伴随业务逻辑,业务逻辑发生伴随UI更新。... 所以从今以后,我们必须采取新的开发模式,尽管看起来繁琐一些,但是理解
  • 我们知道不管后台还是前端,不管是Android 还是iOS开发,都是基于MVC设计模式开发的.那么flutter是怎么使用MVC设计模式的尼? 一切皆dart,一起皆widget. M:数据模型 V:一些视图或者视图组件 C:我们习惯称之为页面...
  • SwiftUI Widget教程设计篇之 05 支持暗模式 什么是Widget 小部件可提升您应用中的关键内容,并将其显示在人们可以在iPhone,iPad和Mac上一目了然的位置。有用且令人愉悦的小部件还可以帮助人们以独特的方式个性化其...
  • 本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,包括年底前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,如果大家想获取最新教程,请关注微信公众号,谢谢建造者模式的定义是:将一个...
  • 第37章 分而治之——Widget模式 Widget :(Web Widget指的是一块可以在任意页面中执行的代码块)Widget模式是指借用Web Widget思想将页面分解成部件,针对部件开发,最终组合成完整的页面。 模块化开发使页面的功能...
  • 架构型设计模式

    2021-04-25 16:50:04
    技巧型设计模式(二)同步模块模式(SyncModuleDefine(SMD))模块管理器实现异步模块模式(AMD)实现执行顺序Widget 模式模版引擎实现封装成AMD模块 提供一些子系统,指定其职责并组合 代码:...
  • 上周学习了抽象工厂模式,该模式包含了简单工厂模式和工厂方法的某些特性,三者之间的区别详见:上周博客,本周深入了解一下简单工厂模式,并对其进行优化,实现一个可动态注册的简单工厂,本次练习参考了AWTK源码中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 24,426
精华内容 9,770
关键字:

widget设计模式