目录(?)[+]
原创不易,转载请注明出处:http://blog.csdn.net/anxpp/article/details/51224293,谢谢!
文章比较长,读者可以通过顶端的目录选择要了解的模式,然后通过文章右边的按钮快速返回顶部重新选择一个新的模式浏览
博主精心准备了大量的示例代码。文章尽量提供与编程相关的例子,而不是像多数其他介绍的文章一样,提供一些感觉挺滑稽的例子(那样的例子可能看完觉得写得很好,然而还是不会用...)。
本文耗费了作者大量时间,还请亲们给个赞O(∩_∩)O~
也可以通过CTRL+F并输入要了解的模式并跳到对应位置。
文章中的示例源码在github上:https://github.com/anxpp/JavaDesignPattern
文中未给出UML图,如果需要请回复说明,本人也可以画出需要的设计模式对应的UML图。
设计模式是针对某一类问题的最优解决方案,是从许多优秀的软件系统中总结出的。
Java中设计模式(java design patterns)通常有23种。
模式可以分成3类:创建型、行为型和结构型。
创建型模式涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new创建对象。
创建型模式有以下5个:
工厂方法模式、抽象工厂方法模式、生成器模式、原型模式和单例模式。
行为型模式涉及怎样合理的设计对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护,易复用。
行为型模式有以下11个:
责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
结构型模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理使用继承机制;和对象有关的结构型模式涉及如何合理的使用对象组合机制。
结构型模式有以下7个:
适配器模式、组合模式、代理模式、享元模式、外观模式、桥接模式和装饰模式。
模式中涉及的重要角色,会在描述中(加粗字体)介绍出来。下面就逐一介绍。
Ensure a class only has one instance,and provide a global point of access to it.
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
何时使用
优点
单例模式概念很简单,而且也比较常用。
在使用这个模式的时候,我们要考虑是否会在多线程中使用,如果不会应用于多线程,那写法就足够简单:
public class SimpleSingleton {
private static SimpleSingleton instance;
private SimpleSingleton(){}
public static SimpleSingleton getIntance(){
if(instance == null)
instance = new SimpleSingleton();
return instance;
}
}
上例就是一个简单的单例模式实现,使用了懒加载模式。但是多线程中可能会创建多个实例。下面就介绍多线程中的使用。
如果直接将上面例子应用到多线程中,可以直接把getInstance()设置为同步的(synchronized),但是并不高效,任一之后,只能有一个线程可以调用这个方法,其余的会排队等待。
所以整个方法做同步不是优解,那就只同步代码块就好了。这就引出了双重检验锁,即在同步块外检查一次null,然后再在同步块内检查一次。但是最终这种方式也是会有问题的,使用静态内部类是一种比较好的方式。
单例模式使用很频繁,也很简单,但不一定都能写对,详细的写法请参考:如何正确地写出单例模式。里面详细的分析的单例模式的各种写法。
其他模式中的示例代码,有很多时候用到了单例模式,此处就不额外添加例子了。
这里给出一个推荐的实现方式(枚举):
别名:虚拟构造(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.
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
何时使用
优点
简单工厂模式
介绍工厂方法模式前,先介绍一下简单工厂模式,简单工厂模式也是一种工厂方法模式。
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么久可以使用简单工厂模式。下面就是简单工厂的例子:
//演示简单工厂
public class SimpleFactory {
public static void main(String args[]) throws Exception{
Factory factory = new Factory();
factory.produce("PRO5").run();
factory.produce("PRO6").run();
}
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品X2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class PRO6 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO6");
}
}
//工厂
class Factory{
MeizuPhone produce(String product) throws Exception{
if(product.equals("PRO5"))
return new PRO5();
else if(product.equals("PRO6"))
return new PRO6();
throw new Exception("No Such Class");
}
}
很容易看出,简单工厂模式是不易维护的,如果需要添加新的产品,则整个系统都需要修改。如果我们需要添加诸如PRO7、PRO8等产品,直接在工程类中添加即可。但是如果这时候根部不知道还有什么产品,只有到子类实现时才知道,这时候就需要工厂方法模式。
而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来是比较麻烦的,那么工厂方法模式正式解决这个问题的,下面就介绍工厂方法模式。
工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
针对上面的例子,如果使用工厂方法模式,即将工厂定义为一个接口,然后由具体的工厂来确定需要生成什么样的产品,为了与简单工厂比较,这里还是贴上代码:
如果了解Java的集合框架,那么它就是一个很好的例子:
Java中的Collection接口的实现都能通过iterator()方法返回一个迭代器,而不同的实现的迭代器都在该实现中以内部类的方式对Iterator接口实现的,然后通过iterator()方法返回。那么,这个iterator()方法就是一种工厂方法。
可以看到,在这里抽象产品是Iterator接口,具体产品就是Collection接口的实现中对Iterator接口的实现,构造者是Collection接口,其提供的工厂方法就是Iterator iterator();,具体构造者就是Collection的实现。而工厂方法模式的结构,也就是由前面加粗的4部分组成。
如果对Java容器不熟悉,下面再提供一个例子(模仿Iterator,其实顺便也介绍了Iterator):
如果有多种数据结构要遍历,我们就需要一种用于遍历不同结构的工具,首先我们就需要为这个工具定义一个接口(抽象产品),用于描述如何来遍历:
//只是需要遍历一堆数据,那么只需要2个方法就可以了
public interface Iterator<T> {
boolean hasNext(); //是否还有下一个元素
T next(); //得到下一个元素
}
然后就是我们要遍历的目标,而这些目标此处我们暂定为列表,这就是构造者:
对于List可能有多种实现方式,比如数组和链表,此处就简陋的介绍一下,而这些就是具体构造者,而里面有遍历器的具体实现(具体产品),此处以内部类的形式放到了List的实现(具体构造者)里面,也完全可以修改代码将遍历器的实现(具体产品)独立出来:
数组的实现:
package com.anxpp.designpattern.factorymethod;
//方便演示而实现的简陋的数组list
public class ArrayList<T> implements List<T>{
private int size; //存放的元素个数,会默认初始化为0
private Object[] defaultList; //使用数组存放元素
private static final int defaultLength = 10;//默认长度
public ArrayList(){ //默认构造函数
defaultList = new Object[defaultLength];
}
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
//添加元素
@Override
public boolean add(T t) {
if(size<=defaultLength){
defaultList[size++] = t;
return true;
}
return false;
}
//遍历器(具体产品)
private class MyIterator implements Iterator<T>{
private int next;
@Override
public boolean hasNext() {
return next<size;
}
@SuppressWarnings("unchecked")
@Override
public T next() {
return (T) defaultList[next++];
}
}
}
链表实现:
使用上述代码(模式的使用):
package com.anxpp.designpattern.factorymethod;
public class TestUse {
public static void main(String args[]){
//分别定义两种结构
List<Integer> array = new ArrayList<Integer>();
List<Integer> link = new LinkList<Integer>();
//添加数据
for(int i = 1;i < 8; i++){
array.add(i);
link.add(i);
}
//获得迭代器
Iterator<Integer> ai = array.iterator();
Iterator<Integer> li = link.iterator();
//遍历并输出
while(ai.hasNext())
System.out.print(ai.next());
System.out.println();
while(li.hasNext())
System.out.print(li.next());
}
}
控制台会输出:
这就是工厂方法模式,其中遍历器也算是一种迭代器设计模式,后面会介绍。我不会跟你讲什么造车,造轮船,造人,我会给出实际应用。这里只是其中一种应用的举例,当一个接口的一系列实现需要另外的对象对其进行相同操作时,我们就可以这样用:在这个接口中定义返回另外一个对象的方法(工厂方法),然后再在这个接口的实现中,返回对其操作的对象。
上面这个例子会在迭代器模式中给出完整的实现代码。
一抽象产品类派生出多个具体产品类;一抽象工厂类派生出多个具体工厂类;每个具体工厂类只能创建一个具体产品类的实例。 即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)。“一对一”的关系。
与简单工厂间的取舍:工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。
如果再分得详细一点,一个工厂可能不只是生产手机(如小米除了手机,连电饭锅都有),但有得工厂智能生成低端的产品,而大一点的工厂可能通常是生成更高端的产品。所以一个工厂是不够用了,这时,就应该使用抽象工厂来解决这个问题。
别名:配套(Another Name:Kit)
Provide an interface for creating families of related or dependent objects without specifying their concrete classess.
提供一个创建一系列或相互依赖对象的接口,而无须指定他们的具体的类。
何时使用:
优点:
上述生成魅族产品的例子中,我们只生产了手机,但是它不止有手机一种产品,可能还有其他的,比如耳机,为了还可以生成耳机,我们需要对上例进行扩展。
我们先给出上面生成手机的例子的扩展后的抽象工厂模式代码,以比较这几种模式:
//抽象工厂模式
public class AbstractFactory {
public static void main(String args[]){
IFactory bigfactory = new BigFactory();
IFactory smallfactory = new BigFactory();
bigfactory.producePhone().run();
bigfactory.produceHeadset().play();
smallfactory.producePhone().run();
smallfactory.produceHeadset().play();
}
}
//抽象产品*2
interface Headset{
void play();
}
//抽象产品
interface MeizuPhone{
void run();
}
//具体产品*2*2
class PRO5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台PRO5");
}
}
class MX5 implements MeizuPhone{
@Override
public void run() {
System.out.println("我是一台MX5");
}
}
class EP21 implements Headset{
@Override
public void play() {
System.out.println("我是一副EP21");
}
}
class EP30 implements Headset{
@Override
public void play() {
System.out.println("我是一台EP30");
}
}
//抽象工厂
interface IFactory{
MeizuPhone producePhone();
Headset produceHeadset();
}
//具体工厂*2
class BigFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new PRO5();
}
@Override
public Headset produceHeadset() {
return new EP30();
}
}
//具体工厂*2
class SmallFactory implements IFactory{
@Override
public MeizuPhone producePhone() {
return new MX5();
}
@Override
public Headset produceHeadset() {
return new EP21();
}
}
在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式(不如上例减去耳机这种产品,就回到工厂方法模式了)。
这样举例子其实很空洞,这里只是为了比较三种模式,给出抽象的例子才更容易看出区别。
那么上例中实际应用就是生产迭代器的例子,这里也对齐扩展来介绍抽象工厂模式。Iterator迭代器是Collection专属的,但是现在我们希望能生产Map的迭代器,我们都知道,Map不是继承自Collection的,遍历的方式是不一样的,这就相当于2个产品族,接下来我们就要来实现它。
为了演示我们如果实现这个同时能生产Map和Collection的迭代器,我会将例子一步步贴出来:
首先是抽象产品,用来描述迭代器的公共接口:
然后是抽象工厂,用来返回不同迭代器:
//抽象工厂
public interface IIteratorFactory<T> {
IIterator<T> iteratorMap(Map<T,Object> m);
IIterator<T> iteratorCollection(Collection<T> c);
}
接下来是具体产品。
Collection的迭代器(具体产品):
Map的迭代器(具体产品):
//具体产品,Map迭代器(用到了代理模式)
public class IteratorMap<T> implements IIterator<T>{
Iterator<Map.Entry<T, Object>> iterator;
public IteratorMap(Map<T,Object> m){
iterator = m.entrySet().iterator();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object next() {
return iterator.next().getValue();
}
}
完成具体产品设计后,我们就要实现具体的工厂了:
至此,这个小框架就完成了,我们可以使用它来遍历Collection(List,Set,Queue都是集成自它)和Map:
//测试使用
public class TestUse {
public static void main(String args[]){
IIteratorFactory<Integer> factory = new IteratorFactory<>();
Collection<Integer> collection = new ArrayList<Integer>();
Map<Integer, Object> map = new LinkedHashMap<>();
for(int i=0;i<10;i++){
collection.add(i);
map.put(i, i);
}
IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection);
IIterator<Integer> iteratorMap = factory.iteratorMap(map);
while(iteratorCollection.hasNext())
System.out.print(iteratorCollection.next());
System.out.println();
while(iteratorMap.hasNext())
System.out.print(iteratorMap.next());
}
}
输出:
实际情况下,我们可能不应该这么做,以为Collection面向一种对象的容器,Map是面向两种对象的关联容器,但是此例使用抽象工厂模式确实实现了不同容器的 统一遍历方式。
如果一个容器持有的大量对象,他们都直接或间接集成自某一个类,使用访问者模式遍历也是一种很好的方式,具体在后面的访问者模式中会详细介绍。
工厂模式主要就涉及上面介绍的三种:
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
何时使用:
优点:
模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。
首先是这个类(产品):
//产品
public class MyDate {
String date;
}
然后就是抽象生成器,描述生成器的行为:
接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:
//具体生成器
public class DateBuilder1 implements IDateBuilder{
private MyDate myDate;
public DateBuilder1(MyDate myDate){
this.myDate = myDate;
}
@Override
public IDateBuilder buildDate(int y, int m, int d) {
myDate.date = y+"-"+m+"-"+d;
return this;
}
@Override
public String date() {
return myDate.date;
}
}
接下来是指挥官,向用户提供具体的生成器:
//指挥者
public class Derector {
private IDateBuilder builder;
public Derector(IDateBuilder builder){
this.builder = builder;
}
public String getDate(int y,int m,int d){
builder.buildDate(y, m, d);
return builder.date();
}
}
使用如下:
输出:
使用不同生成器,可以使原有产品表现得有点不一样。
往往在实际的应用中,生成器要做的工作不会这么简单,而是相对复杂的(因为其产品一般是比较复杂的),原有构建的维护会转移到生成器的维护上。
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
何时使用:
优点:
原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
例子中的抽象原型没有使用方法名clone(),其原因下面会介绍。
简单形式的原型模式:
//具体原型
public class SimplePrototype implements Prototype,Cloneable {
int value;
//clone()实现
@Override
public Object cloneSelf() {
SimplePrototype self = new SimplePrototype();
self.value = value;
return self;
}
//使用
public static void main(String args[]){
SimplePrototype simplePrototype = new SimplePrototype();
simplePrototype.value = 500;
SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
System.out.println(simplePrototypeClone.value);
}
}
//抽象原型
interface Prototype{
Object cloneSelf();//克隆自身的方法
}
//客户端使用
class Client{
SimplePrototype prototype;
public Client(SimplePrototype prototype){
this.prototype = prototype;
}
public Object getPrototype(){
return prototype.cloneSelf();
}
}
简单的原型模式就是在clone()实现时,new一个新的实例,然后为成员变量赋值后返回。
Java的原生支持
Java中所有类都直接或间接继承自Object类,Object类中已有clone()方法:”protected native Object clone() throws CloneNotSupportedException;“,可以看到权限是protected的,所以仅有子类可以访问这个方法,但我们可以在子类中重写这个方法,将访问权限上调到public,然后方法体里面return super.clone()。
我们能看到这个Object方法是可能会抛出异常的,我们必须实现Cloneable接口,才可以使用这个方法,否则会抛出“java.lang.CloneNotSupportedException”的异常。这个Cloneable接口其实是空的,实现它的目的在于让JVM知道这个对象是可以可复制的,否则clone()时就会发生异常。下面看演示代码:
调用这个方法时,成员变量会自动被复制。所以如果需要使用原型模式,Java原生支持就已经很好用了。
除了以上的原生支持,java中还有一种序列化,只需要对象实现Serializable接口。这样,我们可以将对象写入到流中,可以保存到文件,也可以通过网络发送到其他地方:
//使用Serializable支持克隆
public class SerializablePrototype implements Serializable{
private static final long serialVersionUID = 1L;
private int i;
private transient int notClone;//transient关键字的成员不会被序列化
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
public int getNotClone() {
return notClone;
}
public void setNotClone(int notClone) {
this.notClone = notClone;
}
public void writeToFile(String path) throws Exception{
FileOutputStream outStream = new FileOutputStream(path);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
objectOutputStream.writeObject(this);
outStream.close();
}
public SerializablePrototype ReadFromFile(String path) throws Exception{
File file = new File(path);
if(!file.exists())
file.createNewFile();
FileInputStream inStream = new FileInputStream(path);
ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
Object o= objectOutputStream.readObject();
inStream.close();
return (SerializablePrototype) o;
}
public static void main(String args[]) throws Exception{
String path = "D:/SerializablePrototype.instance";
SerializablePrototype prototype = new SerializablePrototype();
prototype.setI(123);
prototype.setNotClone(456);
prototype.writeToFile(path);
SerializablePrototype prototypeClone = new SerializablePrototype();
prototypeClone = prototype.ReadFromFile(path);
System.out.println(prototypeClone.getI() + " " + prototypeClone.getNotClone());
}
}//输出:123 0
我们来分析上例:这个对象有3个成员变量,而其中一个是有transient关键字申明的,一个是序列化id,一个是普通变量,在main方法中,想创建了对象,并设置值,然后写入到文件,再从文件读出来,最后输出读出来的对象的变量,普通变量是可以正常输出的(序列化id也可以,只是此处没有输出来而已),而transient申明的变量为0了,那就证明这个变量没有被保存到文件中,因为这个关键字声明的变量在序列化时会被忽略,而是后来创建时被自动初始化为0了(java中类的成员变量是基本数据类型时,如果没有初值,就会被自动初始化为0)。
额...这里是介绍模式,好像说得多了点,那原型模式就介绍到这儿了。
6、责任链模式(Chain of Responsibility Pattern)
使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
何时使用
有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求
程序希望动态的指定可处理用户请求的对象集合
优点
低耦合
可以动态的添加删除处理者或重新指派处理者的职责
可以动态改变处理者之间的先后顺序
通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。
比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身:
首先我们要定义一个接口(处理者),来描述他们共有的行为:
然后是具体的处理者(3个):
//第一个具体处理者,处理小于0的
public class Handler1 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<0) return -n;
else{
if(next==null)
throw new NullPointerException("next 不能为空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
//第三个具体处理者,处理>=0但小于10的
public class Handler3 implements Handler {
private Handler next;
@Override
public int handleRequest(int n) {
if(n<=Integer.MAX_VALUE) return n;
else{
if(next==null)
throw new NullPointerException("next 不能为空");
return next.handleRequest(n);
}
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
使用:
此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。
我们写java web程序的时候,通常会编写一些过滤器(Filter),然后配置到web.xml中,这其实就是责任链模式的一种实践。而使用Log4j记录日志,配置级别的时候,也同样用到了责任链模式。
我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。
别名:动作,事物(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.
将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
何时使用:
优点:
一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。
比如,我们有一个类可以在磁盘上新建或是删除文件(接收者),但是我们不希望直接提供给别人(请求者)使用,所以我们就为它的各种操作创建对应的命令,下面我们用代码来实现这个需求:
接收者,可以在磁盘删除或新建文件:
//接收者
public class MakeFile {
//新建文件
public void createFile(String name) throws IOException{
File file = new File(name);
file.createNewFile();
}
//删除文件
public boolean deleteFile(String name){
File file = new File(name);
if(file.exists()&&file.isFile()){
file.delete();
return true;
}
return false;
}
}
然后就是执行操作的接口:
我们需要创建具体的命令,这里就是2个,新建和删除:
//新建文件命令
public class CommandCreate implements Command {
MakeFile makeFile;
public CommandCreate(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws Exception {
makeFile.createFile(name);
}
}
最后就是请求者了:
//请求者
public class Client {
Command command;
public Client setCommand(Command command){
this.command = command;
return this;
}
public void executeCommand(String name) throws Exception{
if(command==null)
throw new Exception("命令不能为空!");
command.execute(name);
}
}
这样,我们就可以使用了,方式如下:
这里只是简单的实现,诸如CommandCreate命令的操作,如果我们需要undo的,那么就需要在命令接口中添加undo()方法并在具体命令中实现即可(将操作保存到栈里即可,undo的时候出栈并撤销操作)。
命令模式不宜滥用,比如:使用这种模式,会多出来很多对象(命令)。
命令模式中还有一种具体命令叫宏命令,它会包含一些其他具体命令的引用,执行宏命令可以执行这个宏命令所包含的引用的命令,概念清楚后实现起来也是容易的:
比如输出文章的命令,有中文输出命令、英文输出命令和宏命令,宏命令包含了其他两个命令的引用(可以使用列表保存这些命令),如果执行宏命令,宏命令会一次执行它所包含引用的其他命令(迭代命令列表并执行即可)。
Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
何时使用
优点
概念其实很简单。在有些问题上,我们可能希望自定定义简单的语言来描述,然后我们自己能解释它。
解释器模式一般包括四种角色:
使用该模式设计程序一般需要三个步骤:
这种模式一般会应用到一些特殊的问题上,使用这种模式一般需要了解形式语言中的基本知识。js内核就是一个强大的解释器。
简单的解释器模式,我们需要解释出来表达式的信息即可;而更深一层的,我们需要把表达式中的内容,翻译成我们程序运行的一部分来执行。
本初不提供例子,理解概念即可。有需求的时候再深入学习。如果确实需要举例,请在回复中说明,我会更新文章并添加一些内容。
别名:游标(Another Name:Cursor)
提供一种方法顺序访问一个聚合对象中的各个元素,而由不需要暴露该对象的内部细节。
何时使用
优点
通常容器提供的迭代器时可以高速遍历它本身的,而使用其本身的机制(如LinkedList中使用get(i)方法遍历)遍历性能可能并不好。
其实这个在工厂方法模式给出的例子就足够解释这个模式的使用了,如需看具体代码实现,请移步工厂方法模式中的例子查看。
其中主要的角色是集合、具体集合、迭代器、具体迭代器。
迭代器其实很简单,下面我们就继续工厂方法模式中的例子,将它完善一下:
稍微增强的集合接口:
//集合接口
public interface MyList<T> {
MyIterator<T> iterator(); //返回一个遍历器
boolean add(T t); //添加元素到列表
T get(int index); //得到元素
T remove(); //删除最后一个元素
boolean remove(T element); //删除指定元素
T remove(int index); //删除指定位置元素
boolean set(int index,T element); //修改指定位置值
int size();
}
容量可以自动增长的数组实现的集合:
链表实现的集合:
public class MyLinkedList<T> implements MyList<T>{
private int size; //存放的元素个数,会默认初始化为0
private Node<T> first; //首节点,默认初始化为null
@Override
public MyIterator<T> iterator() {
return new Iterator();
}
@Override
public boolean add(T t) {
if(size==0){
first = new Node<T>(t,null);
size++;
return true;
}
Node<T> node = first;
while(node.next!=null)
node = node.next;
node.next = new Node<T>(t,null);
size++;
return true;
}
@Override
public T get(int index) {
Node<T> node = first;
while(--index>=0)
node = node.next;
return node.data;
}
@Override
public T remove() {
return remove(size-1);
}
@Override
public T remove(int index) {
if(index<0||index>=size) return null;
Node<T> node = first;
while(--index>0)
node = node.next;
T element = node.next.data;
node.next = node.next.next;
size--;
return element;
}
@Override
public boolean remove(T element) {
if(element == null){
if(first.data==null){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data==null){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
else{
if(first.data.equals(element)){
first = first.next;
size--;
return true;
}
Node<T> node = first;
do{
if(node.next.data.equals(element)){
node.next = node.next.next;
size--;
return true;
}
node = node.next;
}
while(node.next!=null);
}
return false;
}
@Override
public boolean set(int index, T element) {
if(index<0||index>=size) return false;
Node<T> node = first;
while(--index>0)
node = node.next;
node.data = element;
return true;
}
@Override
public int size() {
return size;
}
//链表节点
private static class Node<T>{
T data;
Node<T> next;
Node(T data,Node<T> next){
this.data = data;
this.next = next;
}
}
//遍历器
private class Iterator implements MyIterator<T>{
private Node<T> next; //下一个节点
Iterator(){
next = first;
}
@Override
public boolean hasNext() {
return next!=null;
}
@Override
public T next() {
T data = next.data;
next = next.next;
return data;
}
@Override
public T remove() {
// TODO Auto-generated method stub
return null;
}
}
}
迭代器接口:
具体的迭代器就是集合具体实现里面的迭代器内部类,下面是使用 :
public class TestUse {
public static void main(String args[]){
//分别定义两种结构
MyList<String> array = new MyArrayList<String>();
MyList<String> link = new MyLinkedList<String>();
//添加数据
for(int i = 1;i < 15; i++){
array.add(i+"");
link.add(i+"");
}
//数组操作
System.out.println(array.remove());
System.out.println(array.get(5));
System.out.println(array.remove(5));
System.out.println(array.get(5));
System.out.println(array.remove("7"));
System.out.println(array.set(0, "00"));
//使用迭代器
MyIterator<String> ai = array.iterator();
while(ai.hasNext())
System.out.print(ai.next()+" ");
System.out.println();
System.out.println(link.remove());
System.out.println(link.get(5));
System.out.println(link.remove(5));
System.out.println(link.get(5));
System.out.println(link.remove("7"));
System.out.println(link.set(0, "00"));
//使用迭代器
MyIterator<String> li = link.iterator();
while(li.hasNext())
System.out.print(li.next()+" ");
System.out.println();
System.out.println("a size=" + array.size());
System.out.println("l size=" + link.size());
}
}
输出:
这里的迭代器就是典型的迭代器模式的实现(不过此处的迭代器没有实现remove()方法,查找集合的remove()方法实现也简单),介绍得有点多了,把集合都给介绍了...
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之前的交互。
何时使用
优点
两个类直接关联,是很好实现的,但如果不希望两个类直接发生交互,那么就需要使用中介者模式了。
比如有两个类,他们都是做持久化的,一个负责将数据写入文件,一个负责将数据写入数据库。他们谁先接收到数据是不确定的,但是要确保其中一个接收到数据后,另外一个也必须完成这些数据的持久化。如果我们直接将两个类关联在一起,互相调用是可以实现的,但不利于后期扩展或维护(比如再添加一个持久化组建,则原有的组建可能都需要修改),此时,若添加一个中介者,来协调他们,事儿就好办多了:
数据持久化的接口:
//同事(接口)
public interface IPersistent {
void getData(Object data);
void getData(Object data,Midiator midiator);
void saveData();
}
分别实现持久化到文件和持久化到数据库的组件(具体同事):
//具体同事
public class PersistentDB implements IPersistent{
private Object data;
@Override
public void getData(Object data, Midiator midiator) {
getData(data);
midiator.notifyOther(this, data);
}
@Override
public void saveData() {
System.out.println(data + " 已保存到数据库");
}
@Override
public void getData(Object data) {
this.data = data;
saveData();
}
}
中介者:
使用:
public class TestUse {
public static void main(String args[]){
Object data = "数据";
PersistentDB persistentDB = new PersistentDB();
PersistentFile persistentFile = new PersistentFile();
Midiator midiator = new Midiator();
midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
persistentDB.getData(data, midiator);
persistentFile.getData(data, midiator);
}
}//输出(省略了换行符):数据 已保存到数据库数据 已保存到文件数据 已保存到文件数据 已保存到数据库
就上例,如果还有许多的持久化组件(具体同事),可以在中介者中使用一个List来存放他们的引用,set的时候就添加。在通知其他同事时,遍历这个List,除了参数本身这个同事,其他的依次通知,即可实现。
中介者消除了同事与同事间直接的关联。
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)角色:负责保存备忘录对象。不检查备忘录对象的内容。
先看一个简单的实现方式:
备忘录角色对任何对象都提供一个接口,备忘录角色的内部所存储的状态就对所有对象公开,因此是破坏封装性的。
按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。
如果上例中,我们把备忘录以发起人的私有内部类的方式实现的话,那它就只能被发起人访问了,这正好就符合备忘录模式的要求,但是我们的负责人是需要存放备忘录的引用的,于是,我们提供一个公共的接口,他是空的,我们用备忘录实现它,主要就是利用其中的类型信息,具体实现如下:
//备忘录模式
public class BlackMemento {
public static void main(String[] args) {
BlankOriginator originator = new BlankOriginator(); //发起人
BlackCaretaker caretaker = new BlackCaretaker(); //负责人
originator.setState("stateOne"); //设置状态
caretaker.saveMemento(originator.createMemento()); //保存信息
originator.setState("stateTwo"); //修改状态
originator.recoverMemento(caretaker.recoverMemento());//恢复状态
}
}
interface MementoIF {}
//发起人
class BlankOriginator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public MementoIF createMemento(){
return new Memento(state);
}
public void recoverMemento(MementoIF memento){
this.setState(((Memento)memento).getState());
}
//以内部类实现备忘录角色
private class Memento implements MementoIF{
private String state;
private Memento(String state){
this.state = state;
}
private String getState() {
return state;
}
}
}
//负责人
class BlackCaretaker {
private MementoIF memento;
public MementoIF recoverMemento(){
return memento;
}
public void saveMemento(MementoIF memento){
this.memento = memento;
}
}
上面两个例子,演示的都是保存一个状态(不是指一个成员,而是只存了最近一次状态),即一个检查点,但是实际应用中,状态往往不止存储一次,我们将上面储存状态的变量改为一个栈(或队列,主要看需求)即可。比如:BlackCaretaker中的private MementoIF memento;改为LinkedList
针对上例,如果发起人和负责人我们并不介意他们必须是独立的,就可以把他们融合到一起,实现就会更佳简单,代码也简洁:
上例演示仅保存一个检查点。下面再给出一个实际的例子:
我们有个程序,供用户编辑文本,用户做出修改后,可以保存文本,保存修改后,可以依次恢复到保存前的多个状态中的一个,如果恢复后用户没有修改,还可以取消恢复(重做),下面就演示整个程序。
这个程序为了保证功能相对完整,写作演示可能有点长了:
//文本编辑器
public class TextEditor {
public static void main(String[] args) {
//使用这个文本编辑器
MyTextEditor editor = new MyTextEditor("这里是初始文本,可能为文件中读取的值。");
System.out.println("开始修改文本:");
editor.append("添加文字1");
editor.delWords(); //删除最后一个
// editor.delWords(2); //删除最后2个 这两个方法是没有问题的,这里避免控制台输出太多,取消这两次修改
// editor.delWords(1,5); //删除前面5个
System.out.println("开始恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
System.out.println("开始重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
System.out.println("再次恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
System.out.println("再次重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
System.out.println("再次恢复:");
for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
editor.append("添加文字2");
System.out.println("再次重做:");
for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
}
}
interface IMemento {}
//发起人兼负责人
class MyTextEditor {
public StringBuffer text;
private LinkedList<IMemento> mementos; //保存快照
private LinkedList<IMemento> undos; //保存撤销的操作
public MyTextEditor(){
this("");
}
public MyTextEditor(String defaultStr){
text = new StringBuffer(defaultStr);
mementos = new LinkedList<IMemento>();
undos = new LinkedList<IMemento>();
print();
}
public void clearHistory(){
mementos.clear();
undos.clear();
}
public void append(String appendStr){
if(appendStr==null||appendStr.length()==0) return;
createMemento();
text.append(appendStr);
print();
undos.clear();
}
//删除最后一个
public void delWords(){
delWords(1);
}
//删除最后n个
public void delWords(int n){
if(n<1||n>text.length()) return;
delWords(text.length()-n+1,text.length());
}
//删除中间start到end的字符,第一个文字为第一个(而不是0)
public void delWords(int start,int end){
if(start<1 || end>text.length()+1) return;
createMemento();
text = text.delete(start-1, end);
print();
}
public void reset(String text){
this.text = new StringBuffer(text);
}
//新的快照
public void createMemento(){
mementos.push(new Memento(this));
}
//恢复状态
public boolean recoverMemento(){
Memento memento = (Memento) mementos.poll();
if(memento==null) return false;
undos.push(new Memento(this));
reset(memento.state);
print();
return true;
}
//redo,redo的操作也可以恢复!
public boolean redo(){
Memento memento = (Memento) undos.poll();
if(memento==null) return false;
createMemento();
reset(memento.state);
print();
return true;
}
//内部类实现备忘录
private class Memento implements IMemento{
private String state;
private Memento(MyTextEditor editor){
this.state = editor.text.toString();
}
}
void print(){
System.out.println("当前文本:" + text);
}
}
控制台输出:
可以看到功能都是正确的,最后的重做因为在恢复后有修改发生,所以重做是无效的(目前我们所用的编辑器都是这种策略)。多次的恢复和重做是没有问题的。
该例子就是备忘录模式典型的例子。
别名: 依赖,发布/订阅(Another Name: Dependents, Publish/Subscribe)
定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
何时使用
优点
...
比如我们有个天气服务(主题),然后有多个使用它的客户端(观察者),包括android和iphone端app的服务(观察者),那么就可以使用这么模式。
我们需要一种结构存放天气信息(注意,省略了get、set方法!):
//天气的消息实体
public class WeatherInfo {
private long time;
private String weather;
public WeatherInfo(long time,String weather){
this.time = time;
this.weather = weather;
}
@Override
public boolean equals(Object obj) {
WeatherInfo info = (WeatherInfo) obj;
return info.time==this.time&&info.weather.equals(this.weather);
}
}
然后我们定义天气服务的接口(主题),以表示它应实现哪些功能:
接着就是客户端的接口描述:
//观察者
public interface Client {
void getWeather(WeatherInfo info);
}
然后实现具体的天气服务,这里同样用到了单例模式:
最后就是具体的客户端(观察者,此处给出两个):
public class ClientAndroidServer implements Client {
private static String name = "安卓服务";
private WeatherInfo info;
@Override
public void getWeather(WeatherInfo info) {
this.info = info;
dealMsg();
}
private void dealMsg(){
System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
}
}
好,现在就可以直接使用了:
public class TestUse {
public static void main(String args[]){
//创建主题
WeatherService service = WeatherService.instance;
//添加观察者
service.addClient(new ClientAndroidServer());
service.addClient(new ClientIphoneServer());
//更新主题
service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "多云"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24, "多云转晴"));
service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24*2, "晴"));
}
}
运行后,控制台有如下输出:
可以看出,观察者模式是一对多的。而本例是将更新的内容整个推给客户端。
而观察者模式中的数据有推和拉的区别,上例是推。
推的方式会将主题更改的内容全部直接推给客户端,拉的方式就是主题的数据更新后,不直接将数据推给客户端,而是先推送一个通知并提供对应的方法供客户端拉取数据。
如果上例中,天气服务每半小时更新(半点和整点推消息),还有一个客户端,不需要特别即时的天气消息,只取整点的消息,那么我们就可以使用拉的方式,数据更新后,给客户端推送一个标志,客户端自己按需取得数据(天气服务需要提供这样一个接口)。这就是拉。
java.util包中也提供了观察者模式的支持,因为java程序设计中使用比较广泛。有一个Observable类(相当于这里的具体主题)和一个Observer接口(相当于这里的主题接口):
public interface Observer {
void update(Observable o, Object arg);
}
其实跟上面的例子大体差不多,如果有这方面的需求,也可以直接使用Java的API。 但可以看到里面还在使用Vector(已过时),这其实是不推荐的,我们可以自己实现观察者模式,如果是多线程中,我们也可以自己实现同步。
别名:状态对象(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.
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
何时使用:
优点:
用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
可能这段时间老是在想数据库相关的事儿,所以一想例子就就想到这方面来了...不过,这样大家也能更好的对比设计模式之间的差异,下本例还是与这方面相关的。
设想我们有一个程序,要保存数据的,按照数据(这里以String举例)的大小,使用不同的方式保存。如果数据很小,我们将其保存到Redis(缓存数据库)中,如果数据库不太小也不太大,我们将其保存到mysql中,如果数据非常大,我们直接将其写入到文件中。数据的大小就是一种状态,很适合使用状态模式:
环境:
//环境(Context)
public class SaveDataController {
private ISaveData saveData;
public void save(String data){
//为了演示,此处的大的数据其实也是很小的
if(data.length()<1<<2)
saveData = SaveSmallData.instance;
else if(data.length()<1<<4)
saveData = SaveMiddleData.instance;
else
saveData = SaveBigData.instance;
saveData.save(data);
}
}
抽象状态:
具体状态(每个具体状态都使用到了单例模式):
//具体状态
public enum SaveSmallData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到Redis:" + data);
}
}
//具体状态
public enum SaveBigData implements ISaveData{
instance;
@Override
public void save(Object data) {
System.out.println("保存到文件:" + data);
}
}
使用:
输出:
保存到Redis:小数据
保存到Mysql:介于小数据和大数据之间的数据
保存到文件:这里就假定这是一个很大很大很大的数据
可以看到,我们对三种数据都使用了同一个对象的相同方法,但是行为是不同的,因为他们的状态不一样。
上面例子的状态更改是自动的,也可以添加setState()方法,手动切换状态,并在执行的方法体中不在自动判断状态。不过自动判断的,更智能一些,而手动切换状态的,可控性更好。
14、策略模式(Strategy Pattern)
定义一系列算法,把他们一个个封装起来,并且使他们可相互替换。本模式使得算法可独立于其他客户端而变化。
何时使用
优点
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下面就以一个示意性的实现讲解策略模式实例的结构。
策略模式中包括三种角色:
策略(Strategy):一个接口,定义了若干个算法(抽象方法)。
具体策略(ConcreteStrategy):策略的实现。
上下文/环境(Context):依赖于策略接口的类。
策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。 这其实也是典型的将代码向继承等级结构的上方集中的标准做法。
上次我们使用状态模式将数据按不同状态保存到不同地方,这里,我们使用策略模式来实现通过不同的策略来选择数据的保存方式。
首先是抽象的数据保持类(策略):
然后是具体的数据保存类,三个(具体策略):
public class SaveToRedis implements ISaveData {
@Override
public void save(Object data) {
System.out.println("数据:" + data + " 保存到Redis");
}
}
//具体策略
public class SaveToMysql implements ISaveData {
@Override
public void save(Object data) {
System.out.println("数据:" + data + " 保存到Mysql");
}
}
最后是客户端(环境Context):
使用:
public class TestUse {
public static void main(String