DDD for Rails Developers


DDD for Rails Developers. Part 1: Layered Architecture.

Source:http://rubysource.com/ddd-for-rails-developers-part-1-layered-architecture/

What is DDD?

There are many kinds of complexity that you have to deal with developing software and different kinds of applications will have very different sets of problems you need to solve. If you are building the next Twitter,scalability and fault-tolerance are the problems you are probably fighting. On the other hand,these problems are almost never an issue when working on enterprise applications. The complex domain is what you tackle when developing enterprise software. Business processes of a lot of companies are far from being trivial. Thus,refining the domain that provides loose coupling and will be flexible and maintainable in the future is extremely hard and requires a lot of practice and knowledge.

在你开发的软件和各种应用程序中,你需要解决各种不同的问题集,他们存在诸多复杂性。如果你是在构建下一个Twitter,可扩展性和容错是你可能需要攻克的问题。另一方面,当为开发一个企业应用时,这些问题几乎从来没有。复杂领域是当你开发企业软件时你要解决什么。很多公司的业务流程远离繁琐。因此,提炼领域知识达到松耦合和在将来灵活及可维护是非常困难的,需要大量的实践和知识。

The Book

Eric Evans – the author of Domain Driven Design – coined the set of practices and terminology helping in tackling domain complexity. His book is a must read for every developer working on enterprise applications and I highly recommend it.

Eric Evans – 《领域驱动设计》作者 – 创建了一套做法和术语来帮助解决领域的复杂性。他的这本书对于每位企业应用开发者是必读的,强烈推荐。

DDD and Rails

Working more and more on large rails applications I’ve noticed that in many ways DDD and Rails contradict each other. Therefore,I’ve decided to write a short series of articles,which will be my attempt to reconcile both paradigms and to find a way to use DDD while not fighting Rails.

工作在越来越多的rails大型应用程序开发,我注意到在许多方面DDD和Rails彼此矛盾。因此,我决定写一个简短的文章系列,这将是我试图调和这两范式,并找到一种方法来使用DDD和Rails.

Before I start,I’d like to mention that I’m going to write about introducing DDD concepts to an existing application. Therefore,despite that Uncle Bob’s approach (check out this awesome talk) may look appealing,introducing it to an existing Rails application with hundreds of thousands lines of code is,probably,the last thing I want to do. Hence,everything I’m going to write about here is,in some way,a compromise.

在我开始前,我想提的是我打算写关于将DDD概念引入到一个现有的应用程序。因此,Bob叔叔的(check out this awesome talk)做法看起来可能有吸引力,将它引入到现在的应用程序(可能有成百上千行代码),这是我最后想做的。故,我需要在这里写明一下,某种程度上是一种妥协。

Layered Architecture 分层体系结构

One of the core concepts of Domain Driven Design is the layered architecture. Let’s take a look at what it is,what kind of benefits it brings,and how a typical Rails application violates this fundamental concept.

领域驱动的核心概念之一,是分层体系结构。让我们来看看它是什么,它带来了什么好处,和一个典型的Rails应用程序如何违反了这个基本概念。

First of all,the term “Layered Architecture” means that you partition an application into layers:

首先,“分层体系结构”意味着你的应用程序进行分区到层。

*、User Interface. Responsible for showing information to the user and processing the user’s input.

用户界面。负责显示信息给用户并处理用户输入。

*、Application Layer. This layer is supposed to be thin and it should not contain any domain logic. It can have functionality that is valuable for the business but is not “domain” specific. This includes generating reports,sending email notifications etc.

应用层。本层应该是薄的,它不应该包含任何领域逻辑。它可以是有价值的业务功能,但不是领域具体功能。这包括生成报表,发邮件通知等等。

*、Domain Layer. Responsible for describing business processes. Abstract domain concepts (including entities,business rules) must be contained in this layer. In contrast,persistence,message sending do not belong here.

领域层。负责用于描述业务流程。抽象领域概念(包括实体,业务规则)必须被包含在本层。与此相反,持久性、消息发送不属于这里。

*、Infrastructure Layer. Responsible for persistence,messaging,email delivery etc.

基础架构层。负责持久化,消息,电子邮件传递等。

The most important idea behind this architecture is that every layer should depend only on the layers beneath it. Thus,all dependencies have the same direction. For instance,the domain layer might depend on some pieces of infrastructure but not the other way around.

这种架构背后最重要的思想,每一层都应该只依赖于它下面的图层。因此,所有的依赖关系具有相同的方向。例如,领域层可能依赖一些基础架构,但周围没有其它方式。

Layered Architecture and Rails

Now let’s take a look at the most common violations of the layered architecture I see in typical Rails applications:

现在,让我们来看一下常见的违反分层结构的典型Rails应用程序。

*、Domain objects serialize themselves into JSON or XML. In my opinion,there is no big difference between representing an object as a piece of html and representing it as lf a piece of JSON. Both are meant to be consumed by external systems,both are parts of the UI layer. So every time you override the as_json method you violate the core idea of the layered architecture – you change the direction of your dependencies between layers. Your domain objects start being aware of the UI.

领域对象序列化到JSON或XML。 在我看来,用一大片html和用一大片JSON代表一个对象没有大的区别。两者都意味着要消耗外部系统,都是UI层的组成部分。所以每次覆盖as_json方法,你都违背了分层结构的核心思想-你改变了你的层与层之间的依赖关系的方向。你的领域对象开始意识到UI。

*、Controllers contain big chunks of business logic. Usually,it’s a few calls to the domain layer and then persisting changes using low-level methods such as update_attributes. So next time you go and call update_attributes inside a controller,stop and think again: most likely you are doing it wrong. Thankfully,most Rails developers have already realized that this kind of controllers hard to maintain. I believe,there is no excuse for doing it – even for small applications.

控制器包含大块大块的业务逻辑。通常,这是一些调用领域层然后坚持使用低级别的方法,如update_attributes。因此,下一次你去控制器里面调用update_attributes,停止和重新思考:很可能你的做法是错误的。值得庆幸的是,大多数Rails开发人员已经意识这种控制器是难以维持的。我相信,没有借口这样做,即使对于一个小的应用程序。

