Skip to content

JackKuang/JavaDesignPattern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

46 Commits

Repository files navigation

JavaDesignPattern

设计模式在java中的应用示例代码

建议阅读源码时配合文章介绍

http://blog.anxpp.com/index.php/archives/489/

或者

http://blog.csdn.net/anxpp/article/details/51224293

以上为原作者内容,以下为结合上述博客进行整理内容。

java设计模式

模式可以分成3类:创建型、行为型和结构型。

创建型模式

创建型模式涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new创建对象。

创建型模式有以下5个:

工厂方法模式、抽象工厂方法模式、生成器模式、原型模式和单例模式。

行为型模式

行为型模式涉及怎样合理的设计对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护,易复用。

行为型模式有以下11个:

责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。

结构型模式

结构型模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理使用继承机制;和对象有关的结构型模式涉及如何合理的使用对象组合机制。

结构型模式有以下7个:

适配器模式、组合模式、代理模式、享元模式、外观模式、桥接模式和装饰模式。

创建型模式

1.单例模式(Singleton Pattern)

Ensure a class only has one instance,and provide a global point of access to it.
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

【何时使用】:

  • 当系统需要某个类只有一个实例的时候。

【优点】:

  • 单例模式的类唯一实例由其本身控制,可以很好的控制用户何时访问它。

单例模式概念很简单,而且也比较常用。
如果直接将例子应用到多线程中,可以直接把getInstance()设置为同步的(synchronized),但是并不高效,任一之后,只能有一个线程可以调用这个方法,其余的会排队等待。
所以整个方法做同步不是优解,那就只同步代码块就好了。这就引出了双重检验锁,即在同步块外检查一次null,然后再在同步块内检查一次。但是最终这种方式也是会有问题的,使用静态内部类是一种比较好的方式。

2.工厂方法模式(Factory Method Pattern)

别名:虚拟构造(Another Name:Virtual Constructor)。
Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclassess.
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

【何时使用】:

  • 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合。
  • 用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用。

【优点】:

  • 使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦。
  • 工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。

简单工厂模式
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么久可以使用简单工厂模式。下面就是简单工厂的例子:

//演示简单工厂publicclassSimpleFactory{publicstaticvoidmain(Stringargs[]) throwsException{Factoryfactory = newFactory(); factory.produce("PRO5").run(); factory.produce("PRO6").run()} } //抽象产品interfaceMeizuPhone{voidrun()} //具体产品X2classPRO5implementsMeizuPhone{@Overridepublicvoidrun(){System.out.println("我是一台PRO5")} } classPRO6implementsMeizuPhone{@Overridepublicvoidrun(){System.out.println("我是一台PRO6")} } //工厂classFactory{MeizuPhoneproduce(Stringproduct) throwsException{if(product.equals("PRO5")) returnnewPRO5(); elseif(product.equals("PRO6")) returnnewPRO6(); thrownewException("No Such Class")} }

很容易看出,简单工厂模式是不易维护的,如果需要添加新的产品,则整个系统都需要修改。如果我们需要添加诸如PRO7、PRO8等产品,直接在工程类中添加即可。但是如果这时候根部不知道还有什么产品,只有到子类实现时才知道,这时候就需要工厂方法模式。

工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
Java中的Collection接口的实现都能通过iterator()方法返回一个迭代器,而不同的实现的迭代器都在该实现中以内部类的方式对Iterator接口实现的,然后通过iterator()方法返回。那么,这个iterator()方法就是一种工厂方法。
可以看到,在这里抽象产品是Iterator接口,具体产品就是Collection接口的实现中对Iterator接口的实现,构造者是Collection接口,其提供的工厂方法就是Iterator iterator();,具体构造者就是Collection的实现。而工厂方法模式的结构,也就是由前面加粗的4部分组成。
一抽象产品类派生出多个具体产品类;一抽象工厂类派生出多个具体工厂类;每个具体工厂类只能创建一个具体产品类的实例。 即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)。“一对一”的关系。
与简单工厂间的取舍:工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
如果再分得详细一点,一个工厂可能不只是生产手机(如小米除了手机,连电饭锅都有),但有得工厂智能生成低端的产品,而大一点的工厂可能通常是生成更高端的产品。所以一个工厂是不够用了,这时,就应该使用抽象工厂来解决这个问题。

