精华内容
下载资源
问答
  • 什么是MVVM模式

    2017-01-13 15:50:00
    问题引入1场景一:团队辛辛苦苦完成了一个项目,抱着激动的心情去给用户做demo,而用户给你的反馈是UI很不满意,要重新修改,否则拒绝验收。...问题出来了,按照传统的开发模式是基于CodeBehind这样的方式,UI总是和...

    问题引入
    1 场景一:团队辛辛苦苦完成了一个项目,抱着激动的心情去给用户做demo,而用户给你的反馈是UI很不满意,要重新修改,否则拒绝验收。大规模修改UI,晴天霹雳!
    2 场景二:产品在一家客户上线运行反应不错,公司准备扩大营销市场,寻求更多的客户,此时,不同客户对UI纷纷提出修改意见,众口难调,但是老总发话,客户是上帝!
    问题出来了,按照传统的开发模式是基于CodeBehind这样的方式,UI总是和业务逻辑紧密耦合在一起, UI修改,无法避免的业务逻辑修改随之而来,这无非就是我们老生常谈的解耦问题,有没有办法做到UI层剥离出逻辑层呢?MVVM模式为你排忧解难。

    一 什么是MVVM模式
    MVVM(Model-View-ViewModel)是专为WPF和SilverLight设计的开发模式, 与之类似的有Asp.net程序对应的MVC模式, WinForm程序对应的MVP, 关于MVC, MVP此处不展开论述,详情参考http://msdn.microsoft.com/zh-cn/library/dd381412(v=vs.98).aspx。 但在MVC和MVP模式中, View层都具有很多代码逻辑, 最简单的例子是在MVC中当界面发生交互时View去调用Controler中的某个方法,所以 并没有真正意义上实现View与ViewModel完全分离。
    WPF真正引人入胜、使之与WinForm泾渭分明的特点就是——“数据驱动界面”,何为“数据驱动界面” , 与传统的“事件驱动见面”相比较,数据编程了核心,UI处于从属地位;数据是底层、是心脏,数据变了作为表层的UI就会跟着变、将数据展现给用户;如果用户修改了UI元素上的值,相当于透过UI元素直接修改了底层的数据;围绕着这个核心,WPF准备了很多概念相当前卫的技术,其中包括为界面准备的XAML、为底层数据准备的Dependency Property & Binding和为消息传递准备的Routed Event & Command。 
    Binding和Command技术的出现,也为MVVM模式成为WPF平台下一个优秀的开发模式奠定了基础。通过Binding,可以绑定一个View的Property到ViewModel, ViewModel 对象被设置为视图的 DataContex,如果属性值在 ViewModel 更改,这些新值自动传播到通过数据绑定的视图,实现在ViewModel里可以不通过编写任何逻辑代码就直接更新View,做到View与ViewModel之间的完全松耦合,关于Binding,想了解更多可参见 Data and WPF: Customize Data Display with Data Binding and WPF "。 同样,如果没有WPF中的Command, MVVM也很难展示出它的强大力量,ViewModel可将Command暴露给View, 使得View可以消费command中对应的逻辑功能,对于不熟悉command的朋友,可以参考这篇文章Advanced WPF: Understanding Routed Events and Commands in WPF。
    下面简要介绍一下MVVM每个模块的主要职责
    1) View主要用于界面呈现,与用户输入设备进行交互,在code-Behind中还可以些一些UI的逻辑的,比如一些丰富的动画效果,或者直接设置某个元素的样式等,此外,设置View层的DataContext为对于的ViewModel层的逻辑也是写在code-Behind中。
    2) ViewModel是MVVM架构中最重要的部分,ViewModel中包含属性,命令,方法,事件,属性验证等逻辑,用于逻辑实现,负责View与Model之间的通信。
    3) Model就是我们常说的数据模型,用于数据的构造,数据驱动, 主要提供基础实体的属性以及每个属性的验证逻辑。
    MVVM中各个模块的交互方式如图所示:
     

    二 为什么要使用MVVM模式
    MVVM模式的引入能给我们带来什么优势呢?相信这是大多数学习MVVM的人关心的一个主要问题。
    首先我们应该清楚地认识到,MVVM不是适用于任何的项目开发,一个项目是否要上一套框架取决于项目本身的规模和性质,盲目的使用开发模式可能会引起过度开发,通常情况下,企业级的WPF应用软件建议使用,主要优势下面将展开详细阐述。
    1团队层面 统一了项目团队的思维方式,也改变了开发方式,由于View与ViewModel之间的松耦合关系,我们可以轻易做到开发团队与设计团队的明确分工,开发团队可以专注于创建功能强大的 ViewModel 类,而设计团队能够熟练运用Blend等工具能为程序员输出用户友好的试图View的XAML文件。而且,随着项目的进行,不断会有新的成员加入,一个清晰的项目设计模式,能够很大程度地减少他熟悉项目的所需时间,并能够规范的进行接下来的开发维护工作。
    2 架构层面 项目架构更加稳定,模块之间松散的耦合关系使得模块之间的相互依赖性大大降低,这也就意味着项目的扩展性得到了提高,即使以后需要加一些新的模块,或者实现模块的注入,我们也能做到最小的改动,从而保证项目的稳定。
    3 代码层面MVVM的引入也使得项目本身变得模块清晰化,条理化,有助于我们更好地区分哪些逻辑是属于UI操作,哪些逻辑是业务操作,增强了代码的可读性、可测性。对于ViewModel层,Views和Unit tests是两个不同类型的消费者,应用程序中的主要交互逻辑处于ViewModel层,这样,在完成ViewModel之后,我们完全可以有理由相信,我们可以对ViewModel进行单元测试,因为它不依赖于任何UI控件,从这个角度看,似乎UnitTest相比于View而言具备更大的消费能力。

    三 详解ViewModel
     ViewModel是MVVM架构中最重要的部分,负责View与Model直接的通信,对于ViewModel的理解是掌握MVVM的关键,下面我们针对ViewModel进行详细剖析。 
    1 ViewModel的属性ViewModel的属性是View数据的来源,但ViewModel层不能是Model层的简单封装,ViewModel层也不能是View层的简单映射。ViewModel的属性可由三部分组成:一部分是Model的复制属性;另一部分用于控制UI状态。例如Button属性的Disable属性,当操作完成时可以通过这个属性更改通知View做相应的UI变换或者后面提到的事件通知;第三部分是一些方法的参数,可以将这些方法的参数设置成相应的属性绑定到View中的某个控件,然后在执行方法的时候获取这些属性,所以一般方法不含参数。
    2 ViewModel的命令 ViewModel中的命令用于接受View的用户输入,并做相应的处理。我们也可以通过方法实现相同的功能。
    3 ViewModel的事件  ViewModel中的事件主要用来通知View做相应的UI变换。它一般在一个处理完成之后触发,随后需要View做出相应的非业务的操作。所以一般ViewModel中的事件的订阅者只是View,除非其他自定义的非View类之间的交互。
    4 View及ViewModel交互模式
    在View与ViewModel模型之间进行双向的联系的主要方式是通过数据绑定。当正确地使用该设计模式后,每一个View除了纯净的XAML和非常少量的后置代码外不会再包含任何东西,彻底地做到了界面展示和业务逻辑的分离,让程序员更加专注于代码的编写。
    ViewModel也能用来容纳View的状态以及执行View需要的任何命令。
    因为WPF内置了Command模式,对于像Button控件之类的UI元素来说都有一个Command的属性,它是WPF所定义的ICommand类型。可以把这些命令放到ViewModel中并以公有属性的形式暴露出来,这样就可以让View对其进行绑定。这极其强大,因为它可以把ModelView中的可执行代码绑定到窗体的Button上。

    四 MVVM实践
    理论知识已经准备充分,现在是检验真理的时刻,以下是使用了Model-View-ViewModel 设计模式的s世上最简单的WPF应用程序例子,简单加法计算器。
    1 代码结构如下图:
     

    2 CaculatorModel类:
    public class CaculatorModel
        {
            public int Num1 { get; set; }
            public int Num2 { get; set; }
            public int Result { get; set; }
    }

    3 ICommand类型的基类DelegateCommand

    using System;
    using System.Windows.Input;
    namespace MVVMDemo.Commands
    {
        public class DelegateCommand:ICommand
        {
            public DelegateCommand(Action<object> executeCommand, Func<object, bool> canExecuteCommand)
            {
                this.executeCommand = executeCommand;
                this.canExecuteCommand = canExecuteCommand;
            }
            // The specific ExecuteCommand aciton will come from the ViewModel, the same as CanExecuteCommand
            private Action<object> executeCommand;

            public Action<object> ExecuteCommand
            {
                get { return executeCommand; }
                set { executeCommand = value; }
            }

            private Func<object, bool> canExecuteCommand;

            public Func<object, bool> CanExecuteCommand
            {
                get { return canExecuteCommand; }
                set { canExecuteCommand = value; }
            }

            public event EventHandler CanExecuteChanged;

            public bool CanExecute(object parameter)
            {
                if (CanExecuteCommand != null)
                {
                    return this.CanExecuteCommand(parameter);
                }
                else
                {
                    return true;
                }
            }

            public void Execute(object parameter)
            {
                if (this.ExecuteCommand != null) this.ExecuteCommand(parameter);
            }

            public void RaiseCanExecuteChanged()
            {
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }
        }
    }
    注:ICommand中有两个方法CanExecute和Execute必须实现,这两个方法分别对应着当Command调用时判断是否能执行和具体执行逻辑。

    4 ViewModelBase类

    using System.ComponentModel;

    namespace MVVM.ViewModel
    {
        public class ViewModelBase : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public void RaisePropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    }
    注:ViewModelBase实现了接口INotifyPropertyChanged, 在该接口中有一个PropertyChanged事件, 当ViewModel中的Property改变时,允许触发PropertyChanged事件,继而重新绑定数据到UI上。
    5 CaculatorViewModel类
    using System.Windows.Input;
    using MVVM.Model;
    using MVVMDemo.Commands;

    namespace MVVM.ViewModel
    {
        public class CaculatorViewModel:ViewModelBase
        {
            #region Fields

            private int num1;
            private int num2;
            private int result;
            private CaculatorModel model;

            #endregion

            #region Properties

            public int Num1
            {
                get 
                {
                    return num1;
                }
                set
                {
                    num1 = value;
                    this.RaisePropertyChanged("Num1");
                }
            }

            public int Num2
            {
                get
                {
                    return num2;
                }
                set
                {
                    num2 = value;
                    this.RaisePropertyChanged("Num2");
                }
            }

            public int Result
            {
                get
                {
                    return result;
                }
                set
                {
                    result = value;
                    this.RaisePropertyChanged("Result");
                }
            }

            #endregion

            #region Commands

            public ICommand CaculateCommand{get;set;}
            public ICommand ClearCommand { get; set; }

            #endregion

            #region Methods

            public void Add(object param)
            {
                Result = Num1 + Num2;
            }

            public void Clear(object param)
            {
                Result = 0;
                Num1 = 0;
                Num2 = 0;
            }

            public void InitilizeModelData()
            {
                // In gernal, the data comes from database
                var model = new CaculatorModel()
                {
                    Num1 = 1,
                    Num2 = 1,
                    Result = 2
                };

                Num1 = model.Num1;
                Num2 = model.Num2;
                Result = model.Result;
            }

            public CaculatorViewModel()
            {
                CaculateCommand = new DelegateCommand(Add, null);
                ClearCommand  = new DelegateCommand(Clear, null);

                InitilizeModelData();
            }

            #endregion
        }
    }
    6 简单计算器的UI

     

    该View所对应的XAML文件如下:
    <Window x:Class="MVVM.View.CaculatorView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="CaculatorView" Height="300" Width="682">
        <Grid Width="596">
            <TextBox Height="23" HorizontalAlignment="Left" Margin="41,90,0,0" Name="txtNum1" VerticalAlignment="Top" Width="120" Text="{Binding Num1}"/>
            <TextBox Height="25" HorizontalAlignment="Left" Margin="195,88,0,0" Name="txtNum2" VerticalAlignment="Top" Width="120" Text="{Binding Num2}"/>
            <Label Content="+" Height="28" HorizontalAlignment="Left" Margin="167,88,0,0" Name="label1" VerticalAlignment="Top" />
            <TextBox Height="25" HorizontalAlignment="Left" Margin="364,88,0,0" Name="textBox5" VerticalAlignment="Top" Width="120"  Text="{Binding Result}"/>
            <Button Content"=" Height="23" HorizontalAlignment="Left" Margin="328,90,0,0" Name="button1" VerticalAlignment="Top" Width="28" Command="{Binding CaculateCommand}" />
            <Button Content="Clear" Height="26" HorizontalAlignment="Left" Margin="501,88,0,0" Name="button2" VerticalAlignment="Top" Width="45" Command="{Binding ClearCommand}" />
        </Grid>
    </Window>
    6. View的Code-Behind
    using System.Windows;
    using MVVM.ViewModel;

    namespace MVVM.View
    {
        /// <summary>
        /// CaculatorView.xaml 的Ì?交?互£¤逻?辑-
        /// </summary>
        public partial class CaculatorView : Window
        {
            public CaculatorView()
            {
                InitializeComponent();
                this.DataContext = new CaculatorViewModel();
            }
        }
    }
    注:这里讲View的DataContext设为CaculatorViewModel实例,至此,View和ViewModel建立关联。

    转载于:https://www.cnblogs.com/zuochanzi/p/6283007.html

    展开全文
  • 中介,作用于多个事物之间充当...再比如利用电子信息技术的互联网,作为一种新媒体,不单可以更高效地把信息传递给用户,而且可以反向地获得用户反馈评论,用户与用户之间亦可以进行沟通,这种全终端双向互通传...

    转载自:https://mp.weixin.qq.com/s/EsR5hZXNvB6lXsd8rhs4bw

    中介,作用于多个事物之间充当交互沟通的媒介。我们的生活中有各种各样的媒介,比如一些传统媒体,书刊杂志,报纸,把信息传递给读者。再比如利用电子信息技术的互联网,作为一种新媒体,不单可以更高效地把信息传递给用户,而且可以反向地获得用户反馈评论,用户与用户之间亦可以进行沟通,这种全终端双向互通是传统媒体所不能及的。
    在这里插入图片描述除此之外,再如婚介所、房产中介、交换机组网、现代电子商务、C2C购物平台、手机、即时通软件等等,这些都与我们的生活息息相关,离开它们我们将举步维艰。其实不管是任何中介,其本质都是相同的,都是充当中间媒介的角色,并达成多方业务互通的目的。
    在这里插入图片描述
    首先我们以最简单的模型来解决问题,以两个人交谈为例,其实他们之间并不需要任何第三方媒介,而是一对一直接沟通,看代码。

    public class People {
       private String name;//用名字来区别人。
       private People other;//持有对方的引用。
     
       public String getName() {
         return this.name;
       }
     
       public People(String name) {
        this.name = name;//初始化必须起名。
      }
    
      public void connect(People other) {
        this.other = other;//连接方法中注入对方引用。
      }
    
      public void talk(String msg) {
        other.listen(msg);//我方说话时,对方聆听。
      }
    
      public void listen(String msg) {
        //聆听来自对方的声音
        System.out.println(
            other.getName() + " 对 " + this.name + " 说:" + msg
        );
      }
    }
    

    一切就绪,两人开始沟通。

    public class Main {
       public static void main(String args[]) {
        People p3 = new People("张三");
         People p4 = new People("李四");
     
         p3.connect(p4);
         p4.connect(p3);
     
         p3.talk("你好。");
        p4.talk("早上好,三哥。");
      }
      /****************************
      输出结果:
        张三 对 李四 说:你好。
        李四 对 张三 说:早上好,三哥。
      *****************************/
    }
    

    从People类中我们可以看到,沟通只只能在两人之间进行,而且各自都持有对方对象的引用,以便把消息传递给对方的监听方法。这种模式虽然简单,但耦合性太强,你中有我,我中有你,谁也离不开谁。试想如果再有多个人加入交谈,那每个人都要持有其他所有人的引用了,这时会陷入一种多对多的关联陷阱,对象关系变得复杂不堪,如蛛网般难以维护。
    在这里插入图片描述我们就拿群聊天室举例,每当有人加入或离开,都要把每个人持有的其他人的引用关系更新一遍,发消息时更是繁琐不堪,重复工作显得非常多余。那么如何解决这个问题呢?我们开始进行思考,为何不把重复的部分抽离出来呢,也就是把对方的引用放在一个中介类里面去统一维护起来,于是设计更改如下。
    在这里插入图片描述可以看到,每个用户不再所持有其他所有用户的引用了,取而代之的是聊天室的引用,这样引用关系瞬间变得明朗起来,开始我们的代码重构。

    public class User {
         private String name;//名字
     
         private ChatRoom chatRoom;//聊天室引用
     
         public User(String name) {
             this.name = name;//初始化必须起名字
         }
     
        public String getName() {
            return this.name;
        }
    
        public void login(ChatRoom chatRoom) {//用户登陆
            chatRoom.connect(this);//调用聊天室连接方法
            this.chatRoom = chatRoom;//注入聊天室引用
        }
    
        public void talk(String msg) {//用户发言
            chatRoom.sendMsg(this, msg);//给聊天室发消息
        }
    
        public void listen(User fromWhom, String msg) {//且听风吟
            System.out.print("【"+this.name+"的对话框】");
            System.out.println(fromWhom.getName() + " 说: " + msg);
        }
    }
    

    可以看到第14行,用户登陆聊天室时不再是连接对方了,而是连接通知聊天室并告知:“有人进来了请进行注册”,然后记录下来用户当前所在聊天室的引用。第19行,用户发言时也不是直接找对方了,而是把消息扔给聊天室处理。第23行,聆听方法同样也是,将来会接受来自聊天室的声音。很显然,一切沟通都与是中介聊天室进行,这样用户之间就实现了解耦的目的。当然,用户当然还需要注销离开聊天室,请读者可自行练习添加,下面接着写我们的聊天室中介类。

    public class ChatRoom {
         private String name;//聊天室命名
     
         public ChatRoom(String name) {
             this.name = name;//初始化必须命名聊天室
         }
     
         List<User> users = new ArrayList<>();//聊天室里的用户们
     
        public void connect(User user) {
            this.users.add(user);//用户进入聊天室加入列表。
            System.out.print("欢迎【");
            System.out.print(user.getName());
            System.out.println("】加入聊天室【" + this.name + "】");
        }
    
        public void sendMsg(User fromWhom, String msg) {
            // 循环所有用户,只发消息给非发送方fromWhom。
            users.stream()
            .filter(user -> !user.equals(fromWhom))//过滤掉发送方fromWhom
            .forEach(toWhom -> toWhom.listen(fromWhom, msg));//发送消息给剩下的所有人
        }
    }
    

    这里我们新建一个聊天室作为中介类,所有参与者登陆时调用第10行的connect方法进入聊天室,并记录其引用到users列表中。第17行,当用户发消息到平台我们再转发给其他人,这里利用Java8的流和Lambda表达式进行过滤(User类的equals方法请自行加入),并循环调用所有接收方的listen方法即可。

    为了说明问题,我们这里只是保持最简单的方式,如果某天情况变得复杂,有了不同的用户,或是聊天室也各不相同并加入了各自的特性,那我们就需要继续重构,抽象聊天室类,抽象用户类,读者可以灵活运用,这里就不做赘述了。

    其实中介模式不止是在生活中广泛应用,在软件架构中也非常常见,当下流行的微服务分布式软件架构所用到的注册中心,例如最常用到的云组件Eureka Server,其作用就是为众多分布式服务提供注册发现服务,它正是充当像中介一样的角色。

    还记得之前讲到的组合模式中的树型结构吧,它主要描述的是子节点与父节点的关系。
    在这里插入图片描述而中介模式更像是网络拓扑中的星型结构,它描述了众节点与中心点的关系。
    在这里插入图片描述对像之间显式地互相引用越多,意味着依赖性越强,独立性越差,不利于代码维护与扩展,同时多方沟通的任务也应交由中间平台来完成,每个类应只具备各自该有的功能,这便是高内聚低耦合的设计标准。中介模式符合迪米特法则,它解决了对象间过度耦合、复杂频繁交互的问题,打破了你中有我,我中有你的相互依赖,第三方的介入有助于双方调停,打破如胶似漆、纠缠不休的关系,让他们之间变得松散、自由、独立。

    展开全文
  • 虽然这篇文章不是我写的,但我完全同意文章中的观点。Brian Button可能是我所知道的最有才的人之一。...1、单例模式经常被用来为某些服务提供一个全局访问点没错,是可以这么做,但代价是什么呢?众所周知,单例模...

    原文链接:http://blogs.msdn.com/b/scottdensmore/archive/2004/05/25/140827.aspx

    虽然这篇文章不是我写的,但我完全同意文章中的观点。Brian Button可能是我所知道的最有才的人之一。我相信他会喜欢你们的反馈的。

    1、单例模式经常被用来为某些服务提供一个全局访问点
    没错,是可以这么做,但代价是什么呢?众所周知,单例模式为你的应用程序中的某些服务提供全局访问点,这样你就不必到处传递一个该服务的引用。这和一个全局变量有什么区别呢?(记住,全局变量是不好的,不是吗?)最终会发生的事情便是你设计中的依赖关系是隐藏在代码中的,这种依赖关系不能够通过检查你的类和方法的接口所见。你必须检查代码以便准确地知道你的类中都用了哪些其他对象。这可能会是不清楚的(译者注:不知道是不是这么翻译,原文:This is less clear than it could be)。冲动地创建一个全局性的东西来避免把其传来传去是你设计中的异味,这不是全局变量或者单例的功能。如果你更仔细地检查你的设计,你几乎总是能想到一种不必把“流浪数据”四处传递到每个对象和方法的更好的设计。

    2、单例模式允许你限制你所创建的对象的数量
    这也是对的,但是现在你混合了两个不同的职责到同一个类中了,这是违反了单一职责原则的。一个类不应该关心它是否是一个单例,它应该仅仅关心它的业务职责。如果你想限制某些类的实例化能力,创建一个封装了创建并且如你所愿的那样限制创建能力的工厂或者对象生成器,这样创建职责便从业务实体职责中划分出来了。

    3、单例模式促进了类之间的紧耦合
    使代码是可测试的一个基本属性便是它和其周围环境是松耦合的。这个属性允许你在测试过程中为了实现特定的测试目标(想想模拟对象)替换备用的合作者。单例模式把单例对象的确切类型紧耦合在一起,丧失了使用多态来替换的机会。一个更好的选择,如上面第一点所讨论到的,便是改变你的设计以便允许你传递对象的引用到你的类和方法中,这样可以减轻上述的耦合问题。

    4、单例模式在程序的持续过程中一直保存着上一次的状态
    持久状态是单元测试的敌人。让单元测试有效的事情之一便是每个测试必须独立于其他所有测试。如果不是这样,那么测试运行的顺序会影响到测试的结果。这可能会导致测试在不应该失败的地方失败,甚至更坏的事情是仅仅因为测试的运行顺序导致测试通过。这可能会隐藏住bugs并且是邪恶的。防止状态从一个测试携带到另一个测试的一个很好办法便是避免使用静态变量。单例模式,就其本质而言,依赖于在一个静态变量中保存着一个实例。这是测试依赖的一个挑战,可以通过传递对象的引用到你的类和方法中来避免。

    希望这篇文章或多或少地阐释了我对于单例模式观点。我有一个从google或者其他地方找到的一个小链接集,包括Jim Hyslop和Herb Sutter,他们同样分享了这些观点。如果你喜欢他们那么让我知道吧。

    更多文章请关注我的个人博客:http://www.nomoneynowife.com

    转载于:https://www.cnblogs.com/nomoneynowife/p/3719031.html

    展开全文
  • 这一周(02.25-03.03)我定的目标《JavaScript 模式》的第七章学习一遍,学习结果的反馈就是本篇文章啦。 由于内容实在太长,我将本文分为两部分: 《JavaScript 模式》知识点整理(上) 《JavaScript 模式》知识点...

    eb3164f3aff2d4debdc4e1ea3320b13c.png

    介绍

    最近开始给自己每周订个学习任务,学习结果反馈为一篇文章的输出,做好学习记录。
    这一周(02.25-03.03)我定的目标是《JavaScript 模式》的第七章学习一遍,学习结果的反馈就是本篇文章啦。
    由于内容实在太长,我将本文分为两部分:

    1. 《JavaScript 模式》知识点整理(上)
    2. 《JavaScript 模式》知识点整理(下)

    本文内容中主要参考《JavaScript 模式》,其中也有些案例是来自网上资料,有备注出处啦,如造成不便,请联系我删改。

    过两天我会把这篇文章收录到我整理的知识库 【Cute-JavaScript】 中,并已经同步到 【github】上面。

    六.外观模式(Facade Pattern)

    1.概念介绍

    外观模式(Facade Pattern)是一种简单又常见的模式,它为一些复杂的子系统接口提供一个更高级的统一接口,方便对这些子系统的接口访问。

    它不仅简化类中的接口,还对接口和调用者进行解耦,外观模式也常被认为是开发者必备,它可以将一些复杂操作封装起来,并创建一个简单接口用于调用。

    2.优缺点和应用场景

    2.1优点

    • 轻量级,减少系统相互依赖。
    • 提高灵活性。
    • 提高了安全性。

    2.2缺点

    • 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

    2.3应用场景

    • 为复杂的模块或子系统提供外界访问的模块。
    • 子系统相对独立。
    • 预防低水平人员带来的风险。
    • 项目重构。

    3.基本案例

    经常我们在处理一些特殊情况的时候,需要一起调用好几个方法,我们使用外观模式,就可以将多个方法包装成一个方法,哪里需要使用直接调用这个包装好的方法就可以。
    比如我们经常处理浏览器事件,需要同时调用stopPropagation()preventDefault(),于是我们就可以新建一个外观方法,实现这两个方法同时调用:

    let myEvent = {
        // ...
        stop: e => {
            e.stopPropagation();
            e.preventDefault();
        }
    };
    

    然后我们也可以使用外观模式,来做IE事件的兼容性:

    let myEvent = {
        // ...
        stop: e => {
            // 其他 
            if(typeof e.preventDefault === 'function'){
                e.preventDefault();
            }
            if(typeof e.stopPropagation === 'function'){
                e.stopPropagation();
            }
            // IE
            if(typeof e.returnValue === 'boolean'){
                e.returnValue = false;
            }
            if(typeof e.cancelBubble === 'boolean'){
                e.cancelBubble = true;
            }
        }
    };
    

    七、代理模式(Proxy Pattern)

    1.概念介绍

    代理模式(Proxy Pattern) 为其他对象提供一种代理,来控制这个对象的访问,代理是在客户端和真实对象之间的介质。

    e7c33cd89a0b56f82bff1404a9a0c968.png

    简单的理解:如我们需要请明星来做广告,我们会先通过联系Ta的经纪人,谈好条件才会给明星签合同。

    2.优缺点和应用场景

    2.1优点

    • 职责单一且清晰。
    • 保护真实对象。
    • 开闭原则,高拓展性。

    2.2缺点

    • 由于在客户端和真实对象间添加代理对象,导致请求处理速度变慢。
    • 实现代理模式需要额外工作,有些代理模式实现起来非常复杂。

    2.3应用场景

    • 需要隐藏或保护某个类,则为这个类添加代理。
    • 需要给不同访问者提供不同权限,则在代理类上做判断。
    • 需要为某个类添加功能,如添加日志缓存等,我们可以在代理的类做添加,而不管去改原来封装好的类。

    3.基本案例

    这里我们以吃午饭问题来学习代理模式。通常情况下,我们会有两种方式解决午饭问题:“去餐厅吃”和“叫外卖”。
    去餐厅吃的话,我们就是自己过去吃饭了呗,如果是叫外卖,我们就会通过外卖小哥来拿到午饭才能吃起来。

    • 去餐厅吃(没有使用代理模式)
    // 定义午饭类 参数 菜名
    let Lunch = function(greens){
        this.greens = greens;
    }
    Lunch.prototype.getGreens = function(){
        return this.greens;
    }
    // 定义我这个对象
    let leo = {
        buy: function(greens){
            console.log(`午饭吃${greens.getGreens()}`);
        }
    }
    // 去餐厅吃
    leo.buy(new Lunch('青椒炒肉')); // 午饭吃青椒炒肉
    
    • 叫外卖(有使用代理模式)
    // 定义午饭类 参数 菜名
    let Lunch = function(greens){
        this.greens = greens;
    }
    Lunch.prototype.getGreens = function(){
        return this.greens;
    }
    // 定义外卖小哥这个对象
    let brother = {
        buy: function(lunch){
            leo.buy(lunch.getGreens());
        }
    }
    // 定义我这个对象
    let leo = {
        buy: function(greens){
            console.log(`午饭吃${greens}`);
        }
    }
    // 叫外卖
    brother.buy(new Lunch('青椒炒肉')); // 午饭吃青椒炒肉
    

    并且外卖小哥还会帮我们做一些其他事,比如帮我们带瓶可乐,我们改造brotherleo这2个对象,再看看效果:

    let brother = {
        buy: function(lunch){
            if(leo.needCola) leo.buyCola();
            leo.buy(lunch.getGreens());
        }
    }
    
    let leo = {
        needCola: true,
        buy: function(greens){
            console.log(`午饭吃${greens}`);
        },
        buyCola: function(){
            console.log(`顺手买瓶可乐!`);
        }
    }
    brother.buy(new Lunch('青椒炒肉'));
    // 顺手买瓶可乐!
    // 午饭吃青椒炒肉
    

    4.保护代理

    还是借用 3.基本案例 的叫外卖的例子,我们现在要实现保护代理,而我们需要外卖小哥为了我们的身体健康,超过晚上9点,就不帮我们买可乐。
    还是改造上面买可乐的brother对象代码:

    let brother = {
        buy: function(lunch){
            let nowDate = new Date();
            if(nowDate.getHours() >= 21){
                console.log('亲,这么晚不要喝可乐哟!');
            }else{
                if(leo.needCola) leo.buyCola();
                leo.buy(lunch.getGreens());
            }
        }
    }
    brother.buy(new Lunch('青椒炒肉'));
    // 顺手买瓶可乐!
    // 午饭吃青椒炒肉
    

    5.虚拟代理

    虚拟代理能把一些开销大的对象,延迟到真正需要的时候才去创建和执行。
    我们这里举个图片懒加载的例子:

    这个案例参考自JS设计模式-代理模式.

    // 图片加载
    let ele = (function(){
        let node = document.createElement('img');
        document.body.appendChild(node);
        return{
            setSrc : function(src){
                node.src = src;
            }
        }
    })()
    
    // 代理对象
    let proxy = (function(){
        let img = new Image();
        img.onload = function(){
            ele.setSrc(this.src);
        }
        return {
            setSrc : function(src){
                img.src = src;
                ele.setSrc('loading.png');
            }
        }
    })()
    
    proxy.setSrc('example.png');
    

    6.缓存代理

    缓存代理是将一些开销大的运算结果提供暂存功能,当下次计算时,参数和之前一直,则将缓存的结果返回:
    这个案例参考自JS设计模式-代理模式.

    //计算乘积
    let mult = function(){
        let result = 1;
        for(let i = 0; i<arguments.length; i++){
            result *= arguments[i];
        }
        return result;
    }
    
    // 缓存代理
    let proxy = (function(){
        let cache = {};
        return function(){
            let args = Array.prototype.join.call(arguments, '',);
            if(args in cache){
                return cache[args];
            }
            return cache[args] = mult.apply(this,arguments);
        }
    })();
    

    八、中介者模式(Mediator Pattern)

    1.概念介绍

    中介者模式(Mediator Pattern) 是用来降低多个对象和类之间的通信复杂性,促进形成松耦合,提高可维护性。

    8056b200efaa664a7c3d3b666d8edb7c.png

    在这种模式下,独立的对象之间不能直接通信,而是需要中间对象(mediator对象),当其中一个对象(colleague对象)状态改变后,它会通知mediator对象, 然后mediator对象会把该变换通知到任意需要知道此变化的colleague对象。

    2.优缺点和应用场景

    2.1优点

    • 降低类的复杂度,从一对多转成一对一。
    • 为各个类之间解耦。
    • 提高代码可维护性。

    2.2缺点

    中介者会越来越庞大,变得难以维护。

    2.3应用场景

    • 系统中对象之间存在比较复杂的引用关系,而且难以复用该对象。
    • 需要生成最少的子类,实现一个中间类封装多个类中的行为的时候。

    另外: 不要在职责混乱的时候使用。

    3.基本案例

    这里我们实现一个简单的案例,一场测试结束后,公布结果,告知解答出题目的人挑战成功,否则挑战失败:
    这个案例来自JavaScript 中常见设计模式整理

    const player = function(name) {
        this.name = name;
        playerMiddle.add(name);
    }
    
    player.prototype.win = function() {
        playerMiddle.win(this.name);
    }
    
    player.prototype.lose = function() {
        playerMiddle.lose(this.name);
    }
    
    const playerMiddle = (function() { // 将就用下这个 demo,这个函数当成中介者
        const players = [];
        const winArr =  [];
        const loseArr = [];
        return {
            add: function(name) {
                players.push(name)
            },
            win: function(name) {
                winArr.push(name)
                if (winArr.length + loseArr.length === players.length) {
                    this.show()
                }
            },
            lose: function(name) {
                loseArr.push(name)
                if (winArr.length + loseArr.length === players.length) {
                    this.show()
                }
            },
            show: function() {
                for (let winner of winArr) {
                    console.log(winner + '挑战成功;')
                }
                for (let loser of loseArr) {
                    console.log(loser + '挑战失败;')
                }
            },
        }
    }())
    
    const a = new player('A 选手');
    const b = new player('B 选手');
    const c = new player('C 选手');
    
    a.win()
    b.win()
    c.lose()
    
    // A 选手挑战成功;
    // B 选手挑战成功;
    // C 选手挑战失败;
    

    4.书本案例

    这个案例来自 《JavaScript 模式》第七章 中介者模式 的案例。
    这里我们有这么一个游戏例子,规则是两个玩家在规定时间内,比比谁点击按钮次数更多,玩家1按按键2,玩家2按按键0,并且计分板实时更新。

    0c3e48f1a9e85a112ab86c479a190a3e.png

    这里的中介者需要知道所有其他对象信息,并且它需要知道哪个玩家点击了一次,随后通知玩家。玩家进行游戏的时候,还要通知中介者它做的事情,中介者更新分数并显示比分。

    这里的player对象都是通过Player()构造函数生成,并且都有pointsname属性,每次调用play()都会增加1分并通知中介者。

    function Player(name){
        this.points = 0;
        this.name   = name;
    }
    Player.prototype.play = function(){
        this.points += 1;
        mediator.played();
    }
    

    计分板有个update()方法,当玩家回合结束就会调用,它不知道任何玩家的信息也没有保存分值,只是实现展示当前分数。

    let scoreboard = {
        // 待更新HTML元素
        ele: document.getElementById('result');
        // 更新比分
        update: function (score){
            let msg = '';
            for(let k in score){
                if(score.hasOwnProperty(k)){
                    msg = `<p>${k} : ${score[k]}</p>`
                }
            }
            this.ele.innerHTML = msg;
        }
    }
    

    接下来创建mediator对象:

    let mediator = {
        players: {},       // 所有玩家
        setup: function(){ // 初始化
            let players = this.players;
            players.homw = new Player('Home');
            players.guest = new Player('Guest');
        },
        // 当有人玩时 更新分数
        played: function(){
            let players = this.players 
            let score = {
                Home: players.home.points,
                Guest: players.guest.points,
            }
            scoreboard.update(score);
        }
        // 处理用户交互
        keypress: function(e){
            e = e || window.event;  // 兼容IE
            if(e.which === 49){     // 按键1
                mediator.players.home.play();
            }
            if(e.which === 48){     // 按键0
                mediator.players.guest.play();
            }
        }
    }
    

    最后就是需要运行和卸载游戏了:

    mediator.setup();
    window.onkeypress = mediator.keypress;
    // 游戏30秒后结束
    setTimeout(function(){
        window.onkeypress = null;
        alert('游戏结束');
    }, 30000)
    

    九、观察者模式(Observer Patterns)

    1.概念介绍

    观察者模式(Observer Patterns) 也称订阅/发布(subscriber/publisher)模式,这种模式下,一个对象订阅定一个对象的特定活动,并在状态改变后获得通知。
    这里的订阅者称为观察者,而被观察者称为发布者,当一个事件发生,发布者会发布通知所有订阅者,并常常以事件对象形式传递消息。

    所有浏览器事件(鼠标悬停,按键等事件)都是该模式的例子。

    我们还可以这么理解:这就跟我们订阅微信公众号一样,当公众号(发布者)群发一条图文消息给所有粉丝(观察者),然后所有粉丝都会接受到这篇图文消息(事件),这篇图文消息的内容是发布者自定义的(自定义事件),粉丝阅读后可能就会买买买(执行事件)。

    2.观察者模式 VS 发布订阅模式

    2.1观察者模式

    一种一对多的依赖关系,多个观察者对象同时监听一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

    1cea1fc97c65a7e72c5d54f09def90a3.png

    2.2发布订阅模式

    发布订阅模式理念和观察者模式相同,但是处理方式上不同。
    在发布订阅模式中,发布者和订阅者不知道对方的存在,他们通过调度中心串联起来。
    订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(并携带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

    bd8ec8ded55e394c54ba5faa12bc39c8.png

    2.3两者异同点

    • 观察者模式中,观察者知道发布者是谁,并发布者保持对观察者进行记录。而发布订阅模式中,发布者和订阅者不知道对方的存在。它们只是通过调度中心进行通信。
    • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
    • 观察者模式大多是同步,如当事件触发,发布者就会去调用观察者的方法。而发布订阅模式大多是异步的(使用消息队列)。
    • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

    尽管存在差异,但也有人说发布-订阅模式是观察者模式的变异,因为它们概念上相似。

    2.4两者优缺点

    相同优点:

    • 都可以一对多
    • 程序便于扩展

    不同优点:

    • 观察者模式:单向解耦,发布者不需要清楚订阅者何时何地订阅,只需要维护订阅队列,发送消息即可
    • 发布订阅模式:双向解耦,发布者和订阅者都不用清楚对方,全部由订阅中心做处理

    缺点:

    • 如果一个被观察者和多个观察者的话,会增加维护的难度,并且会消耗很多时间。
    • 如果观察者和发布者之间有循环依赖,可能会导致循环调用引起系统奔溃。
    • 观察者无法得知观察的目标对象是如何发生变化,只能知道目标对象发生了变化。
    • 发布订阅模式,中心任务过重,一旦崩溃,所有订阅者都会受到影响。

    4.基本案例

    我们平常一直使用的给DOM节点绑定事件,也是观察者模式的案例:

    document.body.addEventListener('click', function(){
        alert('ok');
    },false);
    document.body.click();
    

    这里我们订阅了document.bodyclick事件,当body点击它就向订阅者发送消息,就会弹框ok。我们也可以添加很多的订阅。

    4.观察者模式 案例

    本案例来自 javascript 观察者模式和发布订阅模式。

    class Dom {
        constructor() {
            // 订阅事件的观察者
            this.events = {}
        }
    
        /**
        * 添加事件的观察者
        * @param {String} event  订阅的事件
        * @param {Function} callback 回调函数(观察者)
        */
        addEventListener(event, callback) {
            if (!this.events[event]) {
                this.events[event] = []
            }
            this.events[event].push(callback)
        }
    
        removeEventListener(event, callback) {
            if (!this.events[event]) {
                return
            }
            const callbackList = this.events[event]
            const index = callbackList.indexOf(callback)
                if (index > -1) {
                callbackList.splice(index, 1)
            }
        }
    
        /**
        * 触发事件
        * @param {String} event
        */
        fireEvent(event) {
            if (!this.events[event]) {
                return
            }
            this.events[event].forEach(callback => {
                callback()
            })
        }
    }
    
    const handler = () => {
        console.log('fire click')
    }
    const dom = new Dom()
    
    dom.addEventListener('click', handler)
    dom.addEventListener('move', function() {
        console.log('fire click2')
    })
    dom.fireEvent('click')
    

    5.发布订阅模式 案例

    本案例来自 javascript 观察者模式和发布订阅模式。

    class EventChannel {
        constructor() {
            // 主题
            this.subjects = {}
        }
    
        hasSubject(subject) {
            return this.subjects[subject] ? true : false
        }
    
        /**
        * 订阅的主题
        * @param {String} subject 主题
        * @param {Function} callback 订阅者
        */
        on(subject, callback) {
            if (!this.hasSubject(subject)) {
                this.subjects[subject] = []
            }
            this.subjects[subject].push(callback)
        }
    
        /**
        * 取消订阅
        */
        off(subject, callback) {
            if (!this.hasSubject(subject)) {
                return
            }
            const callbackList = this.subjects[subject]
            const index = callbackList.indexOf(callback)
            if (index > -1) {
                callbackList.splice(index, 1)
            }
        }
    
        /**
        * 发布主题
        * @param {String} subject 主题
        * @param {Argument} data 参数
        */
        emit(subject, ...data) {
            if (!this.hasSubject(subject)) {
                return
            }
            this.subjects[subject].forEach(callback => {
                callback(...data)
            })
        }
    }
    
    const channel = new EventChannel()
    
    channel.on('update', function(data) {
        console.log(`update value: ${data}`)
    })
    channel.emit('update', 123)
    

    参考资料

    1. 《JavaScript Patterns》
    展开全文
  • 我的很多读者都在反馈说,现在一个岗位可以收到的简历数,前几年的几倍。我们必须承认,僧多粥少就是 Android 行业的现状,别说初中级工程师,就是高级工程师也一抓一大把。企业招人的眼光也越来越高,如果你没...
  • 什么是MVP呢,简单来说就是将view层和逻辑完全独立出来,让逻辑和显示完全独立。本例中就是采用了这种模式,让activity作为view层,activity中涉及了适配器,所以这里尝试让适配器作为P层来进行逻辑处理。以后可能要...
  • 我看不出这与动态类型语言有什么不同(尽管我希望得到一些反馈)。在Visitor的另一个好处,它将运行在每个实体上的代码与枚举实体的代码完全分离。这至少在一个大型项目中为我节省了一些严重...
  • 当你在定义方法时,应该保证此方法的正确性,但是你不能保证你的方法的调用者,他们的输入数据,或者  使用环境的正确性,当客户端代码调用... 代码的作者 “你们写的 什么狗屁代码,怎么总报错" ,或者”强哥你的
  • 设计模式之美 - 加餐六 | 什么所谓的编程能力?如何考察一个人的编程能力? 加餐六-什么所谓的编程能力?如何考察一个人的编程能力? 在招聘要求里,我们经常看到“要求候选人有扎实的编程能力”。在面试...
  • 有很多老师向我们咨询有什么好的方法可以进行课堂评测并结合OneNote进行课堂教学,当然Office 365中的Forms一个非常好的选择。 除此之外就是可以利用PPT的一些插件来进行课堂评测。 但是以上评测都需要学生在...
  • 多个渠道的地推需要做实时数字化监测,再根据市场反馈的结果及时修改此前的方案,将理想化的方案调整到符合现实逻辑的最佳程度。伴随着互联网O2O项目的...一、地推是什么?在很多人印象里,地推可能还只是一群人在...
  • dma是什么意思? 什么是dma?

    千次阅读 2010-01-18 11:51:00
    dma是什么意思? 什么是dma?DMA的英文拼写是“Direct MemoryAccess”,汉语的意思就是直接内存访问,是一种不经过CPU而直接从内存了存取数据的数据交换模式。PIO模式下硬盘和内存之间的数据传输是由CPU来控制的;而...
  • 时代卡通武力:萌界采用IP矩阵来规划主要考虑三个方面。 第一、目前品牌人格化的市场蓝海还比较大,很多角色IP座位都空缺的,没有多少优质竞争对手,在目前...第三、矩阵的模式更容易联动IP生态资源,能够...
  • OFB模式解读

    万次阅读 2018-09-04 21:43:43
    OFB模式的全称output-Feedback模式(输出反馈模式)。在OFB模式中,密码算法的输出会反馈到密码算法的输入中。 OFB模式不是通过密码算法对明文直接加密的,而是通过将“明文分组”和“密码算法的输出”进行XOR来...
  • CFB模式解读

    万次阅读 2018-09-03 22:05:15
    CFB模式全称Cipher FeedBack模式(密文反馈模式)。在CFB模式中,前一个密文分组会被送回到密码算法的输入端。所谓反馈,这里指的就是返回输入端的意思。 在ECB模式和CBC模式中,明文分组都通过密码算法进行...
  • MVC比较直观的架构模式,用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)。 MVC使用非常广泛,比如JavaEE中的SSH框架 三...
  • 在开发django应用的过程中,使用开发者模式启动服务特别方便的一件事,只需要 python manage.py runserver 就可以运行服务,并且提供了非常人性化的autoreload机制,不需要手动重启程序就可以修改代码并看到反馈。...
  • 再比如利用电子信息技术的互联网,作为一种新媒体,不单可以更高效地把信息传递给用户,而且可以反向地获得用户反馈评论,用户与用户之间亦可以进行沟通,这种全终端双向互通传统媒体所不能及的。  除此之外,...
  • 事件:今天早上好不容易程序上线了,结果客户手机反馈更新不到最新版本,原因静默更新失败,随后发现客户手机的USB调试模式的勾选被取消。 求助:***请问大家,在什么情况下,android程序会把USB调试模式勾选状态...
  • 什么是数字化营销

    2021-04-02 14:27:34
    那么什么是传统营销,人们常常说的传统营销的本质便用传统的思维方法和传播手段,传统的营销方法的坏处就在于关于顾客认知的片面性,割裂的执行环节和落地渠道,缺少及时的反馈数据,同时成本很高,效率低下。...
  • 而将它们结合是什么呢?下面我将和大家讲述Java迭代对Java程序中需要反复执行的子程序*(一组指令),进行一次重复,即重复执行程序中的循环,直到满足某条件为止,称为Java迭代。说到Java迭代不得不提的就是Java的...
  • 设计模式之代理模式

    2014-04-17 18:59:00
    很久没有更新设计模式系列的文章了,有了很多热心朋友的反馈,我决定继续将这个系列赶快写完,最近由于过年了,有很多相关的事宜要做,所以 没有时间来写,也对大家的说下抱歉,感觉写文章的时间越来越少了,不过...
  • 设计模式-代理模式

    千次阅读 2013-05-24 09:56:50
     很久没有更新设计模式系列的文章了,有了很多热心朋友的反馈,我决定继续将这个系列赶快写完,最近由于过年了,有很多相关的事宜要做,所以 没有时间来写,也对大家的说下抱歉,感觉写文章的时间越来越少了,...
  • 什么是敏捷流程

    千次阅读 2014-11-08 09:16:43
    敏捷开发针对传统的瀑布开发模式的弊端而产生的一种新的开发模式,目标提高开发效率和响应能力。除了原则和实践,模式很重要的,多研究模式及其应用可以使你更深层次的理解敏捷开发。 沟通 建模不但能
  • 1.什么是监听者模式 2.监听者模式的组成(成员) 3.代码实现 4.思考 1.什么是监听者模式 监听者模式在现实中无处不在,举个常见的例子,我们经常在电影片段中看到,当信号侦察兵接收到上级下发的某个指令信号,会立即...
  • 什么是蓝绿发布

    2019-10-03 22:47:08
    蓝绿发布(Blue Green Deployment)一种平滑过渡的发布模式。蓝绿发布的操作模式上,首先依赖于能够将全站应用划分为对等的A、B两个单元,A先发布新产品代码并引入少许用户流量,B继续运行老产品代码;如果新代码A...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 636
精华内容 254
关键字:

反馈模式是什么