*、Domain objects perform all sorts of infrastructure related tasks. As a community we agreed not to write fat controllers,but we hit another problem – “Swiss army knife” domain objects. Such objects,besides describing the business,can also connect to a remote service,generate a pdf or send an email. If you come across such an object it needs to be split into at least two: one that is responsible for the domain knowledge and others for performing infrastructure related tasks.

领域对象执行各种基础信息相关的任务。作为一个社会,我们同意不写胖控制器,但我们击中了另外一个问题-“瑞士军刀”领域对象。这样的对象,除了描述业务,也可以连接到一个远程服务,生成一个pdf或发送一封邮件。如果你遇到这样的一个对象,它需要被分割成至少两个:一个是负责领域知识和进行其它基础架构相关的任务。

Most of these problems can be fixed. Let’s take a look at all of them to see what can be done.

这些问题都可以被解决。让所有人都来看看,看看有什么可以做的。

*、Domain objects know too much about the database. If you extend your domain objects fromActiveRecordyou couple the domain layer to the infrastructure layer. Your domain objects play two roles at the same time. This kind of coupling makes them almost impossible to test in isolation.

领域对象知道太多关于数据库。如果你扩展你的领域对象从ActiveRecord一对领域层到基础架构层。你的领域对象同时扮演两种角色。这种耦合使它几乎不可能单独测试。

Make a Separate Class for JSON Serialization 为JSON序列编制一个单独的类

You should never override as_json or other similar methods in your domain classes. Remember,that the responsibility of the domain layer is to reflect the business concepts. I have not worked on a domain where JSON was an important concept. Therefore,if you are not writing a JSON parser,move all JSON related stuff out of your domain.

你永远不应该覆盖as_json或其它类似的方法,在你的领域类中。请记住,领域层的责任是反映企业经营理念。我没有在一个以JSON作为重要理念的领域工作过。因此,如果你不是写一个JSON解析器,请将所有的JSON相关的东西移出你的领域。

Imagine a controller having an action that looks like this:

设想一个控制器有如下行为:

def update
  p = Person.find(params[:id])
  if p.update_bank_information params[:bank_information]
    render :json => p.as_json
  else
    render :json => "some kind of error"
  end
end

This Gistis brought to you usingSimple Gist Embed.If you need to customize the JSON serialization of the Person class,don’t do it inside the Person class. Create a separate module (e.g.PersonJsonSerializer,PersonJsonifier) that will be responsible for it.如果你需要为Person类定制JSON序列化,不要在Persion类里面做。创建一个单独的module(例如:PersonJsonSerializer,PersonJsonifier)来完成它。

module PersonJsonSerializer
  def self.as_json person
    if person.errors.present?
      person.as_json(:root => "root-attrs")
    else
      {:errors => person.errors.full_messages}
    end
  end
end
This Gist is brought to you using Simple Gist Embed.ddd_example1_2.rbNow,your controller will look like this:

现在你的控制器看起来将像这样:

def update
  p = Person.find(params[:id])
  p.update_bank_information params[:bank_information]
  render :json => PersonJsonSerializer.as_json(p)
end
What have we achieved by moving the JSON serialization to a separate class?将JSON序列化移到一个单独的类,我们得到了什么?

*、Our domain remains abstract without any knowledge about the UI.

我们的领域仍然保持抽象并没有任何UI的相关知识。

*、We split two responsibilities: being a person and serializing it to a JSON. If we didn’t do it,the Single Resposibility Principle would have been violated.

我们分离了两种责任:作为一个person和将它序列化成JSON。如果我们没这么做,单一责任原则就受到了侵犯。

*、In addition,we made our controller easier to test. You don’t even need a real person object to test the serializer. Just stub it out. Also,you don’t need to check two branches of that if statement in your functional tests. You can just stub the serializer out. So instead of two tests,we have only one.

另外,我们使我们的控制器更加容易测试。你甚至不需要一个真实的person对象来测试这个序列化。你只要把序列化弄灭。所以我们不是两个测试,只有一个。

Controllers Don’t Contain Any Logic 控制器不包含任何逻辑

In a nutshell,controllers should not contain any logic apart from parsing the user’s input or rendering proper templates. If you have a piece of business logic inside a controller move it to the domain layer. There is a misunderstanding that the domain layer consists of only persisted objects. And when you have a complex operations involving several objects you need to orchestrate it inside a controller. This is just wrong. If none of your entities seems to be a good place for this functionality,create a service class (or a module) and put it there.

简而言之,控制器不应该包含任何逻辑,除了解析用户的输入和呈现任何适当的模板。如果你有一块业务逻辑在控制器里,请将它移到领域层。对领域层有一种误解,认为只存在持久对象。当你有一个复杂操作调用多个对象,你需要协调它在一个控制器内。这是绝对错误的。如果没有你的实体,对于这个功能似乎是一个好地方,创建一个服务类(或一个模块)并把它放在里面。

Imagine,we have such an action:

设想,我们有这样一个行为:

def sell_book
  @book = Book.find(params[:id])
  if book.sold?
    book.errors.add :base,"The book is already sold"
  else
    book.sell
  end
end
A better way to do it:

一个更好的作法:

def sell_book
  @book = Book.find(params[:id])
  BookSellingService.sell_book(@book)
end
We’ve achieved:

我们已经得知:

*、The controller is easier to test. When before we would have to have two tests,after our change is done we need only one.

该控制器更容易测试。在完成改变之前我们需要两个测试,而后我们只需要一个。

*、In addition to making our controller easier to test,we’ve made the business logic part explicit and also easier to test.

除了使我们的控制器更容易测试以外,我们已使业务逻辑部分明确,也容易测试。

Domain Objects Should Not Know Anything About Infrastructure Underneath 领域对象不应该知道任务关于基础架构下面的

In short,the domain layer should be abstract. This means that all dependencies on any kind of external services don’t belong there. Imagine that we are developing a blogging engine and one of the requirements is to send a tweet each time a post is published. What is considered to be an “OK” practice is to handle it in the after_create hook:

总之,领域层应该是抽象的。这意味着,所有依赖任何外部的服务都不属于那里。设想,我们正在开发一个blog引擎的要求之一是每次tweet后发表。使用after_create 钩子来处理它,是一个被认为好的实践方法:

class Post < ActiveRecord::Base
  has_many :comments
  after_create :send_tweet

  def send_tweet
    twitter = Twitter.login(username,password)
    twitter.send_tweet generate_tweet_from_subject(subject)
  end
end
Even though it’s only a few lines of code and doesn’t look like much,it is a big deal. Firstly,you’ll have to stub out the Twitter service in all unit tests,which is not what you usually want to test. Secondly,it violates the Single Responsibility Principle,as storing information about a post is not the same as sending notifications to Twitter. Sending this kind of notifications is rather a side effect,an additional service,which is not a part of the core domain. Finally,Twitter can be unavailable and accessing it synchronously is not a good idea anyway.

尽管它只有几行代码,看起来并不像很多,这是一个大处理。首先,你必须在所有单元测试里面弄灭Twitter服务,那不是你通常想要测试的。其次,它违反了单一职责原则,如存储信息后发送通知到Twitter是不一样的。发送这样的通知相当于是一个附加服务,它不是核心领域的一部份。最后,Twitter能否被有效和同步访问,总之不是一个好主意。

There are several ways of decoupling our model from infrastructure. One of them is to observe the Post class:

有多种方法从基础架构来解耦我们的模型。其中之一是观察者模式Post类:

class Post < ActiveRecord::Base
  has_many :comments
end

class TwitterNotification < ActiveRecord::Observer
  observe :post

  def after_create post
    twitter = Twitter.login(username,password)
    twitter.send_tweet generate_tweet_from_subject(post.subject)
  end
end
Even better way of doing it is moving all the responsibilities of generating tweets from the observer to theTwitterServiceclass:

更妙的方法是,把所有负责生成tweets从observer移到TwitterService类:

class TwitterNotification < ActiveRecord::Observer
  observe :post

  def after_create post
    TwitterService.send_tweet post.subject
  end
end

class TwitterService
  def self.send_tweet subject
    twitter = Twitter.login(username,password)
    twitter.send_tweet generate_tweet_from_subject(subject)
  end
  
  ...
end
In summary,extracting this responsibility from the Post class has helped us to achieve the following:

综上所述,从Post类提取这个职责,帮助我们实现以下:

*、It’s easier to test the Post class as you don’t need to stub Twitter out in every test.

方便测试Post类,你不需要在每个测试里弄灭Twitter。

*、Testing theTwitterServiceclass is also straightforward. We even don’t need an instance of Post to do it.

测试TwitterService类也很简单。我们甚至不需要实例化Post。

*、We made the domain boundaries of our application explicit. We have the core domain and we have Twitter integration: two completely different domains and,as a result,two classes separated from each other by an observer.

我们做的让我们的应用程序的领域边界明确。我们有核心领域,我们有Twitter整合:两个完成不同的领域并作为一个结果,用一个观察者将两个类彼此分离。

*、Having an object responsible for integration with Twitter allows us to change its functionality (for instance,make it asynchronous) without touching the core domain.

有一个对象负责整合Twitter,不触及核心领域,以便我们改变它的功能(例如,使它异步)。

Isolate ActiveRecord 孤立的ActiveRecord

I left the hardest problem to the end: coupling withActiveRecord. Since the domain should be abstract the database schema should not affect how we design our entities. However,we all live in the real world and that’s why it never happens. The following things you should consider developing your domain: what objects are transient,if it’s easy to map the graph of objects into your relation schema,performance etc. Taking all these properties into account is important. However,you should not design your domain in a way that it’s impossible to test it without using the database.

我把最难的问题留在了最后:ActiveRecord的偶合。由于领域数据库架构应该是抽象的,不应该影响我们如何设计我们的实体。但,我们每个人都生活在现实世界中,这就是为什么它从未发生过。接下来的事你应该考虑开发你的领域:哪些对象是瞬态的,如果是简单的图形对象映射到你的关系模式,性能等。考虑到所有这些属性是很重要的。但是,你不应该设计你的领域的方式,它不可能不使用数据库来测试它。

The DDD approach is to extract a separate layer responsible for persistence. For instance:

DDD的作法是设计一个单独的层来负责持久。例如:

class PostsRepository
  def find_by_id id
    ...
  end

  def new_posts_of_author author
    ...
  end

  def save post
    ...
  end
end
Having a separate object responsible for persistence simplifies testing a lot and makes it possible to provide alternative implementations. For instance,it’s a common practice to implement SQLPostsRepository andInMemoryPostsRepository. Therefore,you can use the first once for your integration tests and the second one for your unit tests. When your domain is not coupled toActiveRecord,implementing repositories is a way to go. However,it won’t give you much when all your domain objects extendActiveRecord::Base. Thus,I use a compromise variant of the repository pattern: put all persistence related methods into a separate module and just extend it.

有一个单独的对象负责持久化,大量简化了测试,使它可以提供替代实现。例如,它是一种常见的做法,实现SQLPostsRepository 和InMemoryPostsRepository。因此,你可以使用第一个为你的集成测试,第二个为你的单元测试。当你的领域没有耦合到ActiveRecord,要实现资源库有很长的路要走。但,它不会给你太多,当所有的领域对象扩展ActiveRecord::Base。因此,我用一个折衷的存储模式变化:把所有持久化关系方法放到一个单独的模块并扩展它。

module PostsRepository
  def new_posts_of_author author
    ...
  end
end

class Post < ActiveRecord::Base
  extend PostsRepository
end

Post.new_posts_of_author "Jim"
Having a separate module comprising all the logic related to persistence benefits us in the following ways:

拥有一个独立的模块,包括所有逻辑关系到持久的好处有如下方面:

*、It provides separation of concerns. Post is a business object andPostsRepositoryis responsible for persistence.

它提供了分离关注点。Post是一个业务对象和PostsRepository负责持久化。