3.抽象工厂方法模式(Abstract Factory Pattern)

别名:配套(Another Name:Kit)
Provide an interface for creating families of related or dependent objects without specifying their concrete classess.
提供一个创建一系列或相互依赖对象的接口,而无须指定他们的具体的类。

  • 简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
  • 工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
  • 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。

在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式(不如上例减去耳机这种产品,就回到工厂方法模式了)。

4.生成器模式(Builder Pattern)

Separate the construction of a complex object from its representation so that the same construction process can create different representations.
将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

【何时使用】:

  • 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。
  • 当某些系统要求对象的构造过程必须独立于创建该对象的类时。

【优点】:

  • 生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
  • 生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
  • 可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
  • 生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
  • 当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。

模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。

5.原型模式(Prototype Pattern)

Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

【何时使用】:

  • 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
  • 当对象的创建需要独立于它的构造过程和表示时。
  • 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过该实例复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便。

【优点】:

  • 当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
  • 可以动态的保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
  • 可以在运行时创建新的对象,而无须创建一系列类和集成结构。
  • 可以动态的添加、删除原型的复制品。

原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。

Java中所有类都直接或间接继承自Object类,Object类中已有clone()方法:”protected native Object clone() throws CloneNotSupportedException;“,可以看到权限是protected的,所以仅有子类可以访问这个方法,但我们可以在子类中重写这个方法,将访问权限上调到public,然后方法体里面return super.clone()。

我们能看到这个Object方法是可能会抛出异常的,我们必须实现Cloneable接口,才可以使用这个方法,否则会抛出“java.lang.CloneNotSupportedException”的异常。这个Cloneable接口其实是空的,实现它的目的在于让JVM知道这个对象是可以可复制的,否则clone()时就会发生异常。

除此之外,java中还有一种序列化,只需要对象实现Serializable接口。


行为型模式

6.责任链模式(Chain of Responsibility Pattern)

使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

【何时使用】:

  • 有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
  • 希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求。
  • 程序希望动态的指定可处理用户请求的对象集合。

【优点】

  • 低耦合。
  • 可以动态的添加删除处理者或重新指派处理者的职责。
  • 可以动态改变处理者之间的先后顺序。

通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。

我们写java web程序的时候,通常会编写一些过滤器(Filter),然后配置到web.xml中,这其实就是责任链模式的一种实践。而使用Log4j记录日志,配置级别的时候,也同样用到了责任链模式。

我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。

java web中的过滤器,log4j的日志就是使用行为型模式。

7.命令模式(Command Pattern)

别名:动作,事物(Another Name:Action,Transaction)
Encapsulate a request as an object,thereby letting you parameterize clients with different reauests,queue or log requests,and support undoable operations.
将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 【何时使用】:

  • 程序需要在不同的时刻指定、排列和执行请求。
  • 程序需要提供撤销操作。
  • 程序需要支持宏操作。

【优点】:

  • 在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,及请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此间的耦合。
  • 命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有具体命令和接收者,新增加的调用者就可以使用已有的具体命令。
  • 由于请求者的请求被封装到具体的命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
  • 使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些具体命令。

一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。

8.解释器模式(Interpreter Patterm )

Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

【何时使用】:

  • 当有一个简单的语言需要解释执行,并且可以将该语言的每一个规则表示为一个类时,就可以使用解释器模式。

【优点】:

  • 将每一个语法规则表示成一个类,方便与实现简单的语言。
  • 由于使用类表示语法的规则,可以较容易改变或扩展语言的行为。
  • 通过在类结构中加入新的方法,可以在解释的同时增加新的行为。

解释器模式一般包括四种角色:

  • 抽象表达式:该角色为一个接口,负责定义抽象的解释操作。
  • 终结符表达式:实现抽象表达式接口的类。
  • 非终结表达式:也是实现抽象表达式的类。
  • 上下文(Context):包含解释器之外的一些全局信息。

