精华内容
下载资源
问答
  • Angular Pipe 嵌套对象上的非预期行为分析 场景 工作中,存在一个嵌套对象,需要展示嵌套对象内层的一些信息,于是写了个Pipe 来处理,但是发现当嵌套的对象发生变化时,pipe 不会重新执行。例如有下面一个数据...
        

    Angular Pipe 在 嵌套对象上的非预期行为分析

    场景

    在工作中,存在一个嵌套对象,需要展示嵌套对象内层的一些信息,于是写了个Pipe 来处理,但是发现当嵌套的对象发生变化时,pipe 不会重新执行。例如有下面一个数据。

    var feer = {
        name: 'joe',
        skills: [
            {
                name:'js'
            },
            {
                name: 'ts'
            }
        ]
    }

    我们想要的结果是把skills 里面的name 全部展示出来,以, 分割。

    // component
    export class AppComponent {
      private skills = ['css', 'html', 'java', 'gulp']
      name = 'Angular 6';
      feer = {
        name: 'joe',
        skills: [
          {
            name: 'js'
          },
          {
            name: 'ts'
          }
        ]
      }
    
      add() {
        const skill = this.skills.shift();
        if (skill) {
          this.feer.skills.push({
            name: skill
          })
        }
      }
    }
    // html
    {{ feer.skills | defaultPure }}
    // Pipe
    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
        name: 'defaultPure'
    })
    
    export class DefaultPurePipe implements PipeTransform {
        transform(feer:any): string {
            return feer.skills.map((v)=>v.name).join(',');
        }
    }

    在这种情况下,如果调用add 引起 skills 发生变化,pipe 不会重新计算,显示的还是初始值 js,ts

    问题

    这里存在一个问题,对于上面?例子中存在的嵌套结构,在 skills 变化的时候 ,transform 并没有重新执行。这里推测可能是 内部检测时候并不会做 deep-change-detection (也就是不会关注 skills 里的变化),最终导致了上面的问题: skills 发生了变化,但 pipe 并没有重新执行计算变更输出结果。

    下面是摘自官网的一段话:

    Angular executes a pure pipe only when it detects a pure change to the input value. A pure change is either a change to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object).

    Angular ignores changes within (composite) objects. It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.

    pipe 忽略了 对象内部复合对象的变动(如例子中的 skills ), angular 不会做深度检查,当我们调用 add 方法时候,往 skills 数组里 push 里一个对象,对于 angular 来说, skills 是“未”发生变化的,因为引用是一样的。

    解决方案

    @Pipe 的 decorator 中除了name 还有一个叫 pure 类型为 booleanmetadata,官方解释如下

    If Pipe is pure (its output depends only on its input.) Normally pipe's transform method is only invoked when the inputs to pipe's transform method change. If the pipe has internal state (it's result are dependant on state other than its arguments) than set pure to false so that the pipe is invoked on each change-detection even if the arguments to the pipe do not change.

    大意是 pipe 仅在输入发生改变的时候会再次执行 transform 方法。但是如果 pipe 有一个内部状态,并且输出依赖于这个内部状态,那么将 pure 设置为 false 以便在每次 change-detection (数据更新检测) 时候执行 transform即使输入的数据并没有改变。

    对于pure, 默认值为 true ,如果设置为 false ,则可以将上面的功能实现出来。但是同时要注意一个问题,一个 impurepipe (即 pure = false ) 会经常被调用,如果有大量运算,则可能影响用户体验 。

    同样看一段官网的解释:

    Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

    对于 impurepipeangular 会在每次 component 的 变化检测周期里调用执行,甚至一次按键、一次鼠标事件都会触发 pipe 的执行。

    最终,对于上述的问题,只需修改 pipe 的代码即可。

    @Pipe({
        name: 'defaultPure',
        pure: false
    })
    
    export class DefaultPurePipe implements PipeTransform {
        transform(feer:any): string {
            return feer.skills.map((v)=>v.name).join(',');
        }
    }

    完结

    对于上述问题,表面上来说加一下 pure 即可,对于深层次来说,涉及到了 angular 内部的一些工作原理。但是话说回来,目前仅仅研究到这种方案来处理,实际上应该还会有其他的解决方案,大胆猜测可以利用 change-detection 的一些钩子来处理,待研究好了再记录一下。

    之前在遇到这个问题的时候,也是一脸懵逼的找答案,殊不知答案就在文档上写的清清楚楚。 ?‍♀️~~~

    BTW,其实在 vue 中也存在了一个类似的问题, 为了性能等的考虑,不会对复合对象做深度变更检测。而 Vue的做法则简单粗暴一些:重新装饰数组方法。 具体可以查看 Vue源代码

    展开全文
  • 据我理解,状态就是一种数据,它用来描述,某个特定时间,这个对象所具有特质,它将作为对象行为发生的依据和结果。我们平时做设计和编程时候,尤其是做数据访问层时候,特别喜欢一些仅仅包含getter/...

    从结构上看,tlibcqrs项目并不复杂,但对其进行介绍,的确让我感到有点无从着手。还是先从领域模型中的对象的行为和状态谈起吧。

    先来谈谈对象状态。据我理解,状态就是一种数据,它用来描述,在某个特定的时间上,这个对象所具有的特质,它将作为对象行为发生的依据和结果。我们平时做设计和编程的时候,尤其是在做数据访问层的时候,特别喜欢一些仅仅包含getter/setter属性的对象,以便调用方能够通过getter获得对象的状态,使用setter设置对象的状态。之前我也说明过,状态并非getter/setter属性,在OOP上,状态表现为“字段”(fields)。现在我们讨论的不是数据访问层的DAO,而是领域模型中的实体。当然,实体也是对象,自然也有状态,不仅仅是状态,实体是参与业务逻辑的重要对象,它还有处理业务逻辑的行为。

    现在假设我们有个实体为Customer,它同时也是某个聚合的聚合根,在通常情况下,我们会用下面的形式去定义这个Customer实体(为了简化,省去了对象行为):

    image

    当然你不会觉得这样设计有什么太大的问题,事实上在我们平时的开发中,也的确是这么做的,而且非CQRS架构的DDD实践也支持这样的实体模型。于是,我们可以使用下面的代码来更新某个Customer的姓名:

       1: [TestMethod]
       2: public void ChangeCustomerNameTest()
       3: {
       4:     Customer customer = new Customer
       5:     {
       6:         Birth = DateTime.Now.AddYears(-20),
       7:         Email = "daxnet@live.com",
       8:         FirstName = "dax",
       9:         LastName = "net",
      10:         Password = "123456",
      11:         Username = "daxnet"
      12:     };
      13:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      14:     {
      15:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      16:         customerRepository.Add(customer);
      17:     }
      18:     ISpecification<Customer> spec = Specification<Customer>.Eval(p => p.Username.Equals("daxnet"));
      19:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      20:     {
      21:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      22:         var customer2 = customerRepository.Get(spec);
      23:         Assert.AreEqual(customer.Username, customer2.Username);
      24:  
      25:         customer2.FirstName = "qingyang";
      26:         customer2.LastName = "chen";
      27:         customerRepository.Update(customer2);
      28:     }
      29:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      30:     {
      31:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      32:         var customer3 = customerRepository.Get(spec);
      33:         Assert.AreEqual("qingyang", customer3.FirstName);
      34:         Assert.AreEqual("chen", customer3.LastName);
      35:     }
      36: }

    在上面代码段的25行和26行,我们直接设置了Customer的姓名,然后在27行调用仓储进行实体更新。一切都非常顺利。今后,实体中的属性,就成为了我们实现业务逻辑和实体行为的依据。然而,CQRS体系结构模式的实践就与这种方式大相径庭。CQRS是一种事件驱动的架构(Event Driven Architecture,EDA),它的设计与实践需要遵循事件驱动的基本特征。

     

    事件驱动架构下的对象状态

    事件驱动架构是一种体系结构模式,它对事件的整个生命周期进行检测、跟踪和管理,并对事件的产生与发展做出反应。“事件”,可以定义为“造成状态变化的信号”。比如:当用户买了一辆轿车,那么,这辆车的状态就从“等待销售”转变为“已出售”。汽车销售系统就会将此作为一种事件将其发布到事件总线,以便应用程序的其它系统或组件能够订阅到这个事件并做进一步的处理(请参见维基百科:http://en.wikipedia.org/wiki/Event_driven_architecture)。

    从现在开始,我们需要重新认识对象状态的变化。由于EDA的引入,对象状态只能通过事件的发生而产生变化,外界不能无缘无故地对其进行改变(就像上面的例子一样)。这样做的好处是:我们不仅能够知道对象的当前状态,而且还能知道,到底发生了哪些事情,才使对象变成现在这副“模样”。当某一事件发生时,对象捕获到这一事件并对其进行处理,而在处理的过程中再根据事件的类型和数据来改变自己的状态。由于通常情况下,这样的处理过程是对象的内部行为,因此,我们也就无需将更改状态的接口暴露给外部。在理解了这部分内容后,我们的Customer对象的设计就需要做出修改,变成下面这种形式(为了讨论方便,在此将Customer实体命名为SourcedCustomer,意为支持事件溯源):

       1: public class SourcedCustomer : SourcedAggregateRoot
       2: {
       3:     public virtual string Username { get; private set; }
       4:     public virtual string Password { get; private set; }
       5:     public virtual string FirstName { get; private set; }
       6:     public virtual string LastName { get; private set; }
       7:     public virtual string Email { get; private set; }
       8:     public virtual DateTime Birth { get; private set; }
       9: }

    将setter定义为private,以防止外界直接修改对象状态。对于某些外部也不关心的状态,我们甚至连getter都可以省去(也就是不需要再实现为property了),取而代之的是一个private的字段。比如:

       1: public class SourcedCustomer : SourcedAggregateRoot
       2: {
       3:     private DateTime dayOfBirth;
       4:     // ....
       5: }

     

    对象的行为导致状态变化

    现在回到tlibcqrs项目,让我们看看TinyLibrary.Domain下Book实体的实现方式。它的状态是一系列的public getter和private setter的自动实现的属性。Book实体状态的改变,是通过其行为实现的。当某个行为被外界调用时,行为本身会产生一个事件,而对象本身又去处理这个事件,从而导致状态变化。

       1: public class Book : SourcedAggregateRoot
       2: {
       3:     public string Title { get; private set; }
       4:     public string Publisher { get; private set; }
       5:     public DateTime PubDate { get; private set; }
       6:     public string ISBN { get; private set; }
       7:     public int Pages { get; private set; }
       8:     public bool Lent { get; private set; }
       9:  
      10:     public Book() : base() {  }
      11:     public Book(long id) : base(id) {  }
      12:  
      13:     public static Book Create(string title, string publisher, DateTime pubDate, string isbn, int pages, bool lent)
      14:     {
      15:         Book book = new Book();
      16:         book.RaiseEvent<BookCreatedEvent>(new BookCreatedEvent
      17:         {
      18:             Title = title,
      19:             Publisher = publisher,
      20:             PubDate = pubDate,
      21:             ISBN = isbn,
      22:             Pages = pages,
      23:             Lent = lent
      24:         });
      25:         return book;
      26:     }
      27:  
      28:     public static Book Create(long id, string title, string publisher, DateTime pubDate, string isbn, int pages, bool lent)
      29:     {
      30:         Book book = new Book(id);
      31:         book.RaiseEvent<BookCreatedEvent>(new BookCreatedEvent
      32:         {
      33:             Title = title,
      34:             Publisher = publisher,
      35:             PubDate = pubDate,
      36:             ISBN = isbn,
      37:             Pages = pages,
      38:             Lent = lent
      39:         });
      40:         return book;
      41:     }
      42:  
      43:     public void LendTo(Reader reader)
      44:     {
      45:         this.RaiseEvent<BookLentEvent>(new BookLentEvent { ReaderId = reader.Id, LentDate = DateTime.Now });
      46:     }
      47:  
      48:     public void ReturnBy(Reader reader)
      49:     {
      50:         this.RaiseEvent<BookGetReturnedEvent>(new BookGetReturnedEvent { ReaderId = reader.Id, ReturnedDate = DateTime.Now });
      51:     }
      52:  
      53:     #region Domain Event Handlers
      54:     [Handles(typeof(BookCreatedEvent))]
      55:     private void OnBookCreated(BookCreatedEvent evnt)
      56:     {
      57:         this.Title = evnt.Title;
      58:         this.Publisher = evnt.Publisher;
      59:         this.PubDate = evnt.PubDate;
      60:         this.ISBN = evnt.ISBN;
      61:         this.Pages = evnt.Pages;
      62:         this.Lent = evnt.Lent;
      63:     }
      64:     [Handles(typeof(BookLentEvent))]
      65:     private void OnBookLent(BookLentEvent evnt)
      66:     {
      67:         this.Lent = true;
      68:     }
      69:     [Handles(typeof(BookGetReturnedEvent))]
      70:     private void OnBookReturnedBack(BookGetReturnedEvent evnt)
      71:     {
      72:         this.Lent = false;
      73:     }
      74:     #endregion
      75: }

    上面的代码就是TinyLibrary.Domain.Book实体,我们可以看到,在Book被创建的时候,会产生BookCreatedEvent事件,当Book被借出时,会产生BookLentEvent事件,当Book被归还时,又会产生BookGetReturnedEvent事件。而这些事件会被OnBookCreated、OnBookLent和OnBookReturnedBack私有方法捕获,从而引起对象状态的变化。

    不仅如此,SourcedAggregateRoot基类会将这些事件记录下来。领域仓储(Domain Repository)在保存聚合的时候,就只需要保存这一系列事件就可以了。这也是为什么在tlibcqrs项目中有一个TinyLibraryEventDB的数据库,而这个数据库却只有一张DomainEvents数据表的原因。由于实体本身能够通过捕获并处理事件来恢复状态,因此,通过事件回放即可重塑实体。当然,在领域仓储保存聚合的同时,这些事件也会被推送到事件总线,以便系统的其它部分能够对这些事件进行处理。

    有关领域仓储(Domain Repository)和事件存储(Event Store)相关的内容和疑问,将在下一讲中进行讲解,敬请期待!

    转载于:https://www.cnblogs.com/daxnet/archive/2010/12/22/1913745.html

    展开全文
  • 据我理解,状态就是一种数据,它用来描述,某个特定时间,这个对象所具有特质,它将作为对象行为发生的依据和结果。我们平时做设计和编程时候,尤其是做数据访问层时候,特别喜欢一些仅仅包含getter/...

    从结构上看,tlibcqrs项目并不复杂,但对其进行介绍,的确让我感到有点无从着手。还是先从领域模型中的对象的行为和状态谈起吧。

    先来谈谈对象状态。据我理解,状态就是一种数据,它用来描述,在某个特定的时间上,这个对象所具有的特质,它将作为对象行为发生的依据和结果。我们平时做设计和编程的时候,尤其是在做数据访问层的时候,特别喜欢一些仅仅包含getter/setter属性的对象,以便调用方能够通过getter获得对象的状态,使用setter设置对象的状态。之前我也说明过,状态并非getter/setter属性,在OOP上,状态表现为“字段”(fields)。现在我们讨论的不是数据访问层的DAO,而是领域模型中的实体。当然,实体也是对象,自然也有状态,不仅仅是状态,实体是参与业务逻辑的重要对象,它还有处理业务逻辑的行为。

    现在假设我们有个实体为Customer,它同时也是某个聚合的聚合根,在通常情况下,我们会用下面的形式去定义这个Customer实体(为了简化,省去了对象行为):

    image

    当然你不会觉得这样设计有什么太大的问题,事实上在我们平时的开发中,也的确是这么做的,而且非CQRS架构的DDD实践也支持这样的实体模型。于是,我们可以使用下面的代码来更新某个Customer的姓名:

       1: [TestMethod]
       2: public void ChangeCustomerNameTest()
       3: {
       4:     Customer customer = new Customer
       5:     {
       6:         Birth = DateTime.Now.AddYears(-20),
       7:         Email = "daxnet@live.com",
       8:         FirstName = "dax",
       9:         LastName = "net",
      10:         Password = "123456",
      11:         Username = "daxnet"
      12:     };
      13:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      14:     {
      15:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      16:         customerRepository.Add(customer);
      17:     }
      18:     ISpecification<Customer> spec = Specification<Customer>.Eval(p => p.Username.Equals("daxnet"));
      19:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      20:     {
      21:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      22:         var customer2 = customerRepository.Get(spec);
      23:         Assert.AreEqual(customer.Username, customer2.Username);
      24:  
      25:         customer2.FirstName = "qingyang";
      26:         customer2.LastName = "chen";
      27:         customerRepository.Update(customer2);
      28:     }
      29:     using (IRepositoryContext ctx = ObjectContainer.Instance.GetService<IRepositoryContext>())
      30:     {
      31:         IRepository<Customer> customerRepository = ctx.GetRepository<Customer>();
      32:         var customer3 = customerRepository.Get(spec);
      33:         Assert.AreEqual("qingyang", customer3.FirstName);
      34:         Assert.AreEqual("chen", customer3.LastName);
      35:     }
      36: }

    在上面代码段的25行和26行,我们直接设置了Customer的姓名,然后在27行调用仓储进行实体更新。一切都非常顺利。今后,实体中的属性,就成为了我们实现业务逻辑和实体行为的依据。然而,CQRS体系结构模式的实践就与这种方式大相径庭。CQRS是一种事件驱动的架构(Event Driven Architecture,EDA),它的设计与实践需要遵循事件驱动的基本特征。

     

    事件驱动架构下的对象状态

    事件驱动架构是一种体系结构模式,它对事件的整个生命周期进行检测、跟踪和管理,并对事件的产生与发展做出反应。“事件”,可以定义为“造成状态变化的信号”。比如:当用户买了一辆轿车,那么,这辆车的状态就从“等待销售”转变为“已出售”。汽车销售系统就会将此作为一种事件将其发布到事件总线,以便应用程序的其它系统或组件能够订阅到这个事件并做进一步的处理(请参见维基百科:http://en.wikipedia.org/wiki/Event_driven_architecture)。

    从现在开始,我们需要重新认识对象状态的变化。由于EDA的引入,对象状态只能通过事件的发生而产生变化,外界不能无缘无故地对其进行改变(就像上面的例子一样)。这样做的好处是:我们不仅能够知道对象的当前状态,而且还能知道,到底发生了哪些事情,才使对象变成现在这副“模样”。当某一事件发生时,对象捕获到这一事件并对其进行处理,而在处理的过程中再根据事件的类型和数据来改变自己的状态。由于通常情况下,这样的处理过程是对象的内部行为,因此,我们也就无需将更改状态的接口暴露给外部。在理解了这部分内容后,我们的Customer对象的设计就需要做出修改,变成下面这种形式(为了讨论方便,在此将Customer实体命名为SourcedCustomer,意为支持事件溯源):

       1: public class SourcedCustomer : SourcedAggregateRoot
       2: {
       3:     public virtual string Username { get; private set; }
       4:     public virtual string Password { get; private set; }
       5:     public virtual string FirstName { get; private set; }
       6:     public virtual string LastName { get; private set; }
       7:     public virtual string Email { get; private set; }
       8:     public virtual DateTime Birth { get; private set; }
       9: }

    将setter定义为private,以防止外界直接修改对象状态。对于某些外部也不关心的状态,我们甚至连getter都可以省去(也就是不需要再实现为property了),取而代之的是一个private的字段。比如:

       1: public class SourcedCustomer : SourcedAggregateRoot
       2: {
       3:     private DateTime dayOfBirth;
       4:     // ....
       5: }

     

    对象的行为导致状态变化

    现在回到tlibcqrs项目,让我们看看TinyLibrary.Domain下Book实体的实现方式。它的状态是一系列的public getter和private setter的自动实现的属性。Book实体状态的改变,是通过其行为实现的。当某个行为被外界调用时,行为本身会产生一个事件,而对象本身又去处理这个事件,从而导致状态变化。

       1: public class Book : SourcedAggregateRoot
       2: {
       3:     public string Title { get; private set; }
       4:     public string Publisher { get; private set; }
       5:     public DateTime PubDate { get; private set; }
       6:     public string ISBN { get; private set; }
       7:     public int Pages { get; private set; }
       8:     public bool Lent { get; private set; }
       9:  
      10:     public Book() : base() {  }
      11:     public Book(long id) : base(id) {  }
      12:  
      13:     public static Book Create(string title, string publisher, DateTime pubDate, string isbn, int pages, bool lent)
      14:     {
      15:         Book book = new Book();
      16:         book.RaiseEvent<BookCreatedEvent>(new BookCreatedEvent
      17:         {
      18:             Title = title,
      19:             Publisher = publisher,
      20:             PubDate = pubDate,
      21:             ISBN = isbn,
      22:             Pages = pages,
      23:             Lent = lent
      24:         });
      25:         return book;
      26:     }
      27:  
      28:     public static Book Create(long id, string title, string publisher, DateTime pubDate, string isbn, int pages, bool lent)
      29:     {
      30:         Book book = new Book(id);
      31:         book.RaiseEvent<BookCreatedEvent>(new BookCreatedEvent
      32:         {
      33:             Title = title,
      34:             Publisher = publisher,
      35:             PubDate = pubDate,
      36:             ISBN = isbn,
      37:             Pages = pages,
      38:             Lent = lent
      39:         });
      40:         return book;
      41:     }
      42:  
      43:     public void LendTo(Reader reader)
      44:     {
      45:         this.RaiseEvent<BookLentEvent>(new BookLentEvent { ReaderId = reader.Id, LentDate = DateTime.Now });
      46:     }
      47:  
      48:     public void ReturnBy(Reader reader)
      49:     {
      50:         this.RaiseEvent<BookGetReturnedEvent>(new BookGetReturnedEvent { ReaderId = reader.Id, ReturnedDate = DateTime.Now });
      51:     }
      52:  
      53:     #region Domain Event Handlers
      54:     [Handles(typeof(BookCreatedEvent))]
      55:     private void OnBookCreated(BookCreatedEvent evnt)
      56:     {
      57:         this.Title = evnt.Title;
      58:         this.Publisher = evnt.Publisher;
      59:         this.PubDate = evnt.PubDate;
      60:         this.ISBN = evnt.ISBN;
      61:         this.Pages = evnt.Pages;
      62:         this.Lent = evnt.Lent;
      63:     }
      64:     [Handles(typeof(BookLentEvent))]
      65:     private void OnBookLent(BookLentEvent evnt)
      66:     {
      67:         this.Lent = true;
      68:     }
      69:     [Handles(typeof(BookGetReturnedEvent))]
      70:     private void OnBookReturnedBack(BookGetReturnedEvent evnt)
      71:     {
      72:         this.Lent = false;
      73:     }
      74:     #endregion
      75: }

    上面的代码就是TinyLibrary.Domain.Book实体,我们可以看到,在Book被创建的时候,会产生BookCreatedEvent事件,当Book被借出时,会产生BookLentEvent事件,当Book被归还时,又会产生BookGetReturnedEvent事件。而这些事件会被OnBookCreated、OnBookLent和OnBookReturnedBack私有方法捕获,从而引起对象状态的变化。

    不仅如此,SourcedAggregateRoot基类会将这些事件记录下来。领域仓储(Domain Repository)在保存聚合的时候,就只需要保存这一系列事件就可以了。这也是为什么在tlibcqrs项目中有一个TinyLibraryEventDB的数据库,而这个数据库却只有一张DomainEvents数据表的原因。由于实体本身能够通过捕获并处理事件来恢复状态,因此,通过事件回放即可重塑实体。当然,在领域仓储保存聚合的同时,这些事件也会被推送到事件总线,以便系统的其它部分能够对这些事件进行处理。

    有关领域仓储(Domain Repository)和事件存储(Event Store)相关的内容和疑问,将在下一讲中进行讲解,敬请期待!

    展开全文
  • 定义 观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-...这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 结构 一个软件...

    定义

    观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

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

    结构

    一个软件系统里面包含了各种对象,就像一片欣欣向荣的森林充满了各种生物一样。在一片森林中,各种生物彼此依赖和约束,形成一个个生物链。一种生物的状态变化会造成其他一些生物的相应行动,每一个生物都处于别的生物的互动之中。

    一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其他的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作。观察者模式是满足这一要求的各种设计方案中最重要的一种。

     

    观察者模式所涉及的角色有:

    • 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
    • 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
    • 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
    • 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

     实现

    //抽象主题角色类
    public abstract class Subject {
        /**
         * 用来保存注册的观察者对象
         */
        private    List<Observer> list = new ArrayList<Observer>();
        /**
         * 注册观察者对象
         * @param observer    观察者对象
         */
        public void attach(Observer observer){
            
            list.add(observer);
            System.out.println("Attached an observer");
        }
        /**
         * 删除观察者对象
         * @param observer    观察者对象
         */
        public void detach(Observer observer){
            
            list.remove(observer);
        }
        /**
         * 通知所有注册的观察者对象
         */
        public void nodifyObservers(String newState){
            
            for(Observer observer : list){
                observer.update(newState);
            }
        }
    }
    //具体主题角色类
    public class ConcreteSubject extends Subject{
        
        private String state;
        
        public String getState() {
            return state;
        }
    
        public void change(String newState){
            state = newState;
            System.out.println("主题状态为:" + state);
            //状态发生改变,通知各个观察者
            this.nodifyObservers(state);
        }
    }
    //抽象观察者角色类
    public interface Observer {
        /**
         * 更新接口
         * @param state    更新的状态
         */
        public void update(String state);
    }
    //具体观察者角色类
    public class ConcreteObserver implements Observer {
        //观察者的状态
        private String observerState;
        @Override
        public void update(String state) {
            /**
             * 更新观察者的状态,使其与目标的状态保持一致
             */
            observerState = state;
            System.out.println("状态为:"+observerState);
        }
    }
    //客户端类
    public class Client {
        public static void main(String[] args) {
            //创建主题对象
            ConcreteSubject subject = new ConcreteSubject();
            //创建观察者对象
            Observer observer = new ConcreteObserver();
            //将观察者对象登记到主题对象上
            subject.attach(observer);
            //改变主题对象的状态
            subject.change("new state");
        }
    }

    在运行时,这个客户端首先创建了具体主题类的实例,以及一个观察者对象。然后,它调用主题对象的attach()方法,将这个观察者对象向主题对象登记,也就是将它加入到主题对象的聚集中去。客户端调用主题的change()方法,改变了主题对象的内部状态。主题对象在状态发生变化时,调用超类的notifyObservers()方法,通知所有登记过的观察者对象。

    推模型和拉模型

    在观察者模式中,又分为推模型和拉模型两种方式。

    推模型:主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

    拉模型:主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

    根据上面的描述,前面的例子就是典型的推模型,下面给出一个拉模型的实例。

    拉模型的抽象观察者类:拉模型通常都是把主题对象当做参数传递。

    public interface Observer {
        /**
         * 更新接口
         * @param subject 传入主题对象,方面获取相应的主题对象的状态
         */
        public void update(Subject subject);
    }
    //拉模型的具体观察者类
    public class ConcreteObserver implements Observer {
        //观察者的状态
        private String observerState;
        
        @Override
        public void update(Subject subject) {
            /**
             * 更新观察者的状态,使其与目标的状态保持一致
             */
            observerState = ((ConcreteSubject)subject).getState();
            System.out.println("观察者状态为:"+observerState);
        }
    
    }
    //拉模型的抽象主题类,拉模型的抽象主题类主要的改变是nodifyObservers()方法。在循环通知观察者的时候,也就是循环调用观察者的update()方法的时候,传入的参数不同了。
    public abstract class Subject {
        /**
         * 用来保存注册的观察者对象
         */
        private    List<Observer> list = new ArrayList<Observer>();
        /**
         * 注册观察者对象
         * @param observer    观察者对象
         */
        public void attach(Observer observer){
            
            list.add(observer);
            System.out.println("Attached an observer");
        }
        /**
         * 删除观察者对象
         * @param observer    观察者对象
         */
        public void detach(Observer observer){
            
            list.remove(observer);
        }
        /**
         * 通知所有注册的观察者对象
         */
        public void nodifyObservers(){
            
            for(Observer observer : list){
                observer.update(this);
            }
        }
    }
    //拉模型的具体主题类,跟推模型相比,有一点变化,就是调用通知观察者的方法的时候,不需要传入参数了。
    public class ConcreteSubject extends Subject{
        
        private String state;
        
        public String getState() {
            return state;
        }
    
        public void change(String newState){
            state = newState;
            System.out.println("主题状态为:" + state);
            //状态发生改变,通知各个观察者
            this.nodifyObservers();
        }
    }

    两种模式的比较

    推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。

    推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。

    JAVA提供的对观察者模式的支持

    在JAVA语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。

    Observer接口

    这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。

    public interface Observer {
        void update(Observable o, Object arg);
    }

    Observable类

    被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。

    public class Observable {
        private boolean changed = false;
        private Vector obs;
       
        /** Construct an Observable with zero Observers. */
    
        public Observable() {
        obs = new Vector();
        }
    
        /**
         * 将一个观察者添加到观察者聚集上面
         */
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
        }
    
        /**
         * 将一个观察者从观察者聚集上删除
         */
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
    
        public void notifyObservers() {
        notifyObservers(null);
        }
    
        /**
         * 如果本对象有变化(那时hasChanged 方法会返回true)
         * 调用本方法通知所有登记的观察者,即调用它们的update()方法
         * 传入this和arg作为参数
         */
        public void notifyObservers(Object arg) {
    
            Object[] arrLocal;
    
        synchronized (this) {
    
            if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    
        /**
         * 将观察者聚集清空
         */
        public synchronized void deleteObservers() {
        obs.removeAllElements();
        }
    
        /**
         * 将“已变化”设置为true
         */
        protected synchronized void setChanged() {
        changed = true;
        }
    
        /**
         * 将“已变化”重置为false
         */
        protected synchronized void clearChanged() {
        changed = false;
        }
    
        /**
         * 检测本对象是否已变化
         */
        public synchronized boolean hasChanged() {
        return changed;
        }
    
        /**
         * Returns the number of observers of this <tt>Observable</tt> object.
         *
         * @return  the number of observers of this object.
         */
        public synchronized int countObservers() {
        return obs.size();
        }
    }

    这个类代表一个被观察者对象,有时称之为主题对象。一个被观察者对象可以有数个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用Observable的notifyObservers()方法,此方法调用所有的具体观察者的update()方法,从而使所有的观察者都被通知更新自己。

    怎样使用JAVA对观察者模式的支持

    被观察对象叫做Watched;而观察者对象叫做Watcher。Watched对象继承自java.util.Observable类;而Watcher对象实现了java.util.Observer接口。另外有一个Test类扮演客户端角色。

    //被观察者Watched类
    public class Watched extends Observable{
        
        private String data = "";
        
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            
            if(!this.data.equals(data)){
                this.data = data;
                setChanged();
            }
            notifyObservers();
        }
    }
    //观察者类
    public class Watcher implements Observer{    
        public Watcher(Observable o){
            o.addObserver(this);
        }   
        @Override
        public void update(Observable o, Object arg) {     
            System.out.println("状态发生改变:" + ((Watched)o).getData());
        }
    }
    //测试类
    public class Test {
        public static void main(String[] args) {
            //创建被观察者对象
            Watched watched = new Watched();
            //创建观察者对象,并将被观察者对象登记
            Observer watcher = new Watcher(watched);
            //给被观察者状态赋值
            watched.setData("start");
            watched.setData("run");
            watched.setData("stop");
        }
    }

    Test对象首先创建了Watched和Watcher对象。在创建Watcher对象时,将Watched对象作为参数传入;然后Test对象调用Watched对象的setData()方法,触发Watched对象的内部状态变化;Watched对象进而通知实现登记过的Watcher对象,也就是调用它的update()方法。 

    转载于:https://www.cnblogs.com/wade-luffy/p/5812563.html

    展开全文
  • JS dataTransfer 对象在拖拽中使用

    千次阅读 2016-09-22 22:37:14
    dataTransfer对象提供了对于预定义剪贴板格式访问,以便拖曳...dropEffect:设置或返回拖放目标允许发生的拖放行为。如果此处设置拖放行为不再effectAllowed属性设置多种拖放行为之内,拖放操作将会失败。
  • dataTransfer对象提供了对于预定义剪贴板格式访问,以便拖曳操作中使用。...dropEffect:设置或返回拖放目标允许发生的拖放行为。如果此处设置拖放行为不再effectAllowed属性设置多种拖放行为之内
  • 矿井作业人员作为生产活动的直接施行者,其作业...对意向型和无意向型不安全行为的影响因素进行了列举分析,结合影响因素解释了对作业人员的行为选择过程,并此基础初步构建了矿井作业人员不安全行为发生机理模型。
  • dataTransfer对象提供了对于预定义剪贴板格式访问,以便拖曳操作中使用。...dropEffect:设置或返回拖放目标允许发生的拖放行为。如果此处设置拖放行为不再effectAllowed属性设置多种拖放行为之内
  • dataTransfer对象提供了对于预定义剪贴板格式访问,以便拖曳操作...dropEffect:设置或返回拖放目标允许发生的拖放行为。如果此处设置拖放行为不再effectAllowed属性设置多种拖放行为之内,拖放操作将会...
  • 一、事件流 // 事件冒泡、事件捕获 // 事件冒泡: 当一个元素事件触发了,该事件会当前元素所有祖先元素中依次来触发(从里到外效果) // 事件捕获:从外到里 ...// 事件发生时会元素节点之间按照特定...
  • 【C++】 类与对象

    2019-08-02 17:03:25
    面向对象是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物解决整个问题的过程中所发生的行为。 用送外卖来举例 C语言:先找到外卖小哥A,然后让A到指定的卖家...
  • 当实例对象存储堆区时:实例对象存放堆区,对实例引用存在线程栈,而实例元数据存在方法区或者元空间。 那么,实例对象一定是存放堆区吗?答案是不一定,如果实例对象没有发生线程逃逸行为,就会被存储...
  • Java初始化对象的工具 - 构造器

    千次阅读 多人点赞 2020-03-12 02:06:15
    那么具体行为的发生,也就是方法调用要靠对象来完成,同时属性值也要附着在对象上才有意义。创建对象过程被叫做类实例化,或者称为对象初始化,在这个过程中需要使用就是new关键字和类构造器。
  • 老样子,先抛出个问题...with/as语句设计是作为try/finally的替代方案,就像try/finally语句一样,with/as语句也是用于定义必须执行的终止或者说是清理的行为,无论此过程中是否发生异常! 但不同的是,with语句...
  • 面向对象的特征

    2019-03-29 15:55:38
    1.继承:一个父类基础拓展子类特有属性和行为,生成一个新子类,原则:父类存放共性,子类存放特性 (1)代码理解: 1)三个类都有重复代码,可以把这部分内容 抽出去,抽出去放到另外一个类里面; a....
  • Java面向对象的思考

    2014-03-10 16:47:27
    堆和栈的完美结合 ==》就是面向对象的完美体现    面向对象方式的程序与以前结构化的程序执行没有任何区别。  但是,面向对象的引入,使得对待...而对象的行为(方法),  就是运行逻辑,放在栈中。我们
  • 将数据以及这些数据上的操作封装一起 具有类类型的变量 范畴 抽象概念 类的一个具体 状态 静态,不携带任何数据,本身额不存在内存 动态,拥有自己的属性和行为,属性可随行为发生改变 .....
  • 本文主要介绍Java面向对象23种设计模式中行为型模式中部分设计模式,接Java面向对象设计模式学习(五)。 四、责任链模式 责任链模式,也称职责链模式,主要面向以下问题:一个请求有多个对象可以处理,但每个...
  • 这提供了一种管理状态对象的受控方法,并状态对象的某些部分发生更改时得到通知。 它确保状态更新是原子性,这意味着仅状态已完全更新时才通知更改订阅者更改。 订户调用时也将获得最新状态和更改。 它需要...
  • 面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事务解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易于维护、复用性高、...
  • 面向对象思想

    千次阅读 2013-04-13 16:05:08
    1、基本概念 1.1 类和对象初探 类:描述了具有相同特性(属性)和相同行为(操作方法)的对象程序中,类就是数据类型。...变量:可以发生变化量;常量:不发生变化。 函数:实现某种
  • 考察以下场景:ATM机操作中,当持人订阅了短信,邮件等通知话,当有人对卡面ATM机有操作时,就会实时发出短信或邮件。这就是所谓观察者模式。 当一个对象发生变化时,对它变化感兴趣其它对象(订阅了这...
  • 前言 要搞懂面向对象和面向过程...这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁受影响。与面向对象明显不同就是封装、继承、类。简写为POP 之前那篇博客看到这个比喻,觉得特别恰当,挺容
  • 目录一、基本知识点1、面向过程编程视角聚焦于解决问题的步骤,特点是每一步的行为都基本固定,且强烈依赖于下文。一旦程序编码完毕,程序内部各步骤代码就形成一个强相关的整体,不方便频繁的修改和扩展。这种...
  • 面向对象

    2020-07-24 20:17:33
    ​ 思想特点:1、更符合生活中思想行为习惯 ​ 2、让复杂事情简单化 ​ 3、角色发生改变(执行者————》指挥者) 2、面向对象的三个特征 ​ 封装、继承、多态 3、成员变量和局部变量区别? 1、类中...
  • 面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易...
  • 从形式看,封装不过是将数据和行为组合一个包中,并对对象的使用者隐藏了数据实现方式。对象数据称为实例域(instance field),操纵数据过程称为方法(method)。对于每个特定类实例(对象)都有一组...
  • HTML 事件是发生在 HTML 元素上的行为,如网页完成加载,输入字段被修改,按钮被点击等等,事件可以被 JavaScript 侦测到。 如用户点击一个按钮,我们给这个按钮上注册的click事件就会被触发,用一个函数“应对”...

空空如也

空空如也

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

发生在对象上的行为