*、It makes mocking the persistent layer straightforward.

它使mocking持久层简单。

*、Providing an in-memory implementation of Repository becomes straightforward too: Post.extendInMemoryPostsRepository

提供了一个在内存中执行也变得简单的库:Post.extendInMemoryPostsRepository

Summary 总结

Tackling domain complexity is hard. The bigger your application grows the harder it gets. Jamming everything together works fine for small apps but breaks apart when you have to deal with large applications. Just applying the principles of the layered architecture can help a lot.

应对领域复杂度是很难的。你的应用程序增长越大,就越难。一切干扰正常工作的小应用程序都必须被分开,当你处理大型应用程序时。只应用这原则分层架构就能有很大的帮助。

However,there is much more in Domain Driven Design that just partitioning your application into layers: entities and values,services and factories,aggregate roots,domain boundaries,anticorruption layers and more. Understanding these concepts and principles and applying them can be really useful for all Rails developers. I’m going to continue writing on these topics.

然而,在领域驱动设计中还有更多,你的应用程序分解到层:实体和值,服务和工厂,Aggregate根,领域边界,反腐层和更多。了解这些概念和原则,并应用他们,对Rails开发者很有用。我将继续写这些主题。

DDD for Rails Developers. Part 2: Entities and Values


Source:http://rubysource.com/ddd-for-rails-developers-part-2-entities-and-values/

In my previous article about DDD for Rails Developers,I talked about using the layered architecture for tackling domain complexity. I showed a few typical violations of the layered architecture and gave some advice on how to fix them.

在我的上篇关于“DD for Rails Developers”文章中,我谈到了关于使用层架构来解决领域的复杂性。我展示了一些典型的违反分层架构,并就如何解决这些问题提出了些建议。

The Building Blocks of Domain Driven Design 为领域驱动设计构建块

This time I’d like to start talking about the building blocks of Domain Driven Design,and how they can be used for modeling.

这次我想开始谈关于领域驱动设计构建块,并如何将它他应用到建模中。

Entities and Values 实体和值

In Domain Driven Design,an important distinction is drawn between Entities and Value Objects.

在领域驱动设计中,一个重要的区别是绘制实体和值对象之间。

*、“An Entity is an object defined not by its attributes,but by a thread of continuity and identity.” An example of an Entity would be a bank account. Many bank accounts can exist in our system at the same time. Some of them can be assigned to the same branch or have the same owner,but it’s important for our system to treat them as different accounts as long as they have different identities. In case of a Rails application,an identity of an Entity is usually represented by an auto-generated primary key.

“一个实体定义的对象,是由连续性的标识线,而不是由他的属性。”以一个银行帐户作为一个实体举例。许多银行帐户可以在相同的时间,在我们的系统中存在。他们中的一些可以被分配到同一分支或相同的所有者,但只要他们具有不同的身份我们的系统就应该以不同的帐户来对待,这对于我们的系统非常重要。在Rails应用程序情况下,一个实体的身份通常由一个自动生成的主键代表。

*、“A Value Object is an object that describes some characteristic or attribute but carries no concept of identity.” As there is no identity,two Value Objects are equal when all their attributes are equal. An example of a Value Object would be Money.

“值对象是一个对象,它描述某些特征和属性,但带有没有身份概念。”由于没有身份,两个值对象是相等的当他们的属性相同。可以将钱做为值对象的一个例子。

More on Entities 关于实体更多

又称为Reference object,很多对象不是通过它们的属性定义的,而是通过一连串的连续事件和标识定义的。

In the Rails community we have a good understanding of what Entities are. Practically,almost every object extendingActiveRecord::Base is an Entity.

在Rails社区中,对于什么是实体我们有一个很好的理解。实际上,几乎每个扩展ActiveRecord::Base的对象都是一个实体。

Entities have the following characteristics:

实体具有以下特征:

*、Entities care about their identity. The identity is usually represented by an auto-generated primary key,which is used to compare two Entities.

实体关心他们的身份。这个身份通过是由一个自动生成主键负责,它是用来区分两个实体。

*、They are mutable. The only field that cannot be changed is the primary key.

他们是可变的。只有主键是唯一不能改变的。

*、They have long lives. Most Entities are never deleted from the database.

他们有很长的生命。大多数实例都从未从数据库中删除。

*、Since Entities are mutable and long-lived,they usually have a complex life cycle:

由于实体是可变的,寿命长,他们通常有一个复杂的生命周期:

* An Entity Object is created. 一个实体对象是被创建。
* It is saved in the database. 它是被保存在数据库中。
* It is read from the database. 从数据库中读取。
* It is updated. 被更新。
* It is deleted (or marked as deleted). 被删除(或被标记为删除)
Due to mutability and a complex life cycle,dealing with Entities is complicated. Therefore,every time you define an Entity,think over how you are going to persist it,what attributes you have to make mutable,what Aggregate (more on Aggregates in the next post) should contain it,etc.

由于可变性和一个复杂的生命周期,处理实体是复杂的。因此,每次你定义一个实体,思考你要如何维持它,有什么属性你必须做出可变,有什么Aggregate(更多关于Aggregate在接下来的文章中)应该包含,等等。

More on Values 更多关于值

Value Objects,on the other hand,are underused in the Rails community. As a result,most Rails applications suffer from Primitive Obsession:

另一方面,在Rails社区中未充分利用的值对象。其结果是,大多数Rails应用程序遭受从原始的痴迷。

*、Primitive values such as integers and strings are used to represent important concepts of the domain.

如整数和字符串的原始值是用来表示领域的重要概念。

Firstly,as the logic of dealing with a group of attributes is spread out over dozens of classes,Primitive Obsession is usually a source of code duplication. Secondly,using primitives instead of domain specific abstractions clutters high-level services with unnecessary details and makes the intent of your code unclear. Value Objects offer a good remedy for Primitive Obsession.

首先,作为处理一组属性的逻辑是分散在各类中,原始的痴迷通常是源码重复。 其次,使用原语替代领域特定的抽象混乱高层服务和不必要的细节导致你的代码目的不清晰。值对象提供了一个很好的补救原始痴迷。