使用该模式设计程序一般需要三个步骤:

  • 解析语句中的动作标记。
  • 将标记规约为动作。
  • 执行动作。

这种模式一般会应用到一些特殊的问题上,使用这种模式一般需要了解形式语言中的基本知识。js内核就是一个强大的解释器。
简单的解释器模式,我们需要解释出来表达式的信息即可;而更深一层的,我们需要把表达式中的内容,翻译成我们程序运行的一部分来执行。

9.迭代器模式(Cursor Pattern)

别名:游标(Another Name:Cursor)
提供一种方法顺序访问一个聚合对象中的各个元素,而由不需要暴露该对象的内部细节。

【何时使用】:

  • 让用户访问集合汇总的对象而不想暴露这个集合的实现时
  • 对不同集合提供一个统一的遍历接口时

【优点】:

  • 用户使用迭代器访问集合中的对象而不需要知道这个集合的具体实现
  • 可以同时使用多个迭代器遍历一个集合

通常容器提供的迭代器时可以高速遍历它本身的,而使用其本身的机制(如LinkedList中使用get(i)方法遍历)遍历性能可能并不好。
其实这个在工厂方法模式给出的例子就足够解释这个模式的使用了,如需看具体代码实现,请移步工厂方法模式中的例子查看。

10.中介者模式(Mediator Pattern)

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之前的交互。

【优点】:

  • 两个类直接关联,是很好实现的,但如果不希望两个类直接发生交互,那么就需要使用中介者模式了。

    中介者消除了同事与同事间直接的关联。

11.备忘录模式(Memento Pattern)

别名:标记(Another Name:Token)
Without violating encapsulation,captrue and externalize an object' orifianl state so that the object can be restored to this state later.
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存该状态,这样就可以将该对象恢复到之前保存的状态。

【何时使用】:

  • 必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复该对象先前的状态。 一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己IDE内部状态。

【优点】:

  • 备忘录模式使用备忘录可以吧原先者的内部状态全部保存起来,使是有很“亲密”的对象可以访问备忘录中的数据。
  • 备忘录模式强调了类设计单一责任的原则,即将状态的刻画和保存分开。

备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。 备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。

备忘录模式中有三种角色:

  • 备忘录(Memento)角色:将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
  • 发起人(Originator)角色:创建一个含有当前的内部状态的备忘录对象。使用备忘录对象存储其内部状态。
  • 负责人(Caretaker)角色:负责保存备忘录对象。不检查备忘录对象的内容。

把备忘录以发起人的私有内部类的方式实现的话,那它就只能被发起人访问了,这正好就符合备忘录模式的要求,但是我们的负责人是需要存放备忘录的引用的,于是,我们提供一个公共的接口,他是空的,我们用备忘录实现它,主要就是利用其中的类型信息。

12.观察者模式(Observer Pattern)

别名: 依赖,发布/订阅(Another Name: Dependents, Publish/Subscribe)
定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都得到通知并被自动更新。

【何时使用】:

  • 当一个对象的数据更新时,需要通知其他对象,而又不希望和被通知的对象形成紧耦合时

观察者模式是一对多的。而本例是将更新的内容整个推给客户端。
而观察者模式中的数据有推和拉的区别,上例是推。
推的方式会将主题更改的内容全部直接推给客户端,拉的方式就是主题的数据更新后,不直接将数据推给客户端,而是先推送一个通知并提供对应的方法供客户端拉取数据。
如果上例中,天气服务每半小时更新(半点和整点推消息),还有一个客户端,不需要特别即时的天气消息,只取整点的消息,那么我们就可以使用拉的方式,数据更新后,给客户端推送一个标志,客户端自己按需取得数据(天气服务需要提供这样一个接口)。这就是拉。
java.util包中也提供了观察者模式的支持,因为java程序设计中使用比较广泛。

13.状态模式(State Pattern)

别名:状态对象(Another Name:Objects for States)
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

【何时使用】:

  • 一个对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为 。
  • 需要编写大量的条件分支语句来决定一个操作的行为,而且这些条件恰好表示对象的一种状态。

