设计模式介绍及java描述
概述
设计模式是针对某一类问题的最优解决方案,是从许多优秀的软件中总结出来的。
java设计模式通常有23种模式可以分成3类:
-
创建型、行为型和、构型
创建型模式:创建型模式涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new创建用户。
- 创建模式有以下5个:
-
工厂模式、抽象工厂模式、生成器模式、原型模式和单例模式
行为型模式:行为模式涉及这样合理设计对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护易复用。
- 行为模式有以下11个:
-
责任链模式、命令模式、解释器模式、迭代器模式、中介模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问模式
结构型模式:结构类模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式:如何合理使用继承机制;和对象有关的结构型模式:合理使用对象组合机制
- 结构模式有以下7个:
-
适配器模式、组合模式、代理模式、享元模式、外观模式、桥接模式、装饰模式
1、单例模式:
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 何时使用:当系统需要某个类只有一个实例的时候。
- 优点:单例模式的类唯一实例由其本身控制,可以很好的控制用户何时访问它。
/**
* 单例模式
* 应用场景:
* 1、需要频繁的进行创建和销毁的对象;
* 2、创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
* 3、工具类对象;
* 4、频繁访问数据库或文件的对象。
*/
public class SimpleSingleton {
private SimpleSingleton() {}
/**
* 懒汉式加载
*
* @return
*/
private static SimpleSingleton singleton;
//线程不安全,不用于多线程使用(不推荐)
public static SimpleSingleton getIntance() {
if (singleton == null) {
singleton = new SimpleSingleton();
}
return singleton;
}
//线程安全,同步方法(不推荐使用)
public static synchronized SimpleSingleton getIntance2() {
if (singleton == null) {
singleton = new SimpleSingleton();
}
return singleton;
}
//线程安全,同步代码块(不可用)
public static SimpleSingleton getIntance3() {
if (singleton == null) {
synchronized (SimpleSingleton.class) {
singleton = new SimpleSingleton();
}
}
return singleton;
}
//线程安全,同步代码块,双重检查(推荐使用)
public static SimpleSingleton getIntance4() {
if (singleton == null) {
synchronized (SimpleSingleton.class) {
if (singleton == null) {
singleton = new SimpleSingleton();
}
}
}
return singleton;
}
/**
* 饿汉式加载
*/
private static SimpleSingleton simpleSingleton = new SimpleSingleton();
//静态常量(可用)
public static SimpleSingleton getIntance5() {
return simpleSingleton;
}
//静态代码块(可用)
static {
simpleSingleton = new SimpleSingleton();
}
public static SimpleSingleton getIntance6() {
return simpleSingleton;
}
//静态内部类(推荐用)
private static class SimpleSingletonIntance {
private static final SimpleSingleton INTANCE = new SimpleSingleton();
}
public static SimpleSingleton getIntance7() {
return SimpleSingletonIntance.INTANCE;
}
}
2、工厂模式
- 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 何时使用:
-
1、用户需要一个类的子类的实例,但不希望与该类的子类形成耦合
-
2、用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用
- 优点:
-
1、使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦
-
2、工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。
简单工厂模式
介绍工厂方法模式前,先介绍一下简单工厂模式,简单工厂模式也是一种工厂方法模式。
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么就可以使用简单工厂模式。
下面就是简单工厂的例子:
/**
* 简单工厂模式
*/
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();
}
//具体产品类
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 produce) throws Exception {
if (produce.equals("PRO5")) {
return new PRO5();
} else if (produce.equals("PRO6")) {
return new PRO6();
}
throw new Exception("No Such Class");
}
}
1、很容易看出,简单工厂模式是不易维护的,如果需要添加新的产品,则整个系统都需要修改。如果我们需要添加诸如PRO7、PRO8等产品,直接在工程类中添加即可。但是如果这时候根部不知道还有什么产品,只有到子类实现时才知道,这时候就需要工厂方法模式。
2、而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来是比较麻烦的,那么工厂方法模式正式解决这个问题的,下面就介绍工厂方法模式。
工厂方法模式
工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
- 针对上面的例子,如果使用工厂方法模式,即将工厂定义为一个接口,然后由具体的工厂来确定需要生成什么样的产品,为了与简单工厂比较,代码如下:
/**
* 工厂方法模式
*/
public class FactoryMethod {
public static void main(String[] args) {
IFactory bigFactory = new BigFactory();
bigFactory.produce().run();
IFactory smallFactory = new SmallFactory();
smallFactory.produce().run();
}
}
//抽象产品类
interface HuweiPhone {
void run();
}
//具体产品类
class Mate5 implements HuweiPhone {
@Override
public void run() {
System.out.println("我是一台Mate5");
}
}
//具体产品类
class Mate6 implements HuweiPhone {
@Override
public void run() {
System.out.println("我是一台Mate6");
}
}
//抽象的工厂
interface IFactory {
HuweiPhone produce();
}
//工厂1
class BigFactory implements IFactory{
@Override
public HuweiPhone produce() {
return new Mate5();
}
}
//工厂2
class SmallFactory implements IFactory{
@Override
public HuweiPhone produce() {
return new Mate6();
}
}
- 与简单工厂间的取舍:工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
- 可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。如果再分得详细一点,一个工厂可能不只是生产手机(如小米除了手机,连电饭锅都有),但有得工厂智能生成低端的产品,而大一点的工厂可能通常是生成更高端的产品。所以一个工厂是不够用了,这时,就应该使用抽象工厂来解决这个问题。
抽象工厂方法模式
优点:上述生成华为产品的例子中,我们只生产了手机,但是它不止有手机一种产品,可能还有其他的,比如耳机,为了还可以生成耳机,我们需要对上例进行扩展。
- 我们先给出上面生成手机的例子的扩展后的抽象工厂模式代码,以比较这几种模式:
public class AbstractFactory {
public static void main(String[] args) {
AFactory bigerFactory = new BigerFactory();
AFactory smallerFactory = new SmallerFactory();
bigerFactory.produceHeadset().play();
bigerFactory.produceXiaomoPhone().run();
smallerFactory.produceXiaomoPhone().run();
smallerFactory.produceHeadset().play();
}
}
//抽象产品1
interface Headset {
void play();
}
//抽象产品2
interface XiaomiPhone {
void run();
}
//Headset具体产品*2
class EP21 implements Headset {
@Override
public void play() {
System.out.println("我是一副EP21");
}
}
class EP22 implements Headset {
@Override
public void play() {
System.out.println("我是一副EP22");
}
}
//XiaomiPhone具体产品*2
class MixPhone implements XiaomiPhone {
@Override
public void run() {
System.out.println("我是一台MIXPhone");
}
}
class MaxPhone implements XiaomiPhone {
@Override
public void run() {
System.out.println("我是一台MaxPhone");
}
}
//抽象工厂
interface AFactory {
Headset produceHeadset();
XiaomiPhone produceXiaomoPhone();
}
//Headset具体工厂*2
class BigerFactory implements AFactory {
@Override
public Headset produceHeadset() {
return new EP21();
}
@Override
public XiaomiPhone produceXiaomoPhone() {
return new MixPhone();
}
}
class SmallerFactory implements AFactory {
@Override
public Headset produceHeadset() {
return new EP22();
}
@Override
public XiaomiPhone produceXiaomoPhone() {
return new MaxPhone();
}
}
工厂模式主要就涉及上面介绍的三种:
1、简单工厂模式:是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
2、工厂方法模式:是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
3、抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。
3、生成器模式
何时使用:
- 当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式来构造这样的对象。
- 当某些系统要求对象的构造过程必须独立于创建该对象的类时。
优点: - 1、生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
- 2、生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
- 3、可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
- 4、生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
- 5、当增加新的具体的生成器时,不必修改指挥者的代码,即该模式满足开-闭原则。
-
模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。
比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。
首先是这个类:
public class BuilderPattern {
public static void main(String[] args) {
MyDate myDate = new MyDate();
IDateBuilder builder;
builder = new DateBuilder1(myDate);
String date1 = new Derector(builder).getDate(2066, 3, 6);
System.out.println(date1);
builder = new DateBuilder2(myDate);
String date2 = new Derector(builder).getDate(2067, 4, 6);
System.out.println(date2);
}
}
//首先定义一个产品类
class MyDate {
String Date;
}
//然后抽象生成器,描述生成器的行为
interface IDateBuilder {
IDateBuilder buildDate(int y, int m, int d);
String date();
}
//接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:
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;
}
}
//具体生成器
class DateBuilder2 implements IDateBuilder {
private MyDate myDate;
public DateBuilder2(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;
}
}
//接下来是指挥官,向用户提供具体的生成器:
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();
}
}
4、原型模式
何时使用:
- 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
- 当对象的创建需要独立于它的构造过程和表示时。
- 一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过该实例复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便
优点:
-
当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
-
可以动态的保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
-
可以在运行时创建新的对象,而无须创建一系列类和集成结构。
-
可以动态的添加、删除原型的复制品。
要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。例子中的抽象原型没有使用方法名clone(),其原因下面会介绍。
简单形式的原型模式:
/**
* 简单原型模式
*/
public class SimplePrototype implements Prototype, Cloneable{
int value;
//克隆实现
@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;
System.out.println(simplePrototype.value);//原型对象值
//直接调用
SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
simplePrototypeClone.value = 600;
System.out.println(simplePrototypeClone.value);//复制原型对象值
//客户端调用
Client client = new Client(simplePrototype);
SimplePrototype prototypeClone = (SimplePrototype)client.getPrototype();
prototypeClone.value = 700;
System.out.println(prototypeClone.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自带支持clone
*/
public class APITestUse {
public static void main(String[] args) throws CloneNotSupportedException{
MyObject myObject = new MyObject();
myObject.i = 500;
MyObject clone = (MyObject) myObject.clone();
System.out.println(clone.i);
}
}
//一个可以复制的对象
class MyObject implements Cloneable{
int i;
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
- 调用这个方法时,成员变量会自动被复制。所以如果需要使用原型模式,Java原生支持就已经很好用了,所以这里了解一下就好。
5、责任链模式
何时使用:
- 有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
- 希望用户不必明确制定接受者的情况下,想多个接受者中的一个提交请求。
- 程序希望动态指定可处理用户请求的对象集合。
优点:
- 低耦合
- 可以动态的添加或删除处理者或重新指派处理者的职责
- 可以动态改变处理者之间的先后顺序
通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。
-
比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身:
/**
* 责任链模式
*/
public class Handler{
public static void main(String[] args) {
Handle h1,h2,h3;
h1 = new Handler1();
h2 = new Handler2();
h3 = new Handler3();
h1.setNextHandler(h2);
h2.setNextHandler(h3);
System.out.println(h1.handleRequest(-11));
System.out.println(h1.handleRequest(5));
System.out.println(h1.handleRequest(999));
}
}
//首先定义一个接口(处理者),用来描述他们的共同行为
interface Handle {
int handleRequest(int n);
void setNextHandler(Handle next);
}
//然后定义三个具体执行者
//第一个处理n小于0的
class Handler1 implements Handle{
private Handle 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(Handle next) {
this.next = next;
}
}
//第二个处理0<=n<10的
class Handler2 implements Handle{
private Handle next;
@Override
public int handleRequest(int n) {
if(n<10) return n*n;
else{
if (next ==null)
throw new NullPointerException("Next 不能为空");
}
return next.handleRequest(n);
}
@Override
public void setNextHandler(Handle next) {
this.next = next;
}
}
//第三个处理n>=10的
class Handler3 implements Handle{
private Handle 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(Handle next) {
this.next = next;
}
}
此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。
我们写java web程序的时候,通常会编写一些过滤器(Filter),然后配置到web.xml中,这其实就是责任链模式的一种实践。而使用Log4j记录日志,配置级别的时候,也同样用到了责任链模式。
我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。
6、命令模式
介绍:将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
何时使用:
- 程序需要在不同的时刻指定、排列和执行请求。
- 程序需要提供撤销操作。
- 程序需要支持宏操作。
优点:
- 在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,及请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此间的耦合。
- 命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有具体命令和接收者,新增加的调用者就可以使用已有的具体命令。
- 由于请求者的请求被封装到具体的命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
- 使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些具体命令。
一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。
比如,我们有一个类可以在磁盘上新建或是删除文件(接收者),但是我们不希望直接提供给别人(请求者)使用,所以我们就为它的各种操作创建对应的命令,下面我们用代码来实现这个需求:
/**
* 命令模式
*/
//接受者,可以在磁盘删除或新增文件
public class MakeFile {
public static void main(String[] args) throws Exception {
//接收者
MakeFile makeFile = new MakeFile();
//命令
CommandCreate create = new CommandCreate(makeFile);
CommandDelete delete = new CommandDelete(makeFile);
//请求者
Client client = new Client();
//执行命令
client.setCommand(create).executeCommand("/home/sunxw/Downloads/test1.txt");
client.setCommand(create).undoCommand();
client.setCommand(create).executeCommand("/home/sunxw/Downloads/test2.txt");
client.setCommand(create).undoCommand();
client.setCommand(delete).executeCommand("/home/sunxw/Downloads/test2.txt");
client.setCommand(delete).undoCommand();
}
//新建文件
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;
}
}
//然后执行操作接口
interface Command {
void execute(String name) throws IOException;
void undo() throws IOException;
}
//我们需要创建具体的命令,这里就是2个,新建和删除:
class CommandCreate implements Command {
MakeFile makeFile;
String name;
public CommandCreate(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws IOException {
makeFile.createFile(name);
this.name = name;
}
@Override
public void undo() {
makeFile.deleteFile(name);
}
}
class CommandDelete implements Command {
MakeFile makeFile;
String name;
public CommandDelete(MakeFile makeFile) {
this.makeFile = makeFile;
}
@Override
public void execute(String name) throws IOException {
makeFile.deleteFile(name);
this.name = name;
}
@Override
public void undo() throws IOException {
makeFile.createFile(name);
}
}
//最后就是请求者了:
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);
}
public void undoCommand() throws IOException {
command.undo();
}
}
-
命令模式不宜滥用,比如:使用这种模式,会多出来很多对象(命令)。
命令模式中还有一种具体命令叫宏命令,它会包含一些其他具体命令的引用,执行宏命令可以执行这个宏命令所包含的引用的命令,概念清楚后实现起来也是容易的:
比如输出文章的命令,有中文输出命令、英文输出命令和宏命令,宏命令包含了其他两个命令的引用(可以使用列表保存这些命令),如果执行宏命令,宏命令会一次执行它所包含引用的其他命令(迭代命令列表并执行即可)。
7、解释器模式
介绍:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
何时使用:
当有一个简单的语言需要解释执行,并且可以将该语言的每一个规则表示为一个类时,就可以使用解释器模式。
优点:
- 将每一个语法规则表示成一个类,方便与实现简单的语言。
- 由于使用类表示语法的规则,可以较容易改变或扩展语言的行为。
- 通过在类结构中加入新的方法,可以在解释的同时增加新的行为。
- 概念其实很简单。在有些问题上,我们可能希望自定定义简单的语言来描述,然后我们自己能解释它。
解释器模式一般包括四种角色:
抽象表达式:该角色为一个接口,负责定义抽象的解释操作。
终结符表达式:实现抽象表达式接口的类。
非终结表达式:也是实现抽象表达式的类。
上下文(Context):包含解释器之外的一些全局信息。
使用该模式设计程序一般需要三个步骤:
-
解析语句中的动作标记。
-
将标记规约为动作。
-
执行动作。
8、迭代器模式
迭代器就是典型的迭代器模式的实现,可以去看迭代器源码了解一下,这里就不对介绍了
9、中介者模式
介绍:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之前的交互。
何时使用:多个类相互耦合,形成了网状结构。
优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。
比如有两个类,他们都是做持久化的,一个负责将数据写入文件,一个负责将数据写入数据库。他们谁先接收到数据是不确定的,但是要确保其中一个接收到数据后,另外一个也必须完成这些数据的持久化。如果我们直接将两个类关联在一起,互相调用是可以实现的,但不利于后期扩展或维护(比如再添加一个持久化组建,则原有的组建可能都需要修改),此时,若添加一个中介者,来协调他们,事儿就好办多了
/**
* 中介者模式
*/
public class Mediator {
public static void main(String[] args) {
Object data = "数据";
PersistentDB persistentDB = new PersistentDB();
PersistentFile persistentFile = new PersistentFile();
Midiators midiators = new Midiators();
midiators.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
persistentDB.getData(data,midiators);
persistentFile.getData(data,midiators);
}
}
//持久化接口(同事)
interface IPersistent{
//获取数据
void getData(Object data);
//获取数据
void getData(Object data, Midiators midiators);
//保存数据
void saveData();
}
//分别实现持久化到文件和持久化到数据库的组件(具体同事):
class PersistentFile implements IPersistent{
private Object data;
@Override
public void getData(Object data) {
this.data = data;
saveData();
}
@Override
public void getData(Object data, Midiators midiators) {
getData(data);
midiators.notifyOther(this,data);
}
@Override
public void saveData() {
System.out.println(data+"已保存到文件");
}
}
//具体同事
class PersistentDB implements IPersistent{
private Object data;
@Override
public void getData(Object data) {
this.data = data;
saveData();
}
@Override
public void getData(Object data, Midiators midiators) {
getData(data);
midiators.notifyOther(this,data);
}
@Override
public void saveData() {
System.out.println(data+"已保存至数据库");
}
}
//创建中介者
class Midiators{
PersistentFile persistentFile;
//此处可以使用List来存放所有的同事
PersistentDB persistentDB;
public Midiators setPersistentFile(PersistentFile persistentFile) {
this.persistentFile = persistentFile;
return this;
}
public Midiators setPersistentDB(PersistentDB persistentDB) {
this.persistentDB = persistentDB;
return this;
}
public void notifyOther(IPersistent persistent, Object data){
//如果同事都放在List中,此处遍历即可
if (persistent instanceof PersistentDB) {
persistentFile.getData(data);
}
if (persistent instanceof PersistentFile) {
persistentDB.getData(data);
}
}
}
- 就上例,如果还有许多的持久化组件(具体同事),可以在中介者中使用一个List来存放他们的引用,set的时候就添加。在通知其他同事时,遍历这个List,除了参数本身这个同事,其他的依次通知,即可实现(List,List)。
10、备忘录模式
介绍:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存该状态,这样就可以将该对象恢复到之前保存的状态。
何时使用:1、必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复该对象先前的状态。2、一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己IDE内部状态。
优点:
- 备忘录模式使用备忘录可以吧原先者的内部状态全部保存起来,使是有很“亲密”的对象可以访问备忘录中的数据。
- 备忘录模式强调了类设计单一责任的原则,即将状态的刻画和保存分开。
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。
备忘录模式中有三种角色:
- 备忘录(Memento)角色:将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
- 发起人(Originator)角色:创建一个含有当前的内部状态的备忘录对象。使用备忘录对象存储其内部状态。
- 负责人(Caretaker)角色:负责保存备忘录对象。不检查备忘录对象的内容。
先看一个简单的实现方式:
/**
* 简单的备忘录模式
*/
public class SimpleMemento {
public static void main(String[] args) throws Exception {
Originator originator = new Originator();//发起人,要被保存的对象,也是他创建要保存的信息的
Caretaker caretaker = new Caretaker();//辅助保存的对象
originator.setState("stateOne");//设置状态
caretaker.saveMemento(originator.creatMemento());//保存状态
System.out.println(originator.getState());
originator.setState("stateTwo");//修改状态
System.out.println(originator.getState());
originator.recoverMemento(caretaker.recoverMemento());//恢复状态
System.out.println(originator.getState());
}
}
//备忘录
class Memento {
private String state;
//这是关键
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
//发起人
class Originator{
private String state;
public Memento creatMemento(){
return new Memento(state);
}
public void recoverMemento(Memento memento){
this.state = memento.getState();
}
public String getState(){
return state;
}
public void setState(String state) {
this.state = state;
}
}
//负责人
class Caretaker{
private Memento memento;
public Memento recoverMemento() throws Exception{
if (memento == null) {
throw new Exception("没有保存状态!");
}
return this.memento;//恢复状态
}
public void saveMemento(Memento memento){
this.memento = memento;//保存状态
}
}
- 备忘录角色对任何对象都提供一个接口,备忘录角色的内部所存储的状态就对所有对象公开,因此是破坏封装性的。
- 按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。
如果上例中,我们把备忘录以发起人的私有内部类的方式实现的话,那它就只能被发起人访问了,这正好就符合备忘录模式的要求,但是我们的负责人是需要存放备忘录的引用的,于是,我们提供一个公共的接口,他是空的,我们用备忘录实现它,主要就是利用其中的类型信息,具体实现如下:
/**
* 备忘录模式
*/
public class BlackMemento {
public static void main(String[] args) {
BlankOriginator originator = new BlankOriginator(); //发起人
BlackCaretaker caretaker = new BlackCaretaker(); //负责人
originator.setState("stateOne"); //设置状态
System.out.println(originator.getState());
caretaker.saveMemento(originator.createMemento()); //保存信息
originator.setState("stateTwo"); //修改状态
System.out.println(originator.getState());
originator.recoverMemento(caretaker.recoverMemento());//恢复状态
System.out.println(originator.getState());
}
}
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 Memento2(state);
}
public void recoverMemento(MementoIF mementoIF){
this.setState(((Memento2)mementoIF).getState());
}
//以内部类实现备忘录角色
private class Memento2 implements MementoIF{
private String state;
private Memento2(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 mementos 实现,保存的时候压栈(入队),恢复的时候出栈(出队),具体操作请自己实现。
- 针对上例,如果发起人和负责人我们并不介意他们必须是独立的,就可以把他们融合到一起,实现就会更佳简单,代码也简洁:
/**
* 备忘录模式:负责人与发起人合并,代码更加简介
*/
public interface IMemento{};
//负责人兼发起人
class OriginatorCaretaker{
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//创造快照
public Memento3 createMemento(){
return new Memento3(this);
}
//恢复状态
public void recoverMemento(IMemento iMemento){
Memento3 m = (Memento3)iMemento;
setState(m.state);
}
private class Memento3 implements IMemento{
private String state;
private Memento3(OriginatorCaretaker originatorCaretaker){
this.state = originatorCaretaker.state;
}
}
}
我们有个程序,供用户编辑文本,用户做出修改后,可以保存文本,保存修改后,可以依次恢复到保存前的多个状态中的一个,如果恢复后用户没有修改,还可以取消恢复(重做),下面就演示整个程序。
public class TextEditor {
public static void main(String[] args) {
//使用这个文本编辑器
MyTextEditor editor = new MyTextEditor("这里是初始文本,可能为文件中读取的值。");
System.out.println("开始修改文本:");
editor.append("添加文字1");
editor.delWords();//删除最后一个
editor.delWords(2);
editor.delWords(1,5);
System.out.println("开始恢复:");
//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
for(int i=0;i<10;i++) editor.recoverMemento();
System.out.println("开始重做:");
//重做大于实际恢复的次数不会出错,只会将文本设为最后状态
for(int i=0;i<10;i++) editor.redo();
System.out.println("再次恢复:");
//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
for(int i=0;i<10;i++) editor.recoverMemento();
System.out.println("再次重做:");
//重做大于实际恢复的次数不会出错,只会将文本设为最后状态
for(int i=0;i<10;i++) editor.redo();
System.out.println("再次恢复:");
//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
for(int i=0;i<10;i++) editor.recoverMemento();
editor.append("添加文字2");
System.out.println("再次重做:");
//重做大于实际恢复的次数不会出错,只会将文本设为最后状态
for(int i=0;i<10;i++) editor.redo();
System.out.println("再次恢复:");
//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
for(int i=0;i<10;i++) editor.recoverMemento();
}
}
interface TMemento {}
//负责人兼发起人
class MyTextEditor{
public StringBuffer text;
private LinkedList<TMemento> mementos;//保存快照
private LinkedList<TMemento> undos;//撤销操作
public MyTextEditor(){
}
public MyTextEditor(String defaultStr){
text = new StringBuffer(defaultStr);
mementos = new LinkedList<TMemento>();
undos = new LinkedList<TMemento>();
print();
}
public void clearHistory(){
mementos.clear();
undos.clear();
}
public void append(String appendStr){
if (appendStr.isEmpty()) {
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();
}
//重新设置text
public void reset(String text){
this.text = new StringBuffer(text);
}
//新的快照
public void createMemento(){
mementos.add(new Memento4(this));
}
//恢复快照
public boolean recoverMemento(){
Memento4 memento4 = (Memento4) mementos.poll();
if (memento4 == null) return false;
undos.push(new Memento4(this));
reset(memento4.state);
print();
return true;
}
//redo,redo的操作也可以恢复!
public boolean redo (){
Memento4 memento4 = (Memento4) undos.poll();
if (memento4 ==null) return false;
createMemento();
reset(memento4.state);
print();
return true;
}
private class Memento4 implements TMemento{
private String state;
private Memento4(MyTextEditor myTextEditor){
this.state = myTextEditor.text.toString();
}
}
void print(){
System.out.println("当前文本"+ text);
}
}
可以看到功能都是正确的,最后的重做因为在恢复后有修改发生,所以重做是无效的(目前我们所用的编辑器都是这种策略)。多次的恢复和重做是没有问题的。
该例子就是备忘录模式典型的例子。