Value Objects have the following characteristics:

值对象具有以下特征:

*、Value Objects have no identity.

值对象没有身份。

*、They are immutable. For instance,adding 3 to 5 does not change any of these values. A new value is returned instead. Ideally,working with Value Objects should feel like working with primitives.

他们是一成不变的。例如,3+5不改变任何这些值。以一个新的值替代返回。在理想的情况下,工作在值对象应该感觉像使用原语。

*、Value Objects do not have a complex life cycle.

值对象没有复杂的生命周期。

Creating Value Objects in Rails 在Rails中创建值对象

There are many ways of creating and managing Value Objects in Rails,and I’d like to show three of them.

在Rails中有许多方法创建和管理值对象,我喜欢展示的这三种。

Use composed_of 使用composed_of

设想,我们写了另外一个blog应用。我们已经决定赞成这种模式:

*、Blog has many Posts.

Blog有很多文章。

*、Every Post has many Comments.

每篇文章都有很多评论。

*、Posts and Comments have location attributes associated with them.

文章和评论都有他们相关联的位置属性。

We can create Posts and Comments this way:

我们可以创建文章和评论:

blog = Blog.create
post = blog.make_post text: 'great post',location_country: 'Canada',location_city: 'Toronto'
post.make_comment text: 'great comment',location_city: 'Toronto'
We can also search them by their location:

我们也可以通过他们的位置查询:

class Blog < ActiveRecord::Base
  ...

  def all_posts_from country,city
    ...
  end

  def all_comments_from country,city
    ...
  end
end
In addition to this,we have a presenter to display the location attributes:

除了这一步,我们有一个演示者来显示位置属性。

class LocationPresenter
  def initialize country,city
    ...
  end
end
As you can see,we always use the country and city attributes together. Even when I was explaining the application behavior I wrote: ‘by their location’. Apart from having duplication we’ve missed an important part of our domain. There is a notion of a location that our model (our code) does not reflect. Let’s fix it.

正如你所看到的,我们一直一起使用国家和城市属性。即使当我解释这个应用程序行为,写道:“根据他们的位置”。除了有重复,我们已经错过了我们领域的重要组成部分。位置有一个概念,而我们的模型(我们的代码)没有反映出来。让我们来解决它。

Let’s start with defining a class that will encapsulate the location attributes:

让我们开始定义一个类,将封装位置属性:

class Location < Struct.new(:country,:city)
end
Now we need to configure Post to wrap location_country and location_city into an instance of Location:

现在我们需要配置Post封装location_country 和 location_city, 进入位置的一个实例:

class Post < ActiveRecord::Base
  composed_of :location,mapping: [%w(location_country country),                                   %w(location_city city)]

  def self.all_posts_from location
    Post.where location: location
  end
end
This is the result of our refactoring:

这是我们重构的结果:

blog.make_post text: 'great post 2',location: Location.new('Canada','Toronto')
The biggest gain after this refactoring is making an important concept of our domain explicit in the source code. Also,extracting a Value Object helped us to raise the level of abstraction,which leads to more readable code:

这个重构后最大的收获是我们的源码中显示了领域中的一个重要概念。此外,抽取一个值对象,帮助我们提高抽象水平,从而产生更可读的代码。

def Toronto
  Location.new('Canada','Toronto')
end

...

blog.make_post text: 'great post',location: Toronto

Value Objects Extending ActiveRecord::Base

Some people say that everything extendingActiveRecord::Base is an Entity. I disagree with this opinion. In my view,it doesn’t really matter how you implement your Value Objects as long as they have neither state nor identity.

有人说,一切扩展ActiveRecord::Base的都是一个实体。我不同意这个观点。在我看来,这真的不是什么事,你如何实现你的值对象,只要他们即无状态也无身份。

Let’s define the Location class:

让我们来定义位置类:

class Location < ActiveRecord::Base
  validates :city,:uniqueness => {:scope => :country}

  def self.get country,city
    location = Location.find_by_country_and_city(country,city)
    raise "There is no '#{city}' in '#{country}'" unless location
    location.readonly!
    location
  end

  ...
end
Using Location stays pretty much the same:

使用位置几乎保持相同:

toronto = Location.get('Canada','Toronto')
blog.make_post text: 'great post 2',location: toronto
Such requirements as supervising the list of all possible locations dynamically or attaching some additional information to every object (for example,a link to a wikipedia article) may push your decision in favor of this approach.

这种动态监督所有可能的地点列表,或附加一些额外的信息给每一个对象(例如,一个链接到维基百科文章)的要求可能会推你的决定赞成这种做法。

Plain Old Ruby Objects 纯旧Ruby对象

Those developers who learned Ruby via Rails tend to solve all their problems using Rails building blocks. Need to persist something? It’s onlyActiveRecord. Need a Value Object? Use composed_of. Everything that does not use Rails feels dirty for them. For example,all models not extendingActiveRecord::Base go to the lib folder. Even though it may work for small applications,building a complex model will require using Factories,Services,Value Objects,etc. Therefore,don’t be afraid of implementing a Value Object without Rails at all.

这些开发者,他们通过Rails学习Ruby,使用Rails构造块来解决所有的问题。需要坚持什么吗?只有唯一的ActiveRecord。需要一个值对象吗?使用composed_of。对于他们,一切不使用Rails的都感觉到脏。例如,所有的模型不扩展ActiveRecord::Base到lib文件夹。即使它可能适用于小型应用,构建一个复杂模型将需要使用工厂、服务、值对象,等等。因此,不要担心没有Rails的值对象实施。

Summary 总结

To sum up,Entities and Value Objects are extremely important. They are the core elements of object models. Thus,software developers should have a solid understanding of differences between them.

综上所述,实体和值对象是非常重要的。他们是对象模型的核心元素。因此,软件开发者应该坚实的理解它们之间的差异。

DDD for Rails Developers. Part 3: Aggregates.

Source:http://rubysource.com/ddd-for-rails-developers-part-3-aggregates/