【优点】:

  • 使用一个类封装对象的一种状态,很容易增加新的状态
  • 在状态模式中,环境(Context)中不必出现大量的条件判断语句。环境(Context)实例所呈现的状态变得更加清晰、容易理解。
  • 使用状态模式可以让用户程序很方便地切换环境(Context)实例的状态。
  • 使用状态模式不会让环境(Context)中的实例中出现内部状态不一致的情况。
  • 当状态对象没有实例变量时,环境(Context)的各个实例可以共享一个状态对象。

用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
设想我们有一个程序,要保存数据的,按照数据(这里以String举例)的大小,使用不同的方式保存。如果数据很小,我们将其保存到Redis(缓存数据库)中,如果数据库不太小也不太大,我们将其保存到mysql中,如果数据非常大,我们直接将其写入到文件中。也可以添加setState()方法,手动切换状态,并在执行的方法体中不在自动判断状态。

14.策略模式(Strategy Pattern)

定义一系列算法,把他们一个个封装起来,并且使他们可相互替换。本模式使得算法可独立于其他客户端而变化。

【优点】: 策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下面就以一个示意性的实现讲解策略模式实例的结构。

策略模式中包括三种角色:

  • 策略(Strategy):一个接口,定义了若干个算法(抽象方法)。
  • 具体策略(ConcreteStrategy):策略的实现。
  • 上下文/环境(Context):依赖于策略接口的类。

策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。 这其实也是典型的将代码向继承等级结构的上方集中的标准做法。
上次我们使用状态模式将数据按不同状态保存到不同地方,这里,我们使用策略模式来实现通过不同的策略来选择数据的保存方式。

这里数据的保存就是根据使用的时候设置的策略来决定。
使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

15.模板方法模式(Template Method Pattern)

Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

【何时使用】: 设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现。 需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。

【优点】:

  • 可以通过在抽象摸吧能定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现算法细节不会改变整个算法的骨架。
  • 在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。

模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。 模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

16.访问者模式(Visitor Pattern)

Represent an opration to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it oprates.
表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。

【何时使用】:

  • 一个对象结构中,比如某个集合中,包含很多对象,想对集合中的对象增加一些新的操作。
  • 需要对集合中的对象进行很多不同的并且不相关的操作,而不想修改对象的类,就可以使用访问者模式。访问者模式可以在Visitor类中集中定义一些关于集合中对象的操作。

【优点】:

  • 可以在不改变一个集合中的元素的类的情况下,增加新的施加于该元素上的新操作。
  • 可以将集合中各个元素的某些操作集中到访问者中,不仅便于集合的维护,也有利于集合中元素的复用。

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

在介绍访问者模式前,先介绍一下分派的概念。
变量被声明时的类型叫做变量的静态类型(Static Type),而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type),如:

List list = new ArrayList();

这个list变量的静态类型是List,而它的实际类型是ArrayList。根据对象的类型而对方法进行的选择,就是分派(Dispatch)。分派又分为两种:静态分派和动态分派。
静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。
看一个例子:

publicclassDispatch{voidprint(FatherClassc){System.out.print("父类")} voidprint(ChildClassc){System.out.print("子类")} publicstaticvoidmain(Stringargs[]){FatherClasschild = newChildClass(); newDispatch().print(child); child.print()} } classFatherClass{voidprint(){System.out.println("父类")} } classChildClassextendsFatherClass{voidprint(){System.out.print("子类")} }//输出:父类子类

可以看到,重载的分派是根据静态类型进行的。
java的方法重写是根据实际类型来的(动态分派),编译器编译时并不知道其真实类型,而是运行时动态决定的。
一个对象又叫做它所包含的方法的接收者,java中的动态分派,要调用哪一个方法,是由这个对象的真实类型决定的。
如果能够根据参数和接收者来动态的决定调用某个方法,这就是动态的多分派语言,如果可以根据这两种方式来动态的决定方法调用,就是动态双重分派,但前面已经说了,java中重载是根据静态类型进行的,所以java只能动态的根据接收者来进行方法调用,即java是动态单分派语言,如果要实现双重分派,就必须通过设计模式来完成。
我们知道,动态多分派的语言是很容易做到双重分派这一点的,可以通过接收者和参数(实际类型)来判断方法的调用,但是,Java是动态单分派的,只能通过接收者来动态分派,但是现在我们也清楚了,使用访问者模式,通过两次方法调用,我们依然实现了双重分派。

