设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
前言:本文只对各种设计模式的概念和基本实现原理进行分析,示例代码不一定是安全可执行的
参考文章:
Java开发中的23种设计模式详解(转)
java_my_life的博客园
JAVA设计模式初探之组合模式
Java策略模式(Strategy模式) 之体验
设计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的六大原则
开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
合成复用原则(Composite Reuse Principle)
原则是尽量使用合成/聚合的方式,而不是使用继承。
Java的23种设计模式
1. 单例模式(Singleton)
单例模式是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1. 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
代码示例(有关单例模式线程安全的问题在原文参考里面有详细讲解,具体使用案例可参考Calendar类。):
public class Singleton {
//1.将构造方法私有化,不允许外部直接创建对象
private Singleton() {
}
//2.创建类的唯一实例
private static Singleton singleton = null;
//3.提供一个用于获取实例的方法
public static Singleton getInstance() {
if(singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2. 工厂方法模式(Factory Method)
工厂方法模式是类的创建模式,又叫做虚拟构造子(Virtual Constructor)模式或者多态性工厂(Polymorphic Factory)模式。工厂方法模式的用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。
代码示例(关于工厂方法的改进请看原文):
1.创建一个二者的共同接口
public interface Sender {
public void Send();
}
2.创建两个实现类
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mail sender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
3.创建工厂类
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}
3. 抽象工厂模式(Abstract Factory)
抽象工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
代码示例:
1.创建一个二者的共同接口
public interface Sender {
public void Send();
}
2.创建两个实现类
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mail sender!");
}
}
public class SmsSender implements Sender {
@Override
public void Send() {
System.out.println("this is sms sender!");
}
}
3.创建两个工厂类
public class SendMailFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
public class SendSmsFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
4.创建一个提供接口
public interface Provider {
public Sender produce();
}
测试代码:
public class Test {
public static void main(String[] args) {
Provider provider = new SendMailFactory();
Sender sender = provider.produce();
sender.Send();
}
}
这里如果要扩展的话,只需要写一个实现类,实现Sender接口,再写一个工厂类,实现Provider接口就可以了,不用修改原有的代码,符合开闭原则。
4. 建造者模式(Builder)
建造者模式是对象的创建模式。建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
代码示例:
1.创建产品和部件接口
public interface Product { }
public interface Part { }
2.创建一个建造者接口
public interface Builder {
//创建部件A 比如创建汽车车轮
void buildPartA();
//创建部件B 比如创建汽车方向盘
void buildPartB();
//创建部件C 比如创建汽车发动机
void buildPartC();
//返回最后组装成品结果 (返回最后装配好的汽车)
//成品的组装过程不在这里进行,而是转移到下面的Director类中进行,
//从而实现了解耦过程和部件
Product getResult();
}
3.创建一个具体建造者类
public class ConcreteBuilder implements Builder {
//复杂产品的各个部件
Part partA, partB, partC;
public void buildPartA() {
//这里是具体如何构建partA的代码
};
public void buildPartB() {
//这里是具体如何构建partB的代码
};
public void buildPartC() {
//这里是具体如何构建partB的代码
};
public Product getResult() {
//返回最后组装成品结果
};
}
4.创建导演者类
public class Director {
private Builder builder;
public Director( Builder builder ) {
this.builder = builder;
}
// 将部件partA partB partC最后组成复杂对象
//这里是将车轮 方向盘和发动机组装成汽车的过程
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director( builder );
director.construct();
Product product = builder.getResult();
}
}
建造者模式利用一个导演者对象和具体建造者对象一个个地建造出所有的零件,从而建造出完整的产品对象。建造者模式将产品的结构和产品的零件的建造过程对客户端隐藏起来,把对建造过程进行指挥的责任和具体建造者零件的责任分割开来,达到责任划分和封装的目的。
5. 原型模式(Prototype)
原型模式是对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
代码示例:
1.创建抽象原型接口
public interface Prototype {
/**
* 克隆自身的方法(方法名可以随意更改)
* @return 一个从自身克隆出来的对象
*/
public Prototype clone();
}
2.创建具体原型类
public class ConcretePrototype implements Prototype, Cloneable {
private int field;
public Prototype clone() {
//浅克隆
Prototype prototype = (Prototype) super.clone();
return prototype;
}
}
更多关于浅克隆和深克隆的分析请看原文
6. 适配器模式(Adapter)
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
代码示例:
1.创建一个三相插座接口
public interface ThreePlugIf {
//使用三相电流供电
public void powerWithThree();
}
2.创建一个二相插座类
public class TowPlug {
public void powerWithTwo() {
System.out.println("使用二相电流供电");
}
}
3.创建一个笔记本类
public class NoteBook {
private ThreePlugIf plug;
public NoteBook(ThreePlugIf plug) {
this.plug = plug;
}
//使用插座充电
public void charge() {
plug.powerWithThree();
}
}
4.创建二相转三相适配器类
public class TwoPlugAdapter implements ThreePlugIf {
private TwoPlug plug;
public TwoPlugAdapter(TwoPlug plug) {
this.plug = plug;
}
@Override
public void powerWithThree() {
plug.powerWithTwo();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
TowPlug two = new TowPlug();
ThreePlugIf three = new TwoPlugAdapter(two);
NoteBook nb = new NoteBook(three);
nb.charge();
}
}
例子中通过适配器类的转换使得拥有三相插头的笔记本可以使用二相插座充电。适配器模式也分为好几种方式实现,更多方式可在原文中查看。
7. 装饰模式(Decorator)
装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
代码示例:
1.创建一个抽象构件类
public interface Sourceable {
public void method();
}
2.创建被装饰角色类
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
}
3.创建装饰类
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source) {
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Sourceable source = new Source();
Sourceable obj = new Decorator(source);
obj.method();
}
}
优点:
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
缺点:
产生过多相似的对象,不易排错。
使用场景:
需要扩展一个类的功能。
动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)
8. 代理模式(Proxy)
所谓代理,就是一个人或者机构代表另一个人或者机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。AOP就是使用的代理模式。
代码示例:
1.创建抽象对象类
public abstract class AbstractObject {
//操作方法
public abstract void operation();
}
2.创建目标对象类
public class RealObject extends AbstractObject {
@Override
public void operation() {
System.out.println("目标对象执行操作");
}
}
3.创建代理对象类
public class ProxyObject extends AbstractObject {
RealObject realObject = new RealObject();
@Override
public void operation() {
//调用目标对象之前可以做相关操作
System.out.println("before");
realObject.operation();
//调用目标对象之后可以做相关操作
System.out.println("after");
}
}
测试代码:
public class Test {
public static void main(String[] args) {
AbstractObject obj = new ProxyObject();
obj.operation();
}
}
从上面的例子可以看出代理对象将客户端的调用委派给目标对象,在调用目标对象的方法之前跟之后都可以执行特定的操作。
9. 外观模式(Facade)
外观模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。
代码示例:
1.创建两个子系统功能类
public class FeatureA {
public void methodA() {
System.out.println("执行A功能的方法");
}
}
public class FeatureB {
public void methodB() {
System.out.println("执行B功能的方法");
}
}
2.创建外观类
public class Facade {
private FeatureA featureA;
private FeatureB featureB;
public void method() {
featureA.methodA();
featureB.methodB();
System.out.println("执行外观类的方法");
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Facade facade = new Facade();
facade.method();
}
}
例子中,客户端只需要创建一个外观类对象,然后调用外观类提供的方法,隐藏了具体功能的实现,也免去了创建多个功能类对象的麻烦。
优点:
松散耦合,门面模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。
简单易用,门面模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟门面类交互就可以了。
更好的划分访问层次,通过合理使用Facade,可以帮助我们更好地划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到门面中,这样既方便客户端使用,也很好地隐藏了内部的细节。
10. 桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。
代码示例:
1.创建发送消息接口
public interface MessageIf {
//发送消息方法
public void send(String message,String toUser);
}
2.创建抽象的消息对象类
public abstract class AbstractMessage {
//持有一个实现发送消息接口的对象
protected MessageIf messageIf;
public AbstractMessage(MessageIf messageIf) {
this.messageIf = messageIf;
}
public void sendMessage(String message,String toUser) {
messageIf.send(message,toUser);
}
}
3.创建实际消息对象类
public class QQMessage implements MessageIf {
@Override
public void send(String message,String toUser) {
System.out.println("发送QQ消息:"+message+"给好友->"+toUser);
}
}
public class WeChatMessage implements MessageIf {
@Override
public void send(String message,String toUser) {
System.out.println("发送微信消息:"+message+"给好友->"+toUser);
}
}
4.创建发消息桥接类
public class MessageBridge extends AbstractMessage {
public MessageBridge (MessageIf messageIf) {
super(messageIf);
}
public void sendMessage(String message, String toUser) {
//这里可以对方法做扩展
super.sendMessage(message, toUser);
}
//也可以扩展自己的方法
}
测试代码:
public class Test {
public static void main(String[] args) {
MessageIf message = new QQMessage();
AbstractMessage bridge = new MessageBridge(message);
bridge.sendMessage("桥接模式发送QQ消息","mandy");
message = new WeChatMessage();
bridge = new MessageBridge(message);
bridge.sendMessage("桥接模式发送微信消息","mandy");
}
}
例子中一个MessageBridge同时支持两种发送消息的方式,对外提供的方法只有一个,如果发送消息的方式需要做扩展,只需要新建一个类然后继承发消息接口即可。连接数据库使用的DriverManager就是桥接模式实现的。
11. 组合模式(Composite)
组合模式将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有唯一性。
代码示例:
1.创建部件抽象类
public abstract class Component {
String name;
public abstract void add(Component c);
public abstract void remove(Component c);
public abstract void eachChild();
}
2.创建叶子结点类
public class Leaf extends Component {
// 叶子节点不具备添加的能力,所以不实现
@Override
public void add(Component c) {
}
// 叶子节点不具备添加的能力必然也不能删除
@Override
public void remove(Component c) {
}
// 叶子节点没有子节点所以显示自己的执行结果
@Override
public void eachChild() {
System.out.println(name + "执行了");
}
}
3.创建组合类
public class Composite extends Component {
// 用来保存节点的子节点,子节点也可以是组合
List<Component> list = new ArrayList<Component>();
// 添加节点 添加组合 添加部件
@Override
public void add(Component c) {
list.add(c);
}
// 删除节点 删除组合 删除部件
@Override
public void remove(Component c) {
list.remove(c);
}
// 遍历子部件
@Override
public void eachChild() {
System.out.println(name + "执行了");
for (Component c : list) {
c.eachChild();
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Composite rootComposite = new Composite();
rootComposite.name = "根节点";
// 左节点
Composite compositeLeft = new Composite();
compositeLeft.name = "左节点";
// 构建右节点,添加两个叶子几点,也就是子部件
Composite compositeRight = new Composite();
compositeRight.name = "右节点";
Leaf leaf1 = new Leaf();
leaf1.name = "右-子节点1";
Leaf leaf2 = new Leaf();
leaf2.name = "右-子节点2";
compositeRight.add(leaf1);
compositeRight.add(leaf2);
// 左右节点加入 根节点
rootComposite.add(compositeRight);
rootComposite.add(compositeLeft);
// 遍历组合部件
rootComposite.eachChild();
}
}
使用场景:当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。
12. 享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。因此,一个享元可以具有内蕴状态并可以共享。
一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
享元模式可以分成单纯享元模式和复合享元模式两种形式。
单纯享元模式代码示例(更多享元模式内容请看原文):
1.创建享元角色接口
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
2.创建享元角色类
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 构造函数,内蕴状态作为参数传入
*/
public ConcreteFlyweight(Character state) {
this.intrinsicState = state;
}
/**
* 外蕴状态作为参数传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态。
*/
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
3.创建享元工厂类
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
public Flyweight factory(Character state) {
//先从缓存中查找对象
Flyweight fly = files.get(state);
if(fly == null){
//如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
//把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.factory(new Character('a'));
fly.operation("First Call");
fly = factory.factory(new Character('b'));
fly.operation("Second Call");
fly = factory.factory(new Character('a'));
fly.operation("Third Call");
}
}
例子中,虽然客户端申请了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。
优点:
大幅度地降低内存中对象的数量,提升了系统的性能。
缺点:
使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
13. 策略模式(strategy)
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
策略模式一般主要分为以下三个角色:
环境角色(Context):持有一个策略类引用
抽象策略(Strategy):定义了多个具体策略的公共接口,具体策略类中各种不同的算法以不同的方式实现这个接口;Context使用这些接口调用不同实现的算法。一般的,我们使用接口或抽象类实现。
具体策略(ConcreteStrategy):实现抽象策略类中的相关的算法或操作。
代码示例:
1.创建抽象策略类
public abstract class AbstractStrategy {
/**
* 某个希望有不同策略实现的算法
*/
public abstract void algorithm();
}
2.创建两个具体策略类
public class ConcreteStrategy1 extends AbstractStrategy {
@Override
public void algorithm() {
System.out.println("-------------我是策略一算法-------------");
}
}
public class ConcreteStrategy2 extends AbstractStrategy {
@Override
public void algorithm() {
System.out.println("-------------我是策略二算法-------------");
}
}
3.创建环境角色类
public class Context {
private AbstractStrategy strategy;
public Context(AbstractStrategy strategy) {
this.strategy = strategy;
}
public void algorithm() {
strategy.algorithm();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Context context = new Context(new ConcreteStrategy1());
context.algorithm();
context = new Context(new ConcreteStrategy2());
context.algorithm();
}
}
测试代码中,客户端选择不同的具体策略,从而调用了不同的算法,具体使用场景如购物网站的支付页面,不同的银行卡提供了不同的支付算法,但是支付页面提供了统一的接口,只要用户选择对应的银行就可以使用不同的支付方法。
优点:
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
使用场景:
多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
14. 模板方法模式(Template Method)
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
模式涉及到的角色如下:
AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
ConcreteClass(具体子类):它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
代码示例:
1.创建模板抽象类
public abstract class AbstractTemplate {
/**
* 模板方法,算法框架
*/
public final void templateMethod() {
// 步骤1
step1();
// 步骤2
step2();
// 步骤3
step3();
if (hook()) {
// 步骤4
step4();
}
}
/**
* 钩子方法
*/
protected boolean hook() {
return true;
}
/**
* 步骤1基本方法
*/
private void step1() {
System.out.println("步骤1完成");
}
/**
* 步骤2抽象基本方法
*/
public abstract void step2();
/**
* 步骤3基本方法
*/
private void step3() {
System.out.println("步骤3完成");
}
/**
* 步骤4抽象基本方法
*/
public abstract void step4();
}
2.创建实现子类
public class ConcreteClass1 extends AbstractTemplate {
@Override
public void step2() {
System.out.println("ConcreteClass1 步骤2完成");
}
@Override
public void step4() {
System.out.println("ConcreteClass1 步骤4完成");
}
}
public class ConcreteClass2 extends AbstractTemplate {
@Override
public void step2() {
System.out.println("ConcreteClass2 步骤2完成");
}
@Override
public void step4() {
System.out.println("ConcreteClass2 步骤4完成");
}
@Override
protected boolean hook() {
return false;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
AbstractTemplate concrete1 = new ConcreteClass1();
concrete1.templateMethod();
AbstractTemplate concrete2 = new ConcreteClass2();
concrete2.templateMethod();
}
}
从例子中我们可以看到,模板方法分成三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。
抽象方法:一个抽象方法由抽象类声明、由其具体子类实现。
具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
钩子方法:一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个默认实现或空实现。
父类定义了一个final的模版方法,里面调用了以上三种方法,子类通过实现父类的抽象方法和钩子方法来改变父类模板方法的具体表现。
优点:
封装性好
复用性好
屏蔽细节
便于维护
缺点:
受Java继承的特性限制,子类只能继承模板抽象类。
使用场景:
算法或操作遵循相似的逻辑。
重构时(把相同的代码抽取到父类中)。
重要、复杂的算法,核心算法设计为模板算法。
15. 观察者模式(Template Method)
观察者模式是定义对象间的一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。邮件订阅和RSS订阅就是观察者模式的典型应用。
代码示例:
1.创建目标对象类,继承Observable接口(这个接口也可以自己编写,这里使用的是jdk自带的)
public class ConcreteSubject extends Observable {
/**对象内容**/
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
this.setChanged();
this.notifyObservers(content);
}
}
2.创建观察者类
public class ConcreteObserver implements Observer {
private String name;
@Override
public void update(Observable o, Object arg) {
System.out.println(name+"收到了目标对象更新内容:" + arg);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver();
observer1.setName("observer1");
ConcreteObserver observer2 = new ConcreteObserver();
observer2.setName("observer2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.setContent("内容有更新啦");
}
}
使用场景:
当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化。
如果在更改一个对象的时候,需要同时连带更改其他的对象,而且不知道有多少对象需要被连带改变。
当一个对象必须通知其他的对象,但是你又希望这个对象和其他被通知的对象是松散耦合的。
16. 迭代子模式(Iterator)
迭代子模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(internal representation)。
模式涉及到的角色如下:
抽象迭代子(Iterator)角色:此抽象角色定义出遍历元素所需的接口。
具体迭代子(ConcreteIterator)角色:此角色实现了Iterator接口,并保持迭代过程中的游标位置。
聚集(Aggregate)角色:此抽象角色给出创建迭代子(Iterator)对象的接口。
具体聚集(ConcreteAggregate)角色:实现了创建迭代子(Iterator)对象的接口,返回一个合适的具体迭代子实例。
代码示例:
1.创建抽象聚集角色类
public abstract class Aggregate {
/**
* 工厂方法,创建相应迭代子对象的接口
*/
public abstract Iterator createIterator();
}
2.创建具体聚集角色类
public class ConcreteAggregate extends Aggregate {
private Object[] objArray = null;
/**
* 构造方法,传入聚合对象的具体内容
*/
public ConcreteAggregate(Object[] objArray) {
this.objArray = objArray;
}
@Override
public Iterator createIterator() {
return new ConcreteIterator(this);
}
/**
* 取值方法:向外界提供聚集元素
*/
public Object getElement(int index) {
if (index < objArray.length) {
return objArray[index];
} else {
return null;
}
}
/**
* 取值方法:向外界提供聚集的大小
*/
public int size() {
return objArray.length;
}
}
3.创建抽象迭代子角色接口
public interface Iterator {
/**
* 迭代方法:移动到第一个元素
*/
public void first();
/**
* 迭代方法:移动到下一个元素
*/
public void next();
/**
* 迭代方法:是否为最后一个元素
*/
public boolean isDone();
/**
* 迭代方法:返还当前元素
*/
public Object currentItem();
}
4.创建具体迭代子角色类
public class ConcreteIterator implements Iterator {
//持有被迭代的具体的聚合对象
private ConcreteAggregate agg;
//内部索引,记录当前迭代到的索引位置
private int index = 0;
//记录当前聚集对象的大小
private int size = 0;
public ConcreteIterator(ConcreteAggregate agg) {
this.agg = agg;
this.size = agg.size();
index = 0;
}
/**
* 迭代方法:返还当前元素
*/
@Override
public Object currentItem() {
return agg.getElement(index);
}
/**
* 迭代方法:移动到第一个元素
*/
@Override
public void first() {
index = 0;
}
/**
* 迭代方法:是否为最后一个元素
*/
@Override
public boolean isDone() {
return (index >= size);
}
/**
* 迭代方法:移动到下一个元素
*/
@Override
public void next() {
if(index < size) {
index ++;
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Object[] objArray = {"One","Two","Three","Four","Five","Six"};
//创建聚合对象
Aggregate agg = new ConcreteAggregate(objArray);
//循环输出聚合对象中的值
Iterator it = agg.createIterator();
while(!it.isDone()) {
System.out.println(it.currentItem());
it.next();
}
}
}
例子中首先创建了一个聚集类实例,然后调用聚集对象的工厂方法createIterator()以得到一个迭代子对象。在得到迭代子的实例后,开始迭代过程并打印出所有的聚集元素。
优点:
迭代子模式简化了聚集的接口。迭代子具备了一个遍历接口,这样聚集的接口就不必具备遍历接口。
每一个聚集对象都可以有一个或多个迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。因此,一个聚集对象可以同时有几个迭代在进行之中。
由于遍历算法被封装在迭代子角色里面,因此迭代的算法可以独立于聚集角色变化。
17. 责任链模式(Chain of Responsibility)
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
代码示例:
1.创建抽象处理类
public abstract class AbstractHandler {
/**
* 持有一个类型为AbstractHandler的后继对象
*/
protected AbstractHandler successor;
public void setSuccessor(AbstractHandler successor) {
this.successor = successor;
}
/**
* 处理方法
*/
public abstract void process(String str);
}
2.创建两个AbstractHandler的子类
public class FirstHandler {
/**
* 处理方法
*/
public void process(String str) {
if (str.length < 10) {
System.out.println("this is a short String: " + str);
} else {
successor.process(str);
}
}
}
public class SecondHandler {
/**
* 处理方法
*/
public void process(String str) {
if (str.length > 30) {
System.out.println("this is a long String: " + str);
} else {
Ststem.out.println("this is a middle length String " + str);
}
}
}
测试代码:
public class Test {
public static void main(String[] args) {
AbstractHandler first = new FirstHandler();
AbstractHandler second = new SecondHandler();
first.setSuccessor(second);
first.process("hello");
first.process("hello,this is mandy");
first.process("hello,this is mandy.I'm a programmer.");
}
}
责任链上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18. 命令模式(Command)
命令模式定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
代码示例:
1.创建调用者类
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
public void action() {
command.execute();
}
}
2.创建抽象命令类
public abstract class Command {
public abstract void execute();
}
3.创建命令实现类
public class ConcreteCommand extends Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.doSomething();
}
}
4.创建接收者类
public class Receiver {
public void doSomething() {
System.out.println("接受者-业务逻辑处理");
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
//客户端直接执行具体命令方式
command.execute();
//客户端通过调用者来执行命令
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.action();
}
}
通过代码我们可以看到,当我们调用时,执行的时序首先是调用者类,然后是命令类,最后是接收者类。也就是说一条命令的执行被分成了三步,它的耦合度要比把所有的操作都封装到一个类中要低的多,而这也正是命令模式的精髓所在:把命令的调用者与执行者分开,使双方不必关心对方是如何操作的。
优点:
封装性好,每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。比如有一组文件操作的命令:新建文件、复制文件、删除文件。如果把这三个操作都封装成一个命令类,客户端只需要知道有这三个命令类即可,至于命令类中封装好的逻辑,客户端则无需知道。
扩展性好,在命令模式中,在接收者类中一般会对操作进行最基本的封装,命令类则通过对这些基本的操作进行二次封装,当增加新命令的时候,对命令类的编写一般不是从零开始的,有大量的接收者类可供调用,也有大量的命令类可供调用,代码的复用性很好。比如,文件的操作中,我们需要增加一个剪切文件的命令,则只需要把复制文件和删除文件这两个命令组合一下就行了,非常方便。
缺点:
命令如果很多,开发起来就要头疼了。特别是很多简单的命令,实现起来就几行代码的事,而使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装。
使用场景:对于大多数请求-响应模式的功能,比较适合使用命令模式,正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。
19. 备忘录模式(Memento)
备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。
代码示例:
1.创建发起人类
public class Originator {
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento() {
return new Memento(this.state);
}
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
2.创建备忘录类
public 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;
}
}
3.创建管理角色
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("状态1");
System.out.println("初始状态:"+originator.getState());
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.setState("状态2");
System.out.println("改变后状态:"+originator.getState());
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复后状态:"+originator.getState());
}
}
上面代码演示了一个单状态单备份的例子,逻辑非常简单:Originator类中的state变量需要备份,以便在需要的时候恢复;Memento类中,也有一个state变量,用来存储Originator类中state变量的临时状态;而Caretaker类就是用来管理备忘录类的,用来向备忘录对象中写入状态或者取回状态。
优点:
当发起人角色中的状态改变时,有可能这是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。
备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。
缺点:
在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗是比较严重的。
使用场景:如果有需要提供回滚操作的需求,使用备忘录模式非常适合,比如jdbc的事务操作,文本编辑器的Ctrl+Z恢复等。
20. 状态模式(State)
用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
模式涉及到的角色如下:
环境(Context)角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。
代码示例:
1.创建环境角色类
public class Context {
//持有一个State类型的对象实例
private State state;
public void setState(State state) {
this.state = state;
}
/**
* 用户感兴趣的接口方法
*/
public void request(String sampleParameter) {
//转调state来处理
state.handle(sampleParameter);
}
}
2.创建抽象状态类
public interface State {
/**
* 状态对应的处理
*/
public void handle(String sampleParameter);
}
3.创建两个具体状态类
public class ConcreteStateA implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateA handle :" + sampleParameter);
}
}
public class ConcreteStateB implements State {
@Override
public void handle(String sampleParameter) {
System.out.println("ConcreteStateB handle :" + sampleParameter);
}
}
测试代码:
public class Test {
public static void main(String[] args) {
//创建状态
State stateA = new ConcreteStateA();
State stateB = new ConcreteStateB();
//创建环境
Context context = new Context();
//将状态A设置到环境中
context.setState(stateA);
//请求
context.request("test");
//将状态B设置到环境中
context.setState(stateB);
//请求
context.request("test");
}
}
从上面可以看出,环境类Context的行为request()是委派给某一个具体状态类的。通过使用多态性原则,可以动态改变环境类Context的属性State的内容,使其从指向一个具体状态类变换到指向另一个具体状态类,从而使环境类的行为request()由不同的具体状态类来执行。
21. 访问者模式(Visitor)
访问者模式是对象的行为模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
模式涉及到的角色如下:
抽象访问者(Visitor)角色:声明了一个或者多个方法操作,形成所有的具体访问者角色必须实现的接口。
具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参数。
具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。
结构对象(ObjectStructure)角色:有如下的责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如List或Set。
代码示例:
1.创建抽象访问者角色
public interface Visitor {
/**
* 对应于NodeA的访问操作
*/
public void visit(NodeA node);
/**
* 对应于NodeB的访问操作
*/
public void visit(NodeB node);
}
2.创建两个具体访问者类
public class VisitorA implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println("VisitorA:" + node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println("VisitorA:" + node.operationB());
}
}
public class VisitorB implements Visitor {
/**
* 对应于NodeA的访问操作
*/
@Override
public void visit(NodeA node) {
System.out.println("VisitorB:" + node.operationA());
}
/**
* 对应于NodeB的访问操作
*/
@Override
public void visit(NodeB node) {
System.out.println("VisitorB:" + node.operationB());
}
}
3.创建抽象节点类
public abstract class Node {
/**
* 接受操作
*/
public abstract void accept(Visitor visitor);
}
4.创建两个具体节点类
public class NodeA extends Node {
/**
* 接受操作
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeA特有的方法
*/
public String operationA() {
return "NodeA";
}
}
public class NodeB extends Node {
/**
* 接受操作
*/
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* NodeB特有的方法
*/
public String operationB() {
return "NodeB";
}
}
5.创建结构对象角色类
public class ObjectStructure {
private List<Node> nodes = new ArrayList<Node>();
/**
* 执行方法操作
*/
public void action(Visitor visitor) {
for (Node node : nodes) {
node.accept(visitor);
}
}
/**
* 添加一个新元素
*/
public void add(Node node) {
nodes.add(node);
}
}
测试代码:
public class Test {
public static void main(String[] args) {
// 创建一个结构对象
ObjectStructure os = new ObjectStructure();
// 给结构增加一个节点
os.add(new NodeA());
// 给结构增加一个节点
os.add(new NodeB());
// 创建一个访问者
Visitor visitor = new VisitorA();
os.action(visitor);
}
}
虽然在这个示意性的实现里并没有出现一个复杂的具有多个树枝节点的对象树结构,但是,在实际系统中访问者模式通常是用来处理复杂的对象树结构的,而且访问者模式可以用来处理跨越多个等级结构的树结构问题。这正是访问者模式的功能强大之处。
优点:
好的扩展性,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
好的复用性,可以通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为,可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点:
对象结构变化很困难,不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变,代价太高。
破坏封装,访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructrue,这破坏了对象的封装性。
22. 中介者模式(Mediator)
中介者模式是用一个中介者对象封装的一系列对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。
模式涉及到的角色如下:
抽象中介者:在里面定义各个同事对象之间的交互对象,可以是公共的通信方法,比如changed 方法,大家都用,也可以是小范围的交互方法。
具体中介者:从抽象中介者继承而来,实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。
抽象同事:所有具体同事类的父类,通常实现成抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的功能。
具体同事:具体的同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者负责与其他的同事进行交互。
代码示例:
1.创建抽象同事类
public abstract class AbstractColleague {
protected int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public abstract void setNumber(int number, AbstractMediator am);
}
2.创建两个具体同事类
public class ColleagueA extends AbstractColleague {
public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.AaffectB();
}
}
public class ColleagueB extends AbstractColleague {
public void setNumber(int number, AbstractMediator am) {
this.number = number;
am.AaffectA();
}
}
3.创建抽象中介者类
public abstract class AbstractMediator {
protected AbstractColleague A;
protected AbstractColleague B;
public AbstractMediator(AbstractColleague a, AbstractColleague b) {
A = a;
B = b;
}
public abstract void AaffectB();
public abstract void BaffectA();
}
4.创建具体中介者类
public class Mediator extends AbstractMediator {
public Mediator(AbstractColleague a, AbstractColleague b) {
super(a, b);
}
//处理A对B的影响
public void AaffectB() {
int number = A.getNumber();
B.setNumber(number*100);
}
//处理B对A的影响
public void BaffectA() {
int number = B.getNumber();
A.setNumber(number/100);
}
}
测试代码:
public class Test{
public static void main(String[] args) {
AbstractColleague collA = new ColleagueA();
AbstractColleague collB = new ColleagueB();
AbstractMediator am = new Mediator(collA, collB);
System.out.println("==========通过设置A影响B==========");
collA.setNumber(1000, am);
System.out.println("collA的number值为:"+collA.getNumber());
System.out.println("collB的number值为A的10倍:"+collB.getNumber());
System.out.println("==========通过设置B影响A==========");
collB.setNumber(1000, am);
System.out.println("collB的number值为:"+collB.getNumber());
System.out.println("collA的number值为B的0.1倍:"+collA.getNumber());
}
}
优点:
适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。
23.解释器模式(Interpreter)
解释器模式是类的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。
模式涉及到的角色如下:
抽象表达式(Expression)角色:声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称做解释操作。
终结符表达式(Terminal Expression)角色:实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
非终结符表达式(Nonterminal Expression)角色:文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+”就是非终结符,解析“+”的解释器就是一个非终结符表达式。
环境(Context)角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。
代码示例:
1.创建抽象表达式角色类
public abstract class Expression {
/**
* 以环境为准,本方法解释给定的任何一个表达式
*/
public abstract boolean interpret(Context ctx);
/**
* 检验两个表达式在结构上是否相同
*/
public abstract boolean equals(Object obj);
/**
* 返回表达式的hash code
*/
public abstract int hashCode();
/**
* 将表达式转换成字符串
*/
public abstract String toString();
}
2.创建5个具体表达式角色类
/**
*一个Constant对象代表一个布尔常量
*/
public class Constant extends Expression {
private boolean value;
public Constant(boolean value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Constant) {
return this.value == ((Constant)obj).value;
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean interpret(Context ctx) {
return value;
}
@Override
public String toString() {
return new Boolean(value).toString();
}
}
/**
*一个Variable对象代表一个有名变量
*/
public class Variable extends Expression {
private String name;
public Variable(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Variable) {
return this.name.equals(
((Variable)obj).name);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public String toString() {
return name;
}
@Override
public boolean interpret(Context ctx) {
return ctx.lookup(this);
}
}
/**
*代表逻辑“与”操作的And类,表示由两个布尔表达式通过逻辑“与”操作给出一个新的
*布尔表达式的操作
*/
public class And extends Expression {
private Expression left,right;
public And(Expression left , Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof And) {
return left.equals(((And)obj).left) &&
right.equals(((And)obj).right);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean interpret(Context ctx) {
return left.interpret(ctx) && right.interpret(ctx);
}
@Override
public String toString() {
return "(" + left.toString() + " AND " + right.toString() + ")";
}
}
/**
*代表逻辑“或”操作的Or类,代表由两个布尔表达式通过逻辑“或”操作给出一个新的
*布尔表达式的操作
*/
public class Or extends Expression {
private Expression left,right;
public Or(Expression left , Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Or) {
return this.left.equals(((Or)obj).left) && this.right.equals(((Or)obj).right);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean interpret(Context ctx) {
return left.interpret(ctx) || right.interpret(ctx);
}
@Override
public String toString() {
return "(" + left.toString() + " OR " + right.toString() + ")";
}
}
/**
*代表逻辑“非”操作的Not类,代表由一个布尔表达式通过逻辑“非”操作给出一个新的
*布尔表达式的操作
*/
public class Not extends Expression {
private Expression exp;
public Not(Expression exp) {
this.exp = exp;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Not)
{
return exp.equals(
((Not)obj).exp);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean interpret(Context ctx) {
return !exp.interpret(ctx);
}
@Override
public String toString() {
return "(Not " + exp.toString() + ")";
}
}
3.创建环境类
public class Context {
private Map<Variable,Boolean> map = new HashMap<Variable,Boolean>();
public void assign(Variable var , boolean value) {
map.put(var, new Boolean(value));
}
public boolean lookup(Variable var) throws IllegalArgumentException {
Boolean value = map.get(var);
if(value == null) {
throw new IllegalArgumentException();
}
return value.booleanValue();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
Context ctx = new Context();
Variable x = new Variable("x");
Variable y = new Variable("y");
Constant c = new Constant(true);
ctx.assign(x, false);
ctx.assign(y, true);
Expression exp = new Or(new And(c,x) , new And(y,new Not(x)));
System.out.println("x=" + x.interpret(ctx));
System.out.println("y=" + y.interpret(ctx));
System.out.println(exp.toString() + "=" + exp.interpret(ctx));
}
}