关于Aggregate 这里做一些补充说明

Aggregate 就是一组相关对象的集合,我们把它作为数据修改的单元。每个Aggregate都有一个根(root)和一个边界(boundary)。边界定义了Aggregate内部都有什么。根则是Aggregate中所包含的一个特定Entity。在Aggregate中,根是唯一允许外部对象保持对它的引用 的元素,而边界内部的对象之间则可以互相引用。除根以外的其它entity都有本地标识,但这些标识只有在Aggregate内部才需要加以区别,因为外部对象除了根entity之外看不到其它对象。

汽车配件工厂的软件可能会使用一个汽车模型。汽车是一个具有全局标识的Entity:我们需要将这部汽车与世界上所有其它汽车区分开(即使是一些非常相似的汽车)。我们可以使车辆识别号来进行区分,车辆识别号是为每辆新汽车分配的唯一标识符。我们可能想跟踪4个轮胎的历史转数。我们可能想知道每个轮胎的里程数和磨损度。要想知道哪个轮胎在哪儿,必须将轮胎标识为entity。但我们可能不会关心这些轮胎在这辆汽车上下文之外的标识。如果更换了轮胎并将旧轮胎送到回收厂,那么软件将不再需要跟踪它们,它们会成为一堆废旧轮胎中的一部分。没有人会关心它们的转动历史。更重要的是,即使轮胎被 安在汽车上,也不会有人要系统中查询特定的轮胎,然后看看这个轮胎在哪辆汽车上。人们只会在数据库中查找汽车,然后临时查看一下这部汽车的轮胎情况。因此,汽车是Aggregate的根entity,而轮胎只是牌这个Aggregate的边界之内。另一方面,发动机组上面都刻有序列号,而且有时是独立于汽车被跟踪的。在一些应用程序中,发动机可以是自己的Aggregate根。

仔细地简化和约束模型的关联是通往Model-driven design的必经之路。

My Previous Articles About DDD for Rails Developers 我之前关于DDD为Rails开发人员的文章

In Part 1,I talked about using the layered architecture for tackling domain complexity. I showed a few typical violations of the layered architecture and gave some advice on how to fix them.

在第一部分,我谈了关于使用层架构缓解领域复杂度。我展示了一些典型的违反分层架构问题,并给出了一些如何解决这些问题的建议。

In Part 2,I started talking about the building blocks of Domain Driven Design. I wrote about an important distinction between Entities and Value Objects. I also gave some advice on how to implement Value Objects in Rails.

在第二部分,我开始谈论关于构建块在领域驱动设计中。我写了关于实现实体和值之间的重要差异。我也就如何落实Rails值对象提出了一些建议。

Aggregates 数据修改的单元

This time I’d like to go into another building block of Domain Driven Design. I’d like to talk about Aggregates.

这一次,我想进入领域驱动设计的另一个构造块。我想谈谈Aggregates。

We’ve all experienced this situation before:

我们都经历过这种情况:

You start with nicely designed groups of objects. All the objects have clear responsibilities,and all interactions among them are explicit. Then,you have to consider additional requirements,such as transactions,integration with external systems,event generation. Satisfying all of them and not making all the objects interconnected is a nontrivial task. What usually happens is database hooks,conditional validations,and remote calls are added on an ad hoc basis. The result is more connections among objects. Hence,the boundaries of object groups become fuzzy and enforcing invariants becomes harder. Remember all the cases when you were thinking,“Maybe I need to reload this object?” It indicates that your objects are interconnected,and you cannot reason about your code with confidence. Instead,you just guess.

开始时你设计了一组很好的对象。所有的对象都有明确的职责,以及它们之间的相互作用是明确的。然后,你必须考虑额外的要求,如交易,与外部系统的集成,发生事件。满足所有人,而不是让所有对象相互关联,是一项重要任务。通常发生的是数据库挂钩,有条件的验证,以及特设的基础上添加的远程调用。其结果是更多对象之间关联。因此,对象组的界线变得模糊,执行固定规则变得更加困难。当你想记住所有事件的情况下,“也许我需要重新加载这个对象?”这表明你的对象是相互关联的,你不能有信心的推理你的代码。相反,你只是猜测。

Defining Aggregates is a good remedy for the described situation.

对于描述的情况,定义Aggregates是一个很好的补救措施。

*、“An Aggregate is a cluster of associated objects that are treated as a unit for the purpose of data changes.”

Aggregate被视为一个单位的数据变化为目的集群关联的对象

*、An Aggregate consists of a few Entities and Value Objects,one of which is chosen to be the root of the Aggregate.

Aggregate由一些实体和值对象组成,其中之一被选择为Aggregate的根。

*、All external references are restricted to the root. Objects outside the Aggregate can hold references to the root only.

所有的外部引用只限于根。外部对象唯一能引用Aggregate的根。

*、Accessing other members of the Aggregate happens through the root. Therefore,nobody (outside the Aggregate) should hold references to those objects.

访问Aggregate的其它成员需要通过根发生。因此,没有人(Aggregate外面)应持有这些对象的引用。

*、As all external objects can hold reference only to the root,enforcing invariants becomes easier.

由于所有外部对象只能引用根,执行固定规则变得容易。

*、Aggregates help to reduce the number of bidirectional associations among objects in the system because you are allowed to store references only to the root. That significantly simplifies the design and reduces the number of blindsided changes in the object graph.

Aggregates有利于减少系统中的双向关联,因为你只被允许存储根引用。在对象图里这显著的简化设计和减少傻眼的变化。

Example 举例

It may sound too abstract,so I’d like to show you an example. I’m going to model an online bookstore. The main responsibility of the model will be selling and shipping books. Hopefully the example will bring some clarity to the definition of Aggregates and will demonstrate how they can be implemented in Rails.

这听起来可能太抽象,所以我想展示一个例子。我要模拟一个网上书店。该模型的主要职责是出售和运送书籍。希望这个例子让Aggregates定义变得清晰和将展现他们如何在Rails中实现。

Sketch 草图

This is a sketch illustrating all the classes that will form the model.