结构型模式

17.适配器模式(Adapter Pattern)

将一个类的接口转换成客户希望的另外一个接口。该模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

【何时使用】:

  • 一个程序想使用已经存在的类,但是该类所实现的接口和当前程序所使用的接口不一致时。

【优点】

  • 目标与被适配者解耦
  • 满足开-闭原则

原理就是保留现有的类所提供的服务,修改其接口,从而达到客户端的期望。
再者,适配器也分对象适配器和类适配器,先看下面代码再来解释。

类适配器与对象适配器的区别就是类适配器需要继承被适配者,而Java的单继承的,所以通常情况下,使用对象适配器更好。
如果目标接口中的方法数与被适配器者中的数目相同,就是完全适配,若目标接口中的方法更多,则是剩余适配,反之,为不完全适配。
上面的被适配器是一个类,但也可以使用接口,这样,其实现会由子类决定。如果适配器同时实现目标接口和被适配者的接口,那么适配器就成为了一个双向适配器。

18.组合模式(Composite Pattern)

Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使用户对单个对象和组合对象的使用具有一致性。

【何时使用】:

  • 当想表示对象的部分-整体层次结构。
  • 希望用户用一致的方式处理个体对象和组合对象。

【优点】:

  • 组合模式中包含个体对象和组合对象,并形成树形结构,使用户可以方便地处理个体对象和组合对象。
  • 组合对象和个体对象实现了相同的接口,用户一般无须区分个体对象和组合对象。
  • 当增加新的Composite节点和Leaf节点时,用户的重要代码不需要做出修改。

组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念 ,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。组合模式让你可以优化处理递 归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是电脑的文件系统。下面我们就以这个例子来介绍组合模式(虽然我们直接使用Tree这种数据结构也能直接描述)。

19.代理模式(Proxy Pattern)

Provide a surrogate or placeholder for another object to control access to it.
为其它对象提供一种代理以控制对这个对象的访问。

【何时使用】:

  • 程序可能不希望用户直接访问该对象,而是提供一个特殊的对象以控制对当前对象的访问。
  • 如果一个对象(例如很大的图像)需要很长时间才能完成加载。
  • 如果对象位于远程主机上,需要为用户提供访问该远程对象的能力。

【优点】:

  • 代理模式可以屏蔽用户真正请求的对象,是用户程序和正在的对象解耦。
  • 使用代理来担当那些创建耗时的对象的替身。

一个用户不想或者不能够直接引用一个对象(或者设计者不希望用户直接访问该对象),而代理对象可以在客户端和目标对象之间起到中介的作用。而且这个代理对象中,我们可以做更多的操作。
将目标对象与使用的用户隔离开,而且在调用目标对象的方法前后还能执行额外的操作(有点aop的概念)
还有种代理叫远程代理,需要使用到RMI,然后在程序中可以调用网络上另外一个JVM上的对象方法。此处不多介绍,读者可以自行检索RMI相关资料。

20.享元模式(Plyweight Pattern)

Use sharing to support large numbers of fine-grained objects efficiently.
运用共享技术有效地支持大量细粒度的对象。

【何时使用】:

  • 一个应用程序使用大量的对象,这些对象之间部分属性本质上是相同的,这时应使用享元来封装相同的部分。
  • 对象的多数状态都可变为外部状态,就可以考虑将这样的对象作为系统中发的享元来使用。

【优点】:

  • 使用享元可以节省内存的开销,特别适合处理大量细粒度对象,这些对象的许多属性值是相同的,而且一旦创建则不允许修改。
  • 享元模式中的享元可以使用方法的参数接收外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,这就使享元可以在不同的环境中被共享。

在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String str="string",其中"str"就是一个字符串常量。

享元模式包括三种角色:

  • 享元接口(Plyweight):定义了对外公开的获取其内部数据和接收外部数据的方法。
  • 具体享元(Concrete Plyweight):享元接口的实现。
  • 享元工厂(Plyweight Factory):该类的实例负责创建和管理享元对象,用户或其他对象必须请求他以获取一个享元对象。

享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长。

21.外观模式

为系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

我们提供给用户的某个功能,可能是包含很多个步骤的,但我们可以把这些步骤封装到一个统一的接口中,让用户感觉仅仅就是一个单一的操作,使用起来也就更加简单。

用户要购买一件商品,系统首先会判断库存,然后要计算费用(商品价格,邮费和优惠等),最后才会生成一个订单。这其中就包含几个子系统,库存管理子系统和计费子系统,而计费子系统又分更小的子系统(此处两处使用到外观模式).

最后是面向用户的购买接口,在这里也是一个外观,同时,这里使用了枚举实现的单例模式:

//外观publicenumProductSalesman{instance; Stockstock = newStock(); FinalPricefinalPrice = newFinalPrice(); ObjectbuySomething(Stringproduct,Stringaddr,StringdiscountCode){if(!stock.hasStock(product)) return"库存不足"; intprice = finalPrice.getFinalPrice(product, addr, discountCode); return"订单信息:" + product + "-" + addr + "-" + discountCode + "-" + price} }

使用很简单,用户无需关系内部是如何操作了,只需要使用这个购买接口即可:

publicclassTestUse{publicstaticvoidmain(Stringargs[]){Objectinfo = ProductSalesman.instance.buySomething("银河飞船", "地球", "K1234523"); System.out.println(info)} }

22.桥接模式(Bridge Pattern)

别名:柄体模式(Another Name:Handle-Body)
Decouple an abstraction from its implementation so that the two can vary independently.
将抽象部分与它的实现部分分离,使它们都可以独立的变化。

【何时使用】:

  • 不想让抽象和某些重要的实现代码是固定的绑定关系,这部分实现可运行时动态决定。
  • 抽象和实现者都可以继承当方式独立地扩充而互不影响,程序在运行期间可能需要动态的将一个抽象的子类的实例与一个实现者的子类的实例进行组合。
  • 希望对实现者层次代码的修改对抽象层不产生影响,即抽象层的代码不需要重新编译,反之亦然。

【优点】:

  • 桥接模式分离实现与抽象,使抽象可实现可以独立的扩展。当修改实现的代码时,不影响抽象的代码,反之也一样。
  • 满足开闭-原则,抽象和实现者处于同层次,使系统可独立的扩展这两个层次。增加新的具体实现者,不需要修改细化抽象,反之增加新的细化抽象也不需要修改具体实现。

桥接模式是一种结构型模式,它主要应对的是:由于实际的需要,某个类具有两个或两个以上的维度变化,如果只是用继承将无法实现这种需要,或者使得设计变得相当臃肿。
桥接模式的做法是把变化部分抽象出来,使变化部分与主类分离开来,从而将多个维度的变化彻底分离。最后,提供一个管理类来组合不同维度上的变化,通过这种组合来满足业务的需要。

桥接模式中有4种角色:

  • 抽象
  • 细化抽象
  • 实现者
  • 具体实现者

23.装饰模式(Decorator Pattern)

别名:包装器(Wrapper)
动态的给对象添加额外的职责。就功能来说,装饰模式比生产子类更为灵活。

【何时使用】:

  • 程序希望动态的增强类的某对对象的功能,而不影响其他对象时
  • 采用继承来增强对象功能不利于系统的扩展和维护时

【优点】:

  • 被装饰者和装饰者是松耦合关系。
  • 满足开-闭原则
  • 可以使用多个具体装饰器装饰具体组件的实例

装饰模式使用被装饰类的一个子类的实例,把客户端的调用委派到被装饰类,装饰模式的关键在于这种扩展是完全透明的。装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为。
例如,我们有一个工具,用于持久化数据的,最开始设计的功能时是将数据持久化到本地文件,但现在部分数据需要同时持久化到数据库,再后来,又有数据需要同时持久化到网络上的位置,而我们是不能改之前的实现的,因为后面这些需求,只是针对部分数据的,所以我们理所当然就可以使用装饰器模式了。

About

23种设计模式 Java 实现

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java100.0%