这是一个草图,说明将形成模型的所有类。

正如你所见,我有:

*、Entities: Order,Item,User,Book,Payment

实体:Order,Payment

*、Value Objects: Address

值对象:Address

*、Services:ShipmentService,PaymentService

服务:ShipmentService,PaymentService

*、View:OrderPresenter

视图:OrderPresenter(订单演示者)

Defining Aggregate Boundaries 定义Aggregate边界

Now,after I am done with sketching,I can establish Aggregate boundaries and choose roots.

现在,我做完草图后,我能建立Aggregate的边界和选择根。

There are a few rules of thumb to use:

这里有些经验和法则可用:

*、Entities forming the parent-child relationship,most likely,should form an Aggregate. In this case,the parent class becomes the root.

实体形成的亲子关系,最有可能应该形成一个Aggregate。在这种情况下,父类成为成根。

*、Entities that are semantically close to each other are good candidates for forming an Aggregate as well. For instance,Book and Payment have no obvious connections with each other. Having them inside an Aggregate is awkward. On the other hand,Order and Item are closely related. Thus,we should consider putting them inside an Aggregate.

在语义上彼此接近的实体也是一个形成Aggregate的很好候选。例如, Book and Payment 彼此没有明显的联系。让他们在一个Aggregate是很尴尬的。另一方面,Order and Item 关系是很接近的。因此,我们应该考虑把它们Aggregate。

*、If two Entities have to be modified inside a transaction,they should be parts of the same Aggregate.

如果两个实体在一个事务中被修改,他们应该是同一Aggregate的组成部分。

Note:

注意:

These rules should just help you get started. After your first sketch you should look at all the invariants that need to be maintained and finalize your Aggregate boundaries based on them.

这些规则久久是帮助你开始。你的第一个草图后,你应该看看所有需要保持固定规则,并完成你基于它们的Aggregate边界。

As you may have already guessed,Order and Item form an Aggregate. What other Entities should we include? Including Book does not make much sense because I can easily imagine clients using Book without Order (e.g. you may need to display the list of all available books in the store).Including User into the same Aggregate with Order is not the best idea either. Just imagine if User becomes the root,you will have to access all the orders of a user through the user itself. Furthermore,updating two orders of the same user simultaneously will be tricky. Clearly neither Book nor User should be a part of the Aggregate.

你也许已经猜到,Order和Item形成一个Aggregate。我们应该有什么样的其它实体,包含哪些内容?包含书没有太多意义,因为我可以轻意想像客户无需订购书(例如:你可能需要显示所有可用书在书店的列表中)。包括将User和Order Aggregate不是一个最好的主意。只需设想,如果用户成为根,你将不得不通过用户本身访问用户所有的订单。此外,同时更新两个相同用户的订单是非常棘手的。显然,Book和User不应该是Aggregate的一部分。

The situation with the Payment class is different. Conceptually,Payment is an important part of Order. Also,you cannot simultaneously pay for an Order and modify it. It’s decided,Payment becomes a part of the Aggregate.

与支付类的情况不同。从概念上讲,支付是订购的一个重要组成部分。此外,你不能同时支付一个订单并修改它。它决定了,支付变成了Aggregate的一部分。

An updated sketch with the defined boundary:

更新定义边界的草图:

Implementation 实现

Let’s get started with the code. First,let’s define the Book and User classes:

让我们开始代码,首先,我们定义 Book 和 User 类:

class Book
  include DataMapper::Resource

  property :id,Serial
  property :title,String
  property :author,String
  property :price,Decimal
end

class User
  include DataMapper::Resource

  property :id,Serial
  property :name,String

  property :address_country,String
  property :address_city,String

  validates_presence_of :name

  def address= address
    self.address_country = address.country
    self.address_city = address.city
  end

  def address
    Address.new(address_country,address_city)
  end
end

class Address < Struct.new(:country,:city)
end
There is nothing really interesting here. There are two Entities (Book and User) and one Value Object (Address). It gets interesting when we implement the Order and Item classes:

这里没有什么真正有趣的。有两个实体(Book 和 User)和一个值对象 (Address) 。 当我们实现 Order 和 Item 类时变得有趣。

class Order
  include DataMapper::Resource

  property :id,Serial,key: true
  property :status,Enum[:new,:ready,:paid,:shipped,:closed,:canceled]

  belongs_to :buyer,'User'
  has n,:items
  has 1,:payment

  property :shipping_address_country,String
  property :shipping_address_city,String

  def shipping_address= address
    self.shipping_address_country = address.country
    self.shipping_address_city = address.city
  end

  def shipping_address
    Address.new(shipping_address_country,shipping_address_city)
  end


  def self.make buyer
    create buyer: buyer,shipping_address: buyer.address,status: :new
  end

  def self.get buyer,id
    #...
  end

  def self.all_orders buyer
    #...
  end

  def self.active_orders buyer
    #...
  end


  def make_item book,quantity
    ensure_status :new

    amount = book.price * quantity
    items.create book: book,quantity: quantity,amount: amount
  end

  def make_payment masked_card
    ensure_status :ready

    self.payment = Payment.new(masked_card: masked_card,amount: total_amount)
    self.status = :paid
    save
  end

  def mark_as_ready
    #...
  end

  def mark_as_shipped
    #...
  end

  def total_amount
    #...
  end

  private

  def ensure_status required_status
    raise InvalidOrderStatus.new(self) if status != required_status
  end
end

class Item
  include DataMapper::Resource

  property :id,Serial
  belongs_to :book

  property :quantity,Integer
  property :amount,Decimal
end

class Payment
  include DataMapper::Resource

  property :id,Serial
  property :masked_card,String
  property :amount,Decimal

  property :created_at,DateTime
  property :updated_at,DateTime
end
That is how the creation of an order may look like:

如何创建一个订单看起来可能像这样:

buyer = current_user
order = Order.make buyer

#At this moment: 
#order.shipping_address == buyers_address
#order.status == :new

order.make_item book1,2
order.make_item book2,3
order.mark_as_ready
Reading the same order from the database may look like this:

从数据库中读取同一用户的订单可能像这样:

order = Order.get(buyer,params[:id])
There are a few important things I’d like to point out:

有些重要的东西我想指出:

*、All items and payments are created through an instance of Order.

所有的items 和 payments 是通过一个Order实例创建的。

*、I don’t update attributes of an Order directly.

我不直接更新Order属性。

*、I don’t useDataMappermethods directly. I wrote a few methods to decouple our model fromDataMapperas much as possible.

我不直接使用DataMapper方法。我尽可能的写了一些方法去耦和我们的模型从DataMapper

*、There are no bidirectional associations. Every order knows about its items,but the items don’t have any references to their orders. Why? Because they don’t need to. Order is the root; therefore,any client can get an item only through its order. This means that an item’s order will always be known. * Bidirectional associations are a bad practice established in the Rails community. If you can avoid it,please,do it.

没有双向关联。每个order知道关于它的items,但items没有它的orders的任何引用。为什么?因为他们不需要。Order是根:因此,任何客户只能通过它的order来得到一个item。这意味着,一个item的order总是被知道。在Rails社区,双向关联是一种不好的做法。如果你能避免它,请尽量避免。

*、A user does not know about its orders. Apart from the fact that it’s an unnecessary bidirectional association,it also makes testing much harder.

用户不知道其订单。除了从实际出发,它是一个不必要的双向关联外,这也使测试更难。

Don’t believe me? Take a look at this line of code:

不相信我吗?请看下面这行代码:

@all_orders = current_user.orders.active
It looks so simple. Some people might even say that it’s nicer than this one:

它看起来那么简单。甚至有些人可能会说,它是比这一个更好的:

@all_orders = Order.active_orders(user)

但是,如果你尝试在你的测试中去掉它,你可能会写一些类似下面的:

order = ...
stub(orders = Object.new).active {[order]}
stub(user = Object.new).orders {orders}
stub(UserSession).current_user {user}

现在,比较它与去掉Order.active_orders(user):

order = ...
stub(UserSession).current_user {user}
stub(Order).active_orders(user){[order]}
The second test is much easier to read and understand. In addition,in large applications such models as User tend to grow. In a few years,you may get the User class having many orders,promotions,friends,wish lists etc.

第二个测试更加易读和理解。另外,在一个大的应用中,像User这类模型趋向于增长。在短短几年内,你可能会得到User类有许多订单,促销活动,朋友,愿望清单等等。

Accessing Our Model in View 在View中访问我们的模型

Please,don’t be mistaken. The fact that Order is the root of the Aggregate does not mean I cannot access its payment or items. Of course,I can. The only rule is not to store references to those objects. For instance,I’d probably need a presenter. As presenters store references to the objects they present,creatingItemPresenterorPaymentPresenteris a violation of the Aggregate boundary. Instead,we can createOrderPresenterand pass an instance of Order to it.OrderPresentercan access the order’s items or payment through the order itself.

请不要误会。实事上,Order是Aggregate的根,并非意味着我不能访问它的payment或items. 当然,我能。唯一的规则是不要存储引用到这些对象。例如,我想可能需要一个presenter. 随着presenters 引用他们存储的对象,创建一个ItemPresenterPaymentPresenter是违反Aggregate边界。相反,我们可以创建OrderPresenter,通过一个Order的实例。OrderPresenter能访问order的items或payment通过订单本身。

class OrderPresenter < Struct.new(:order)
  def render_items
    order.items.map do |item|
      item_row item
    end.join("")
  end

  private

  def item_row item
    "<div>#{item.book.title} - #{item.quantity} - #{item.amount}</div>"
  end
end

Invariants 固定规则

固定规则(Invariants):是指在数据变化时必须保持不变的一致性规则。Aggregate内部的成员之间可能存在固定关系。Aggregate中的所有规则并不是每时每刻都被更新为最新的的状态。通过事件处理、批处理或其它更新机制,在一定的时间内可以解决部分依赖性。但在每个事务完成时,必须要满足Aggregate内所应用的固定规则的要求。

Remember that Aggregates are not only about access restriction. They also define the boundaries of invariants and transactions. There is a common opinion that those invariants must be enforced by the root. It’s also common to make the root responsible for managing all transactions. Although sometimes it may make perfect sense,there are lots of situations when it does not. Just to give you an example,let’s take a look at thePaymentServiceclass:

请记住,Aggregates不只是关于访问限制。他们还定义固定规则和事务边界。有一个普遍的观点认为,那些固定规则必须被强制通过根。这也是常见的让根负责管理所有事务。虽然,有时它可能非常有意见,但很多情况下它没有。只是为了给你举例,让我们来看看PaymentService类:

module PaymentService
  def self.process order,credit_card
    with_transaction do
      order.make_payment mask_card(credit_card)
      make_remote_call credit_card,order.payment
    end
  end

  ...
end
PaymentServicehas several responsibilities. First,it masks the credit card. Next,it updates the database to mark the order as paid. After that,it makes a remote call to some external service that processes credit card transactions. Also,it wraps everything into a transaction.PaymentServiceensures an important invariant that updating the status of the order and making the remote call must be done together. An alternative would be to make Order responsible for calling external services. The result would be a violation of Single Responsibility Principle and a few nasty dependencies of Order on external payment systems.

PaymentService有连带责任。首先,它掩盖了信用卡。接下来,它更新了数据库标记的order做为支付。之后,它进行了一个远程调用一些外部服务来处理信用卡交易。此外,它封装所有的到一个事务。PaymentService确保一个重要的固定规则,更新状态和远程调用必须一起做。另一种方法是使订单负责调用外部服务。其结果将是违反了单一责任原则,和一些讨厌的依赖于Order的外部支付系统。

Wrapping Up 封装

Sometimes invariants need to be applied not to discrete objects,but to clusters of objects. Defining Aggregates and restricting access to Aggregate members is an arrangement that makes enforcing all the invariants possible.

有时候固定规则需要被应用到不是离散对象,但集群的对象。定义的Aggregates和限制访问到Aggregate成员的安排,让执行所有固定规则可能。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结