目录
6. Adapter Class/Object(适配器模式)
15. Chain of Responsibility(责任链)
前言
设计模式是很重要的编码技巧和编码思想,对于构建结构合理,逻辑清晰的代码有很大的指导意义。
设计模式的六大原则
1、开闭原则(Open Close Principle)
对扩展开放,对修改关闭。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且相关功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现。
3、依赖倒转原则(Dependence Inversion Principle)
针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
5、迪米特法则,又称最少知道原则(Demeter Principle)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
尽量使用合成/聚合的方式,而不是使用继承。
设计模式的分类
总体来说设计模式分为三大类:
创建型模式(共五种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(共七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(共十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
模式 类型 | 描述 | 具体包括 |
创建型模式 | 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 |
|
结构型模式 | 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 |
|
行为型模式 | 这些设计模式特别关注对象之间的通信。 |
|
创建型
1. Factory Method(工厂方法)
意图:
Factory Method 将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,有子类决定实例化哪个类。
UML类图如下:
类图角色分析:
(1) Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类
(2) ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应
(3) Factory(抽象工厂):在抽象工厂类中声明了工厂方法( Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
(4) ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了在抽象工厂中声明的工厂方法,并可由客户端调用,返回一个具体产品类的实例。
代码示例:
/**
* 抽象工厂
*/
public interface ProductFactory {
Product manufacture();
}
/**
* 抽象产品
*/
public interface Product {
void show();
}
/**
* 具体工厂A
*/
public class FactoryA implements ProductFactory{
@Override
public Product manufacture() {
System.out.println("具体工厂A");
return new ProductA();
}
}
/**
* 具体工厂B
*/
public class FactoryB implements ProductFactory{
@Override
public Product manufacture() {
System.out.println("具体工厂B");
return new ProductB();
}
}
/**
* 具体产品A
*/
public class ProductA implements Product{
@Override
public void show() {
System.out.println("具体产品A");
}
}
/**
* 具体产品B
*/
public class ProductB implements Product{
@Override
public void show() {
System.out.println("具体产品B");
}
}
public class Client {
public static void main(String[] args) {
ProductFactory factoryA = new FactoryA();
Product productA = factoryA.manufacture();
productA.show();
ProductFactory factoryB = new FactoryB();
Product productB = factoryB.manufacture();
productB.show();
}
}
模式优点:
1,符合开闭原则:增加新产品时,只需要增加相应的具体工厂类和具体产品类;
2,符合单一职责原则:一个工厂只负责一个产品的创建
模式缺点:
1,一个具体工厂只生产一个具体产品,当工厂和产品增多,类也增多,系统复杂度上升;
2,如果需要更换具体工厂里面生产的具体产品还是要更改原有代码,这就违反了开闭原则;
2. Abstract Factory(抽象工厂)
抽象工厂模式(Abstract Factory Pattern):属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。
意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
以下图为例,有手机和路由器两种产品,有华为和小米两种品牌,两种品牌都可以生产手机和路由器;
- 有手机和路由器两种产品,定义两个接口;
- 小米和华为都可以生产这两种产品,所以有4个实现类;
- 现在需要创建华为和小米的工厂类,先将工厂类进行抽象,里面有创建两个产品的方法,返回的是产品的接口类;
- 创建华为和小米的工厂实现类,继承工厂类接口,实现创建各自产品的方法;
- 客户端调用时,直接用工厂接口类创建需要的工厂,拿到对应的产品;
代码示例:
- 工厂接口类
//产品工厂接口 public interface IProductFactory { //生产手机 IPhoneProduct phoneProduct(); //生成路由器 IRouterProduct routerProduct(); }
- 华为和小米工厂实现类,继承工厂接口
//华为工厂实现类 public class HuaweiFactory implements IProductFactory { @Override public IPhoneProduct phoneProduct() { return new HuaweiPhone(); } @Override public IRouterProduct routerProduct() { return new HuaweiRouter(); } } //小米工厂实现类 public class XiaomiFactory implements IProductFactory { @Override public IPhoneProduct phoneProduct() { return new XiaomiPhone(); } @Override public IRouterProduct routerProduct() { return new XiaomiRouter(); } }
- 手机产品接口和路由器产品接口
//手机产品接口 public interface IPhoneProduct { //开机 void start(); //关机 void shutdown(); //打电话 void callup(); //发邮件 void sendSMS(); } //路由器产品接口 public interface IRouterProduct { //开机 void start(); //关机 void shutdown(); //打开wifi void openwifi(); //设置 void setting(); }
- 华为和小米的产品的4个实现类
//华为手机实现类 public class HuaweiPhone implements IPhoneProduct { @Override public void start() { System.out.println("开启华为手机"); } @Override public void shutdown() { System.out.println("关闭华为手机"); } @Override public void callup() { System.out.println("华为手机打电话"); } @Override public void sendSMS() { System.out.println("华为手机发邮件"); } } //华为路由器实现类 public class HuaweiRouter implements IRouterProduct { @Override public void start() { System.out.println("开启华为路由器"); } @Override public void shutdown() { System.out.println("关闭华为路由器"); } @Override public void openwifi() { System.out.println("打开华为wifi"); } @Override public void setting() { System.out.println("设置华为路由器"); } } //小米手机实现类 public class XiaomiPhone implements IPhoneProduct { @Override public void start() { System.out.println("开启小米手机"); } @Override public void shutdown() { System.out.println("关闭小米手机"); } @Override public void callup() { System.out.println("小米手机打电话"); } @Override public void sendSMS() { System.out.println("小米手机发邮件"); } } //小米路由器实现类 public class XiaomiRouter implements IRouterProduct { @Override public void start() { System.out.println("开启小米路由器"); } @Override public void shutdown() { System.out.println("关闭小米路由器"); } @Override public void openwifi() { System.out.println("打开小米wifi"); } @Override public void setting() { System.out.println("设置小米路由器"); } }
- 客户端,通过
IProductFactory
创建各自的工厂,通过工厂拿到对应的产品public class Client { public static void main(String[] args) { System.out.println("============小米产品============"); //创建小米工厂 IProductFactory xiaomiFactory = new XiaomiFactory(); //生产小米手机 IPhoneProduct xiaomiPhone = xiaomiFactory.phoneProduct(); xiaomiPhone.start(); xiaomiPhone.sendSMS(); //生产小米路由器 IRouterProduct xiaomiRouter = xiaomiFactory.routerProduct(); xiaomiRouter.openwifi(); xiaomiRouter.setting(); System.out.println("============华为产品============"); //创建华为工厂 IProductFactory huaweiFactory = new HuaweiFactory(); //生产华为手机 IPhoneProduct huaweiPhone = huaweiFactory.phoneProduct(); huaweiPhone.start(); huaweiPhone.sendSMS(); //生产华为路由器 IRouterProduct huaweiRouter = huaweiFactory.routerProduct(); huaweiRouter.openwifi(); huaweiRouter.setting(); } }
抽象工厂模式的实现就是这样,还记得前面讲的产品族和产品等级的概念吗,如果新增一个产品族或产品等级会怎样?
拓展一个产品族:
我们会发现,拓展一个产品族是非常困难的,例如产品族中新增一个笔记本电脑,也就是说华为和小米现在可以生产电脑了,如下图所示(黄色字体为新增一个产品族需要做的事),对顶层的工厂接口类也要修改,这是非常麻烦的;
拓展一个产品等级:
如果扩展一个产品等级,例如新增一个手机,也就是说新增一个品牌来生产手机,如下图所示(黄色字体为新增一个产品等级需要做的事),新增一个产品等级不用修改原来的代码,符合OCP原则(开放关闭原则),这是非常舒服的;
总结抽象工厂模式的优缺点 :
优点:一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象(将一个系列的产品统一一起创建);
缺点:
- 产品族扩展非常困难,要增加一个系列的某一产品,既要修改工厂抽象类里加代码,又修改具体的实现类里面加代码;
- 增加了系统的抽象性和理解难度;
抽象工厂模式符合依赖抽象原则
- 创建对象实例时,不要直接 new一个对象, 而是把创建对象的动作放在一个工厂的方法中;
- 不要让类继承具体类,而是继承抽象类或者是实现接口;
- 不要覆盖基类中已经实现的方法
3. Builder(建造者)
创建者模式:是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
UML类图如下:
建造者(Builder)模式包含如下角色:
- 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
- 具体建造者类(ConcreteBuilder):实现 Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
- 产品类(Product):要创建的复杂对象。
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
示例说明
为了方便说明,我直接通过举例子的方式来说明构建者模式的定义过程,假设我们要实现创建共享单车:
我们知道,生产自行车是一个复杂的过程,它包含了车架、
车座
等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。
组件说明:
这里Bike是产品,包含车架,车座等组件;Builder
是抽象建造者,MobikeBuilder
和OfoBuilder
是具体的建造者;Director
是指挥者。
//自行车类
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
// 抽象 builder 类
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
//摩拜单车Builder类
public class MobikeBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("铝合金车架");
}
@Override
public void buildSeat() {
mBike.setSeat("真皮车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//ofo单车Builder类
public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
mBike.setFrame("碳纤维车架");
}
@Override
public void buildSeat() {
mBike.setSeat("橡胶车座");
}
@Override
public Bike createBike() {
return mBike;
}
}
//指挥者类
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
//测试类
public class Client {
public static void main(String[] args) {
showBike(new OfoBuilder());
showBike(new MobikeBuilder());
}
private static void showBike(Builder builder) {
Director director = new Director(builder);
Bike bike = director.construct();
System.out.println(bike.getFrame());
System.out.println(bike.getSeat());
}
}
但是,有个问题,就是这个类的构造方式是不是太过于复杂了?于是我们还可以进行简化。
我们知道,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下可以简化系统结构,可以把指挥者类和抽象建造者进行结合。
// 抽象 builder 类
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
public Bike construct() { //将buildFrame 与buildSeat 封装到construct方法里
this.buildFrame();
this.BuildSeat();
return this.createBike();
}
}
这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果
construct()
过于复杂,建议还是封装到Director
中。
另一个应用场景举例(类似Lombok包的@Builder功能):
就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}
意图:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适用性:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
工厂模式和建造者模式比较?
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
4. Prototype(原型)
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
UML类图如下:
原型模式主要包含三个角色:
抽象原型(Prototype):规定拷贝接口。
public interface IPrototype<T> {
T clone();
}
具体原型(Concrete Prototype):被拷贝的对象。
public class ConcretePrototype implements IPrototype {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ConcretePrototype clone() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
return concretePrototype;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
客户(Client):客户类提出创建对象的请求。
public class Client {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
System.out.println(prototype);
//拷贝原型对象
ConcretePrototype cloneType = prototype.clone();
System.out.println(cloneType);
}
}
意图:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用性:
当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者为了避免创建一个与产品类层次平行的工厂类层次时;或者当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
代码示例
利用java应用原型模式方法:实现Cloneable接口。如创建一个娱乐应用,它需要频繁创建Movie,Album和Show类实例。我会首先创建他们的原型类实例,当每次需要创建对象实例时,通过原型clone实现。
抽象原型角色:
public interface PrototypeCapable extends Cloneable {
PrototypeCapable clone() throws CloneNotSupportedException;
}
具体原型角色:
public class Movie implements PrototypeCapable {
private String name = "钢铁侠";
@Override
public Movie clone() throws CloneNotSupportedException {
System.out.println("拷贝Movie对象");
return (Movie)super.clone();
}
@Override
public String toString() {
return "Movie{name='" + name + "'}";
}
}
public class Album implements PrototypeCapable {
private String name = "无法长大";
@Override
public Album clone() throws CloneNotSupportedException {
System.out.println("拷贝Album对象");
return (Album)super.clone();
}
@Override
public String toString() {
return "Album{name='" + name + "'}";
}
}
public class Show implements PrototypeCapable {
private String name = "维多利亚的秘密";
@Override
public Show clone() throws CloneNotSupportedException {
System.out.println("拷贝Show对象实例");
return (Show)super.clone();
}
@Override
public String toString() {
return "Show{name='" + name + "'}";
}
}
客户端角色:
public class PrototypePatternTest{
public static void main(String[] args) throws CloneNotSupportedException {
Movie moviePrototype = new Movie();
Movie movie = moviePrototype.clone();
System.out.println(movie);
Album albumPrototype = new Album();
Album album = albumPrototype.clone();
System.out.println(album);
Show showPrototype = new Show();
Show show = showPrototype.clone();
System.out.println(show);
}
}
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式本质是一种克隆对象的方法,其核心是重写Object中的clone方法,调用该方法可以在内存中进行对象拷贝。
Java提供了一个标记接口——Cloneable,实现该接口完成标记,在JVM中具有这个标记的对象才有可能被拷贝。如果不实现该接口,克隆对象会抛出CloneNotSupportedException异常。
使用原型模式创建对象对直接new一个对象在性能上要好得多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别比较明显。
原型模式总结:原型模式是通过拷贝原型对象实例来创建新的对象实例。
5. Singleton(单例)
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
示例代码:
public class LazySingletonDoubleCheck {
private volatile static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck() {}
public static LazySingletonDoubleCheck getInstance() {
if (lazySingletonDoubleCheck == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (lazySingletonDoubleCheck == null) {
lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
结构型
6. Adapter (适配器模式)
适配器模式(Adapter Pattern)又叫做变压器模式,它的功能是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而导致无法在一起工作的两个类能够一起工作,属于结构型设计模式。
UML类图如下:
适配器模式一般包含三种角色:
目标角色(Target):也就是我们期望的接口;
源角色(Adaptee):存在于系统中,内容满足客户需求(需转换),但接口不匹配的接口实例;
适配器(Adapter):将源角色(Adaptee)转化为目标角色(Target)的类实例;
适配器模式各角色之间的关系如下:
假设当前系统中,客户端需要访问的是Target接口,但Target接口没有一个实例符合需求,而Adaptee实例符合需求;但是客户端无法直接使用Adpatee(接口不兼容);因此,我们需要一个适配器(Adapter)来进行中转,让Adaptee能转换为Target接口形式;
适配器模式有3种形式:类适配器、对象适配器、接口适配器。
类适配器
类适配器的原理就是通过继承来实现适配器功能。具体做法是让Adapter实现Target接口并且继承Adaptee,这样Adapter就具备Target和Adaptee,这样Adapter就具备Target和Adaptee的特性,就可以将两者进行转化。
下面我们以一个示例进行讲解,来看下该示例分别用类适配器,对象适配器和接口适配器是怎样进行代码实现。
在中国民用带你都是220V交流电,但我们手机使用的锂电池使用的5V直流电。因此,我们给手机充电时就需要使用电源适配器来进行转换。下面我们有代码来还愿这个生活场景。创建Adaptee角色,需要被转换的对象AC220类,表示220V交流电:
public class AC220{
public int outputAC220V() {
int output = 220;
System.out.println("输出电压" + output + "V");
return output;
}
}
创建Target角色DC5接口,表示5V直流电的标准:
public interface DC5 {
int output5V();
}
创建Adapter角色电源适配器PowerAdapter类:
public class PowerAdapter extends AC220 implements DC5 {
public int output5V() {
int adapterInput = super.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
}
客户端测试代码:
public class Test {
public static void main(String[] args) {
DC5 adapter = new PowerAdapter();
adapter.output5V();
}
}
上面的案例中,通过增加PowerAdapter电源适配器,实现了二者的兼容。
对象适配器
对象适配器的原理就是通过组合来实现适配器功能。具体做法:让Adapter实现Target接口,然后内部持有Adaptee实例,然后再Target接口规定的方法内转换Adaptee。
代码只需要更改适配器(Adapter)实例,其它与类适配器一致:
public class PowerAdapter implements DC5 {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
public int output5V() {
int adapterInput = ac220.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
}
接口适配器
接口适配器的关注点与类适配器和对象适配器的关注点不太一样,类适配器和对象适配器着重将系统存在的一个角色(Adaptee)转化成目标接口(Target)所需内容,而接口适配器的使用场景是解决接口方法过多,如果直接实现接口,那么类会多处许多空实现的方法。类显得臃肿。此时,使用接口适配器就能让我们只实现我们需要的接口方法,目标更清晰。
接口适配器的主要原理就是利用抽象类实现接口,并且空实现接口众多方法。下面我们来接口适配器的源码实现,首先Target角色的DC类:
public interface DC {
int output5V();
int output12V();
int output24V();
int output36V();
}
创建Adaptee角色AC220类:
public class AC220 {
public int outputAC220V(){
int output = 220;
System.out.println("输出电压" + 220 + "V");
return output;
}
}
创建Adapter角色PowerAdapter类:
public class PowerAdapter implements DC {
private AC220 ac220;
public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
}
public int output5V() {
int adapterInput = ac220.outputAC220V();
int adapterOutput = adapterInput / 44;
System.out.println("使用Adapter输入AC" + adapterInput + "V,输出DC" + adapterOutput + "V");
return adapterOutput;
}
public int output12V() {
return 0;
}
public int output24V() {
return 0;
}
public int output36V() {
return 0;
}
}
客户端代码:
public class Test {
public static void main(String[] args) {
DC adapter = new PowerAdapter(new AC220());
adapter.output5V();
}
}
适配器模式在源码中的体现
Spring中适配器模式也应用的非常广泛,例如:SpringAOP中的AdvisorAdapter类,它有三个实现类MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter和ThrowsAdviceAdapter。
先来看顶层AdvisorAdapter的源代码:
再看MethodBeforeAdviceAdapter类:
其它两个类这里就不把代码贴出来了。Spring会根据不同的AOP配置来确定使用对应的Advice,跟策略模式不同的是,一个方法可以同时拥有多个Advice。
下面再来看一个SpringMVC中的HandlerAdapter类,它也有多个子类,类图如下:
其适配调用的关键代码还是在DispatcherServlet的doDispatch()方法中,下面我们还是来看源代码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
在doDispatch()方法中调用了getHandlerAdapter()方法,来看代码:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
在getHandlerAdapter()方法中循环调用了supports()方法判断是否兼容,循环迭代集合中的Adapter又是在初始化时早已赋值。
意图:
将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用性:
你想使用一个已经存在的类,而它的接口不符合你的需求。
你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
(仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
适配器模式的优缺点
优点:
1、能提高类的透明性和复用,现有的类复用但不需要改变。
2、目标类和适配器类解耦,提高程序的扩展性。
3、在很多业务场景中符合开闭原则。
缺点:
1、适配器编写过程需要全面考虑,可能会谧加系统的复杂性。
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
7. Bridge(桥接)
桥接模式(Bridge模式),是一种结构型设计模式,将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责,它的主要特点是把抽象(Abstraction)与行为实现(Implementaion)分离开来,从而可以保持各部分的独立性以及对应他们的功能扩展。
UML类图如下:
UML类图说明:
1)Client类:桥接模式的调用者
2)抽象类(Abstraction):维护了Implementor,即它的实现类,ConcreteImplementorA..二者是聚合关系, Abstraction充当桥接类
3)RefinedAbstraction:是Abstraction抽象类的子类
4)Implementor:行为实现类的接口
5) ConcreteImplementorA/B:行为的具体实现类
6)从UML图:这里的抽象类和接口是聚合的关系,也是调用与被调用关系
示例举例如下:
Phone.java
public abstract class Phone {
//组合品牌
private Brand brand;
//构造器
public Phone(Brand brand){
super();
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
brand.close();
}
protected void call(){
brand.call();
}
}
FolderPhone.java
public class FoldedPhone extends Phone{
//构造器
public FoldedPhone(Brand brand){
super(brand);
}
public void open(){
super.open();
System.out.println(" 折叠样式手机 ");
}
public void close(){
super.close();
System.out.println(" 折叠样式手机 ");
}
public void call(){
super.call();
System.out.println(" 折叠样式手机 ");
}
}
Brand接口
public interface Brand {
void open();
void close();
void call();
}
Vivo.java (implements Brand)
public class Vivo implements Brand{
@Override
public void open() {
System.out.println("Vivo手机开机");
}
@Override
public void close() {
System.out.println("Vivo手机关机");
}
@Override
public void call() {
System.out.println("Vivo手机打电话");
}
}
XiaoMi.java (implements Brand)
public class XiaoMi implements Brand{
@Override
public void open() {
System.out.println("小米手机开机");
}
@Override
public void close() {
System.out.println("小米手机关机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
客户端调用Main.java
public class Main {
public static void main(String[] args) {
System.out.println("===============折叠式小米手机=================");
Phone phone = new FoldedPhone(new XiaoMi());
phone.open();
phone.call();
phone.close();
System.out.println("===============折叠式Vivo手机=================");
phone = new FoldedPhone(new Vivo());
phone.open();
phone.call();
phone.close();
System.out.println("===============直立式Vivo手机=================");
phone = new UpRightPhone(new Vivo());
phone.open();
phone.call();
phone.close();
}
}
当需要扩展一个新的样式的时候,只需要在实现一个类,继承Phone即可
public class UpRightPhone extends Phone{
//构造器
public UpRightPhone(Brand brand){
super(brand);
}
public void open(){
super.open();
System.out.println(" 直立样式手机 ");
}
public void close(){
super.close();
System.out.println(" 直立样式手机 ");
}
public void call(){
super.call();
System.out.println(" 直立样式手机 ");
}
}
解释:FolderPhone里调用的open()方法其实调用的是Phone里的open()方法,Phone里的open()方法其实调用的是Brand里的open()方法,但是Brand()里的open()方法调用的又是Vivo或者是XiaoMi里面的方法。
桥接模式在JDBC的应用源码剖析:
1)Jdbc的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类
2) MySQL有自己的ConnectionImpl类,同样Oracle也有对应的实现类
3) Driver和Connection之间是通过DriverManager类进行桥连接的
以上代码解释:所以调用getConnection方法,最终其实是获取的不同数据库类型的Driver中的Connection的实现类。
意图:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
适用性:
你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
8. Composite(组合)
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。
UML类图如下:
结构说明:
组合模式有透明式组合模式和安全式组合模式两种形式。
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法
示例代码:
public abstract class Component{
//这个是容器类的抽象类,定义好行为,定义创建移除子容器的方法抽象的。
public abstract void addComposite(Component c); //添加成员
public abstract void removeComposite(Component c);//移除成员
public abstract Component getComposite(int i);//获取子容器
public abstract void operation();//业务方法
}
public class Leaf extends Component{
//首先重写component的方法
//叶子节点关键点在于业务
public void operation(){
System.out.print("业务方法");
}
public void addComponent(Component c){
//提示报错呗 。
System.out.print("不是子容器");
}
public void removeComponent(Component c){
//提示报错呗 。
System.out.print("不是子容器");
}
public Component addComponent(int c){
//提示报错呗 。
System.out.print("不是子容器");
return null;
}
}
//子容器类
public class Composite extends Component{
//首先来一个存储的集合
private ArrayList<Component> list = new ArrayList<Component> ;
public void addComponent(Component c){
list.add(c);
}
public void removeComponent(Component c){
list.remove(c);
}
public Component getComponent(int c){
Component c1 =list.get(c);
return c1;
}
public void operation(){
for(Object obj:list){
((Component)obj).operation();
}
}
}
以上是一个最简单的实现。现在来一个正式例子,杀毒软件,该软件能够对某个文件夹杀毒,也可以指定对某些文件杀毒。
AbstractFile: 抽象文件类,充当抽象构建。
public abstract class AbstractFiles {
public abstract void add(AbstractFiles af);
public abstract void remove(AbstractFiles af);
public abstract AbstractFiles get(int i);
public abstract void killVirus();
}
叶子节点:文件类型,这里就写了一种。
public class ImageFile extends AbstractFiles {
private String name;
public ImageFile(String name) {
this.name=name;
}
@Override
public void add(AbstractFiles af) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
}
@Override
public void remove(AbstractFiles af) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
}
@Override
public AbstractFiles get(int i) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
return null;
}
@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.println("开始进行--"+name+"--文件杀毒");
}
}
子容器 构件部分文件类型:
public class Folder extends AbstractFiles {
//文件夹类,所有的都可以用
private ArrayList<AbstractFiles> list = new ArrayList<AbstractFiles>();
private String name;
public Folder(String name) {
this.name=name;
}
@Override
public void add(AbstractFiles af) {
list.add(af);
System.out.println("添加成功");
}
@Override
public void remove(AbstractFiles af) {
// TODO Auto-generated method stub
if(list.remove(af)) {
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
}
@Override
public AbstractFiles get(int i) {
// TODO Auto-generated method stub
return list.get(i);
}
@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.println("对文件夹"+name+"进行杀毒");
for(Object o:list) { //本模式重点在于 递归调用"本操作方法"
((AbstractFiles)o).killVirus();
}
}
}
测试代码:
public static void main(String[] args) {
//创建一个文件类型
AbstractFiles f1 = new Folder("主文件夹");
//创建文件
AbstractFiles file1= new ImageFile("孙悟空.png");
AbstractFiles file2= new ImageFile("龙珠.jpg");
AbstractFiles file3= new ImageFile("帅哥威.gif");
f1.add(file1);
f1.add(file2);
f1.add(file3);
f1.killVirus();
file1.killVirus();
}
测试结果:
示例代码二:
public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1); //子容器间传递, 方法演示后续operation方法的递归过程
c1.add(leaf2);
c1.add(leaf3);
c0.operation(); //调用c0.operation时 会发现该方法内会递归调用子类的相同operation方法
}
}
//抽象构件
interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
//树叶构件
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
public void add(Component c) {
}
public void remove(Component c) {
}
public Component getChild(int i) {
return null;
}
public void operation() {
System.out.println("树叶" + name + ":被访问!");
}
}
//树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((Component) obj).operation(); //递归实现子类的本operation方法
}
}
}
组合模式的优缺点
优点:
- 可以清楚地定义分层次的复杂类型,表示对象的全部层次或者部分层次 ,它让客户端忽略了层次的差异,方便对整个层次经行控制。
- 客户端可以一致的使用一个组合模式或对单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端的代码。
- 在组合模式种增加新的容器构件和叶子构件都很方便,无需对现有类库进行任何修改,符合开闭原则。
- 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形机构,但对树形结构的控制却很简单。
缺点:
在增加新的构件时就比较难咯。而且难以限定,有时候希望在一个容器种能有某些特定的对象,例如在某个文件夹只能有image或者gif等。这个就比较难以实现。
适用性:
你想表示对象的部分-整体层次结构。
你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
组合模式总结
- 组合模式用于组合多个对象所构成的树形结构层次。
- 组合模式包含抽象构建,叶子构建,和容器构建三种角色。
- 组合模式的优点是解决客户端不好统一对待两种类型的类,缺点是面对一些特殊要求时不好办!
9. Decorator(装饰)
装饰者模式:在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象上,从而实现动态拓展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
UML类图如下:
角色介绍:
1、Component是基类。通常是一个抽象类或者一个接口,定义了属性或者方法,方法的实现可以由子类实现或者自己实现。通常不会直接使用该类,而是通过继承该类来实现特定的功能,它约束了整个继承树的行为。比如说,如果Component代表人,即使通过装饰也不会使人变成别的动物。
2、ConcreteComponent是Component的子类,实现了相应的方法,它充当了“被装饰者”的角色。
3、Decorator也是Component的子类,它是装饰者共同实现的抽象类(也可以是接口)。比如说,Decorator代表衣服这一类装饰者,那么它的子类应该是T恤、裙子这样的具体的装饰者。
4、ConcreteDecorator是Decorator的子类,是具体的装饰者,由于它同时也是Component的子类,因此它能方便地拓展Component的状态(比如添加新的方法)。
每个装饰者都应该有一个实例变量用以保存某个Component的引用,这也是利用了组合的特性。在持有Component的引用后,由于其自身也是Component的子类,那么,相当于ConcreteDecorator包裹了Component,不但有Component的特性,同时自身也可以有别的特性,也就是所谓的“装饰”或”包装“的真实思路。
示例说明:
为了更加深刻地理解装饰者模式,我们来看一个简单的例子。首先,我们假设现在有这样一个需求:你有一家服装店,卖各式各样的衣服,现在需要用一个系统来记录客户所要购买的衣服的总价,以便方便地结算。那么在这个例子里面,我们可以用装饰者模式,把客户当做被装饰者,衣服是装饰者,这很直观形象吧,接着我们来一步步实现需求。
1.创建Component基类
因为总体对象是人,所以我们可以把人抽象为基类,新建Person.java:
public abstract class Person {
String description = "Unkonwn";
public String getDescription()
{
return description;
}
public abstract double cost(); //子类应该实现的方法
}
2.创建被装饰者——ConcreteComponent
客户分为很多种,有儿童、青少年、成年人等,因此我们可以创建不同的被装饰者,这里我们创建青少年的被装饰者,新建Teenager.java:
public class Teenager extends Person {
public Teenager() {
description = "Shopping List:";
}
@Override
public double cost() {
//什么都没买,不用钱
return 0;
}
}
3.创建Decorator
由于不同的部位有不同的衣物,不能混为一谈,比如说,衣服、帽子、鞋子等,那么这里我们创建的Decorator为衣服和帽子,分别新建ClothingDecorator.java和HatDecorator.java:
public abstract class ClothingDecorator extends Person {
public abstract String getDescription();
}
public abstract class HatDecorator extends Person {
public abstract String getDescription();
}
4.创建ConcreteDecorator
上面既然已经创建了两种Decorator,那么我们基于它们进行拓展,创建出不同的装饰者,对于Clothing,我们新建Shirt.java,对于Hat,我们新建Casquette.java,其实可以根据不同类型的衣物创建更多不同的装饰者,这里只是作为演示而创建了两种。持有抽象”Component基类“对象Person是关键,代码如下所示:
public class Shirt extends ClothingDecorator {
//用实例变量保存Person的引用
Person person;
public Shirt(Person person)
{
this.person = person;
}
@Override
public String getDescription() {
return person.getDescription() + "a shirt ";
}
@Override
public double cost() {
return 100 + person.cost(); //实现了cost()方法,并调用了person的cost()方法,目的是获得所有累加值
}
}
public class Casquette extends HatDecorator {
Person person;
public Casquette(Person person) {
this.person = person;
}
@Override
public String getDescription() {
return person.getDescription() + "a casquette "; //鸭舌帽
}
@Override
public double cost() {
return 75 + person.cost();
}
}
5.最后我们在测试类内测试我们的代码:
public class ShoppingTest {
public static void main(String[] args) {
Person person = new Teenager();
person = new Shirt(person);
person = new Casquette(person);
System.out.println(person.getDescription() + " ¥ " +person.cost());
}
}
先创建一个Teenager对象,接着用Shirt装饰它,就变成了穿着Shirt的Teenager,再用Casquette装饰,就变成了戴着Casquette的穿着Shirt的Teenager。运行结果如下所示:
代码分析:
Teenager、Shirt、Casquette都是继承自Person基类,但是具体实现不同,Teenager是Person的直接子类,表示被装饰者;Casquette、Shirt是装饰者,保存了Person的引用,实现了cost()方法,并且在cost()方法内部,不但实现了自己的逻辑,同时也调用了Person引用的cost()方法,即获取了被装饰者的信息,这是装饰者的一个特点,保存引用的目的就是为了获取被装饰者的状态信息,以便将自身的特性加以组合。
装饰者模式特点:
以上就是装饰者模式的一个小栗子,讲述了装饰者的基本用法。通过上述的例子,我们可以总结一下装饰者模式的特点。
(1)装饰者和被装饰者有相同的接口(或有相同的父类)。
(2)装饰者保存了一个被装饰者的引用。
(3)装饰者接受所有客户端的请求,并且这些请求最终都会返回给被装饰者。
(4)在运行时动态地为对象添加属性,不必改变对象的结构。
使用装饰者模式的最大好处就是其拓展性十分良好,通过使用不同的装饰类来使得对象具有多种多样的属性,灵活性比直接继承好。然而它也有缺点,那就是会出现很多小类,即装饰类,使程序变得复杂。
装饰者模式在Java 中的应用场景,如:
学习了装饰者模式用法、特点以及优缺点后,我们再来看看装饰者模式在实际开发过程的应用。装饰者模式在Java中经常出现的地方就是JavaIO。如提到JavaIO,脑海中就冒出了大量的类:InputStream、FileInputStream、BufferedInputStream……等,真是头都大了,其实,这里面大部分都是装饰类,只要弄清楚这一点就容易理解了。我们来看看JavaIO是怎样使用装饰者模式的。
从字符流来分析,我们知道,有两个基类,分别是InputStream和OutputStream,它们也就是我们上面所述的Component基类。接着,它有如下子类:FileInputStream、StringBufferInputStream等,它们就代表了上面所述的ConcreteComponent,即装饰对象。此外,InputStream还有FilterInputStream这个子类,它就是一个抽象装饰者,即Decorator,那么它的子类:BufferedInputStream、DataInputStream等就是具体的装饰者了。所以,从装饰者模式的角度来看JavaIO,是不是更加容易理解了呢?
装饰者模式适用性:
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
10. Facade(外观/门面模式)
门面模式又称为外观模式,提供统一的接口来访问子系统的一系列接口,属于结构型模式
是一种很常见的设计模式。
UML类图如下:
在这个对象图中,出现了两个角色:
- 门面(Facade)角色 :客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
- 子系统(SubSystem)角色 :可以同时有一个或者多个子系统。每个子系统都不是一个单独的类,而是一个类的集合(如上面的子系统就是由ModuleA、ModuleB、ModuleC三个类组合而成)。每个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
举例说明:
比如:我们经常用的controller,一个客户下单,它去调用订单接口,订单接口里面可能会去调用积分系统,支付系统,优惠券系统等的接口,这样客户端就不必多求次发起请求。案例:一个订单下单,先去判断是否有优惠券,判断是否支付成功,最后加积分。
门面角色:
public class OrderServiceFacade {
private CouponService couponService = new CouponService();
private IntegralService integralService = new IntegralService();
private PaymentService paymentService = new PaymentService();
public String placeOrder(OrderInfo orderInfo){
if(couponService.isHaveCoupon(orderInfo)){
if(paymentService.isSuccessPay(orderInfo)){
integralService.addIntegral(orderInfo);
}else{
return "我太穷了^^";
}
}else{
return "太贵了买不起";
}
return orderInfo.getName()+"就是好";
}
}
子系统角色:CouponService
public class CouponService {
public boolean isHaveCoupon(OrderInfo orderInfo){
System.out.println("商品:"+orderInfo.getName()+",价格:"+orderInfo.getPrice()+",有优惠券30块");
return true;
}
}
子系统角色:PaymentService
public class PaymentService {
public boolean isSuccessPay(OrderInfo orderInfo){
System.out.println("商品:"+orderInfo.getName()+",价格:"+orderInfo.getPrice()+",已成功支付");
return true;
}
}
子系统角色:IntegralService
public class IntegralService {
public void addIntegral(OrderInfo orderInfo){
System.out.println("商品:"+orderInfo.getName()+",价格:"+orderInfo.getPrice()+",支付成功,添加积分99");
}
}
测试类:
public class Test {
public static void main(String[] args) {
OrderServiceFacade orderServiceFacade = new OrderServiceFacade();
OrderInfo orderInfo = new OrderInfo("霸王洗发水",99.9);
orderServiceFacade.placeOrder(orderInfo);
}
}
门面模式在Spring中的应用举例:Spring ApplicationContext
它实现了Factory、ResourceLoader等接口,并通过引用这些接口的实例,对外统一提供:加载配置、解析资源、创建Bean、提供环境、启动流程等功能;客户代码只需要操作context就可以获取spring的提供的功能,而无需关心内部的细节;
意图:
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性:
当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
门面模式的优点:
- 简化接口调用,不必一个接口一个接口的调用
- 符合迪米特法则,最少知道原则,调用接口只提供一个外在接口
- 减少系统依赖
缺点:
- 不符合开闭原则,当我们需要新增一个接口时,需要修改门面角色里面的代码
- 某些情况下可能违背单一职责原则
11. Flyweight(享元)
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于 享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种 对象结构型模式。
角色分析:
- Flyweight: 享元接口,通过这个接口传入外部状态并作用于外部状态;
- ConcreteFlyweight: 具体的享元实现对象,必须是可共享的,需要封装享元对象的内部状态;
- UnsharedConcreteFlyweight: 非共享的享元实现对象,并不是所有的享元对象都可以共享,非共享的享元对象通常是享元对象的组合对象;
- FlyweightFactory: 享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口;
在享元模式中可以共享的相同内容称为 内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为 外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。
享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
意图:
运用共享技术有效地支持大量细粒度的对象。
示例代码:
单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
//抽象享元角色类
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
//具体享元角色类
//具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象
//被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,
//在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 构造函数,内蕴状态作为参数传入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蕴状态作为参数传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
//享元工厂角色类
//享元工厂角色类,必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用一个factory()方法得到享元对象。
//一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。
//当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的
//享元对象。
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 Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
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");
}
}
复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
//抽象享元角色类
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
//具体享元角色类
//具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象
//被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,
//在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 构造函数,内蕴状态作为参数传入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蕴状态作为参数传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
//复合享元角色类
//复合享元对象是由单纯享元对象通过复合而成的,因此它提供了add()这样的聚集管理方法。由于一个复合享元对象具有不同的聚集元素,
//这些聚集元素在复合享元对象被创建之后加入,这本身就意味着复合享元对象的状态是会改变的,因此复合享元对象是不能共享的。
//复合享元角色实现了抽象享元角色所规定的接口,也就是operation()方法,这个方法有一个参数,代表复合享元对象的外蕴状态。
//一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的;
//而一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的,不然就没有使用价值了。
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 增加一个新的单纯享元对象到聚集中
*/
public void add(Character key , Flyweight fly){
files.put(key,fly);
}
/**
* 外蕴状态作为参数传入到方法中
*/
@Override
public void operation(String state) {
Flyweight fly = null;
for(Object o : files.keySet()){
fly = files.get(o);
fly.operation(state);
}
}
}
//享元工厂角色类
//享元工厂角色提供两种不同的方法,一种用于提供单纯享元对象,另一种用于提供复合享元对象。
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 复合享元工厂方法
*/
public Flyweight factory(List<Character> compositeState){
ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
for(Character state : compositeState){
compositeFly.add(state,this.factory(state));
}
return compositeFly;
}
/**
* 单纯享元工厂方法
*/
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 Client {
public static void main(String[] args) {
List<Character> compositeState = new ArrayList<Character>();
compositeState.add('a');
compositeState.add('b');
compositeState.add('c');
compositeState.add('a');
compositeState.add('b');
FlyweightFactory flyFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyFactory.factory(compositeState);
Flyweight compositeFly2 = flyFactory.factory(compositeState);
compositeFly1.operation("Composite Call");
System.out.println("---------------------------------");
System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2));
Character state = 'a';
Flyweight fly1 = flyFactory.factory(state);
Flyweight fly2 = flyFactory.factory(state);
System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2));
}
}
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。其中:
- 内部状态 是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
- 外部状态 是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。
public class Flyweight
{
//内部状态作为成员属性
private String intrinsicState;
public Flyweight(String intrinsicState)
{
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState)
{
......
//比如重置内部属性 this.intrinsicState = extrinsicState;
}
}
12. Proxy(代理)
所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
代理模式的UML图
从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。
为什么要采用这种间接的形式来调用对象呢?
一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式:
静态代理
我们先看针对上面UML实现的例子,再看静态代理的特点。示例代码如下:
Subject接口的实现
public interface Subject {
void visit();
}
实现了Subject接口的两个类,真实类与代理类:
public class RealSubject implements Subject {
private String name = "byhieg";
@Override
public void visit() {
System.out.println(name);
}
}
public class ProxySubject implements Subject{
private Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void visit() {
//其他增强逻辑
subject.visit();
}
}
具体的调用如下:
public class Client {
public static void main(String[] args) {
ProxySubject subject = new ProxySubject(new RealSubject());
subject.visit();
}
}
通过上面的代理代码,我们可以看出代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
动态代理
动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。
jdk动态代理原理
动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy
,通过固定的规则生成。
jdk动态代理其步骤如下:
- 编写一个委托类的接口,即静态代理的(Subject接口)
- 实现一个真正的委托类,即静态代理的(RealSubject类)
- 创建一个动态代理类,实现
InvocationHandler
接口,并重写该invoke
方法 - 在测试类中,生成动态代理的对象。
第一、二步骤,和静态代理一样,不过说了。第三步,代码如下:
public class DynamicProxy implements InvocationHandler {
// 被代理类的实例
private Object object;
// 将被代理者的实例传进动态代理类的构造函数中
public DynamicProxy(Object object) {
this.object = object;
}
/**
* 覆盖InvocationHandler接口中的invoke()方法
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
* 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
* 代码切入的扩展点了。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* before :doSomething(); //其他增强逻辑
*/
Object result = method.invoke(object, args);
/*
* after : doSomething();
*/
return result;
}
}
第四步,创建动态代理的对象。
Subject realSubject = new RealSubject();
InvocationHandler handler = new DynamicProxy(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, handler);
subject.visit();
创建动态代理的对象,需要借助Proxy.newProxyInstance
。该方法的三个参数分别是:
- ClassLoader loader表示当前使用到的appClassloader。
- Class<?>[] interfaces表示目标对象实现的一组接口。
- InvocationHandler h表示当前的InvocationHandler实现实例对象。
从上面的代码可以看出,动态代理对象不需要实现目标对象接口,但是目标对象一定要实现接口,否则不能使用动态代理。
Cglib代理
上面的静态代理和动态代理模式都需要目标对象是一个实现了接口的目标对象,但是有的时候,目标对象可能只是一个单独的对象,并没有实现任何的接口,这个时候我们就可以使用目标对象子类的方式实现代理,这种代理方式就是:Cglib代理
Cglib代理定义,也叫做子类代理,它是在内存中构件一个子类对象,从而实现对目标对象的功能拓展。
- JDK的动态代理有个限制,就是使用动态代理的目标对象必须实现至少一个接口,由此,没有实现接口但是想要使用代理的目标对象,就可以使用Cglib代理。
- Cglib是强大的高性能的代码生成包,它可以在运行期间拓展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,为他们提供方法的
interception
(拦截)。 - Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不鼓励直接只使用ASM,因为它要求你必须对JVM内部结构,包括class文件的格式和指令集都很熟悉。
Cglib子类代理的实现方法
使用步骤:
- 需要引入Cglib的jar文件,在Maven中可以直接在POM.xml中添加下列引用即可。
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
- 引入包后,就可以在内存中动态构建子类。
- 代理的对象不能为final的,否则会报错。
- 目标对象的方法如果为final/static修饰的,那么就不会被拦截,即不会执行目标对象额外的方法。
代码示例
1.目标对象类
public class UserDao {
public void save() {
System.out.println("--------已经保存数据--------");
}
}
2.Cglib代理工厂类
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Cglib子类代理工厂
* 对UserDao对象在内存中动态构建出一个子类对象
*/
public class ProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取目标对象的代理对象
public Object getProxyInstance() {
//1. 实例化工具类
Enhancer en = new Enhancer();
//2. 设置父类对象
en.setSuperclass(this.target.getClass());
//3. 设置回调函数
en.setCallback(this);
//4. 创建子类,也就是代理对象
return en.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Begin Transaction");
//执行目标对象的方法
Object returnValue = method.invoke(target, objects);
System.out.println("End Transaction");
return returnValue;
}
}
3.测试类
public class TestProxyFactory {
public static void main (String[] args) {
//目标对象
UserDao userDao = new UserDao();
//生成代理对象
UserDao userDaoProxy = (UserDao) new ProxyFactory(userDao).getProxyInstance();
//调用对象方法
userDaoProxy.save();
}
}
行为型
13. Interpreter(解释器)
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
解释器模式UML类图
角色分析:
- Context:环境角色(上下文),含有每个解释器所需的一些数据或全局的一些信息。
- AbstractExpression:抽象表达式类,声明了抽象的解释操作,所有解释器类都继承或实现该类。
- TerminalExpression:终结符表达式类,是AbstractExpression的子类,实现了文法中有关终结符相关的解释操作。
- NonTerminalExpression:非终结符表达式,AbstractExpression的子类,该类的功能与终结表达式类相反,文法中所有非终结符由该类进行解释。
- Client:客户端测试类。
简单实现
抽象解释器
public abstract class AbstractExpression {
abstract void interpret(Context context);
}
终结符解释器
public class TerminalExpression extends AbstractExpression {
@Override
void interpret(Context context) {
//do something()
}
}
非终结符解释器
public class NonterminalExpression extends AbstractExpression {
@Override
void interpret(Context context) {
//do something()
}
}
上下文对象
public class Context {
//包含各个解释器所需要的数据、属性或是公共的功能。
}
示例说明:
做了一个正则化的小例子 该标准是 [单个数字-单个小写-单个大写]
抽象解释器
public abstract class AbstractExpression {
public abstract boolean interpret(String info);
}
终结符解释器
public class TerminalExpression extends AbstractExpression{
private Set<String> set =new HashSet<String>();
public TerminalExpression(String[] data)
{
for(int i=0; i<data.length;i++)
set.add(data[i]);
}
@Override
public boolean interpret(String info) {
if(set.contains(info))
{
return true;
}
return false;
}
}
非终结符解释器
public class NonTerminalExpression extends AbstractExpression{
private AbstractExpression address=null;
private AbstractExpression name=null;
private AbstractExpression id=null;
public NonTerminalExpression(AbstractExpression address, AbstractExpression name, AbstractExpression id) {
this.address = address;
this.name = name;
this.id = id;
}
@Override
public boolean interpret(String info) {
String s[]=info.split("-");
return address.interpret(s[0])&&name.interpret(s[1])&&id.interpret(s[2]);
}
}
上下文Context
public class Context {
private String[] shuzis={"1","2","3","4","5","6","7","8","9","0"};
private String[] xiaoxiezimus={"a","b","c","d","e","f","g","h","i","j","k","l"};
private String[] daxiezimus={"A","B","C","D","E","F","G"};
private AbstractExpression infomation;
public Context()
{
AbstractExpression shuzi=new TerminalExpression(shuzis);
AbstractExpression xiaoxiezimu=new TerminalExpression(xiaoxiezimus);
AbstractExpression daxiezimu=new TerminalExpression(daxiezimus);
infomation=new NonTerminalExpression(shuzi,xiaoxiezimu,daxiezimu);
}
public void jieshi(String info)
{
boolean ok=infomation.interpret(info);
if(ok) System.out.println("正确! ["+info+"] 满足 [单个数字-单个小写-单个大写] 的条件");
else System.out.println("错误! ["+info+"] 不满足 [单个数字-单个小写-单个大写] 的条件");
}
}
测试使用
public class InterpreterClient {
public static void main(String[] args) {
Context people=new Context();
people.jieshi("2-a-A");
people.jieshi("11-A-5");
people.jieshi("你-好-吖");
people.jieshi("2aA");
}
}
适用性:
当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。
效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。
14. Template Method(模板方法)
模板方法:一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模板方法定义在抽象类中,并由子类不加以修改地完全继承下来。
模板方法是一个具体方法,它给出了一个顶层逻辑框架,而逻辑的组成步骤在抽象类中可以是具体方法,也可以是抽象方法。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。
定义抽象类并且声明一些抽象基本方法供子类实现不同逻辑,同时在抽象类中定义具体方法把抽象基本方法封装起来,这就是模板方法模式。子类共用具体方法,具体方法用final修饰。防止被子类修改业务流程。
抽象类体现的就是一种模板的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
模板方法中的角色:
- AbstractClass 抽象类,定义一个算法的基本框架,并定义对象的原语操作,具体的子类重定义这些原语操作并实现它们。
- ConcreteClass具体实现类,实现原语操作,并完善算法中特定子类相关的步骤。
说明: 原语操作指抽象方法
代码示例:
定义AbstractClass抽象模板类:
public abstract class AbstractClass {
//基本方法 子类去实现
protected abstract void doSomething();
//基本方法 子类去实现
protected abstract void doAnything();
//模板方法
public final void templateMethod(){
this.doSomething();
if(this.isDoAnything()) {
this.doAnything();
}
System.out.println(this.toString());
}
public boolean isDoAnything(){
return true;
}
}
注意: 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
具体模板实现类:
public class ConcreteClass1 extends AbstractClass{
private boolean isFlag = true;
@Override
protected void doSomething() {
System.out.println("我是1 doSomething");
}
@Override
protected void doAnything() {
System.out.println("我是1 doAnything");
}
@Override
public boolean isDoAnything() {
return this.isFlag;
}
//要不要doAnything,使用时决定
protected void setIsDoAnything(boolean isDo){
this.isFlag = isDo;
}
}
public class ConcreteClass2 extends AbstractClass{
@Override
protected void doSomething() {
System.out.println("我是2 doSomething");
}
@Override
protected void doAnything() {
System.out.println("我是2 doSomething");
}
@Override
public boolean isDoAnything() {
return false;
}
}
测试类:
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
((ConcreteClass1) class1).setIsDoAnything(false);
class1.templateMethod(); //输出this代表的对象
System.out.println(class1.toString());
class2.templateMethod(); //输出this代表的对象
System.out.println(class2.toString());
new Client().test();
}
}
意图:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
适用性:
一次性实现不变的部分,并将可变的行为留给子类来实现。当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
控制子类扩展。模板方法只在特定点调用钩子方法“hook ”操作,这样就只允许在这些点进行扩展。
15. Chain of Responsibility(责任链)
UML类图
参与者
Handler
定义一个处理请求的接口。 (可选)实现后继链。ConcreteHandler
处理它所负责的请求。 可访问它的后继者。 如果可处理该请求,就处理之;否则将该请求转发给它的后继者。Client
向链上的具体处理者(ConcreteHandler
)对象提交请求。
意图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
适用性:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
- 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可处理一个请求的对象集合应被动态指定。
示例代码:
- Handler
public interface RequestHandle { void handleRequest(Request request); }
- ConcreteHandler
public class HRRequestHandle implements RequestHandle { @Override public void handleRequest(Request request) { if (request instanceof DimissionRequest){ System.out.println("想辞职,不可能的!"); } System.out.println("请求完毕"); } }
public class PMRequestHandle implements RequestHandle { RequestHandle requestHandle; public PMRequestHandle(RequestHandle requestHandle){ this.requestHandle = requestHandle; } @Override public void handleRequest(Request request) { if (request instanceof AddMoneyRequest){ System.out.println("想加薪?不可能的!"); }else { requestHandle.handleRequest(request); } } }
public class TLRequestHandle implements RequestHandle{ RequestHandle requestHandle; public TLRequestHandle(RequestHandle requestHandle){ this.requestHandle = requestHandle; } @Override public void handleRequest(Request request) { if (request instanceof LeaveRequest){ System.out.println("要请假?不可能的!"); }else { requestHandle.handleRequest(request); } } }
Client
Testpublic class Test { public static void main(String[] args) { RequestHandle hr = new HRRequestHandle(); RequestHandle pm = new PMRequestHandle(hr); RequestHandle tl = new TLRequestHandle(pm); //team leader处理离职请求 Request request = new Request(); tl.handleRequest(request); System.out.println("==========="); //team leader处理加薪请求 request = new AddMoneyRequest(); tl.handleRequest(request); System.out.println("========"); //项目经理上理辞职请求 request = new DimissionRequest(); pm.handleRequest(request); } }
16. Command(命令)
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,解耦合。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。
角色说明:
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
示例说明:
具体示例如日常生活中,我们出去吃饭都会遇到下面的场景。我们可以将女招待理解成一个请求的发送者,用户通过它来发送一个“点餐”请求,而厨师是“点餐”请求的最终接收者和处理者,在图中,顾客和厨师之间并不存在直接耦合关系,它们通过女招待连接在一起,而不同的订单最终的餐点也不同。
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
类图如下:
public interface Command {
void execute();//只需要定义一个统一的执行方法
}
public class OrderCommand implements Command {
//持有接受者对象
private SeniorChef receiver;
private Order order;
public OrderCommand(SeniorChef receiver, Order order){
this.receiver = receiver;
this.order = order;
}
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Set<String> keys = order.getFoodDic().keySet();
for (String key : keys) {
receiver.makeFood(order.getFoodDic().get(key),key);
}
try {
Thread.sleep(100);//停顿一下 模拟做饭的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order.getDiningTable() + "桌的饭弄好了");
}
}
public class Order {
// 餐桌号码
private int diningTable;
// 用来存储餐名并记录份数
private Map<String, Integer> foodDic = new HashMap<String, Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDic() {
return foodDic;
}
public void setFoodDic(String name, int num) {
foodDic.put(name,num);
}
}
// 资深大厨类 是命令的Receiver
public class SeniorChef {
public void makeFood(int num,String foodName) {
System.out.println(num + "份" + foodName);
}
}
public class Waitor {
private ArrayList<Command> commands;//可以持有很多的命令对象
public Waitor() {
commands = new ArrayList();
}
public void setCommand(Command cmd){
commands.add(cmd);
}
// 发出命令 喊 订单来了,厨师开始执行
public void orderUp() {
System.out.println("美女服务员:叮咚,大厨,新订单来了.......");
for (int i = 0; i < commands.size(); i++) {
Command cmd = commands.get(i);
if (cmd != null) {
cmd.execute();
}
}
}
}
public class Client {
public static void main(String[] args) {
//创建2个order
Order order1 = new Order();
order1.setDiningTable(1);
order1.getFoodDic().put("西红柿鸡蛋面",1);
order1.getFoodDic().put("小杯可乐",2);
Order order2 = new Order();
order2.setDiningTable(3);
order2.getFoodDic().put("尖椒肉丝盖饭",1);
order2.getFoodDic().put("小杯雪碧",1);
//创建接收者
SeniorChef receiver=new SeniorChef();
//将订单和接收者封装成命令对象
OrderCommand cmd1 = new OrderCommand(receiver, order1);
OrderCommand cmd2 = new OrderCommand(receiver, order2);
//创建调用者 waitor
Waitor invoker = new Waitor();
invoker.setCommand(cmd1);
invoker.setCommand(cmd2);
//将订单带到柜台 并向厨师喊 订单来了
invoker.orderUp();
}
}
JDK源码解析命令模式场景
Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法。
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
//调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
意图:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
适用性:
抽象出待执行的动作以参数化某对象,你可用过程语言中的回调(call back)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
17. Iterator(迭代器)
意图:
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
适用性:
访问一个聚合对象的内容而无需暴露它的内部表示。
支持对聚合对象的多种遍历。
为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
具体例子可参考java.util.Collection JDK的集合类、java.lang.Iterable、java.util.Iterator
18. Mediator(中介者)
中介者模式(Mediator Pattern):中介者模式又称调解着模式或调停着模式,属于行为型模式,用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的UML类图
角色说明:
- 抽象中介者(Mediator): 它定义一个接口,该接口用于与各同事对象之间进行通信。
- 具体中介者(Concrete Mediator):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。
- 抽象同事类(Colleague): 它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
- 具体同事类(Concrete Colleague)::它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。
中介者类承担了两方面的职责:
(1) 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。
(2) 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。
举例说明:
以下通过一个简单的问题来描述中介者模式中所涉及的各个角色。古代相互交战的A,B,C三方,想通过一个中介者调停之间的战火。A方委托调停者转达的信息是:“要求B方归还曾经抢夺的100斤土豆,要求C方归还曾经抢夺的20头牛”;B方委托调停者转达的信息是:“要求A方归还曾经抢夺的10只公鸡,要求C方归还曾经抢夺的15匹马”;C方委托调停者转达的信息是:“要求A方归还曾经抢夺的300斤小麦,要求B方归还曾经抢夺的50头驴”。
针对以上问题,使用中介者模式设计若干个类。
1.同事(Colleague)
本问题中,同事接口是Colleague,定义了具体同事,即交战各方需要实现的方法,包括giveMess发送消息,receiveMess接受消息,setName设置名称,getName获取名称等方法,具体代码如下所示:
public interface Colleague {
public void giveMess(String[] mess);
public void receiveMess(String mess);
public void setName(String name);
public String getName();
}
2.具体中介者(ConcreteMediator)
本问题中,只需要一个具体中介者,并不需要一个中介者(Mediator)接口,具体中介者是ConcreteMediator类,在里面包含了所有同事(Colleague)的引用,还包括deliverMess方法,即实现同事之间消息的传递,具体代码如下:
public class ConcreteMediator {
ColleagueA colleagueA;
ColleagueB colleagueB;
ColleagueC colleagueC;
public void registerColleagueA(ColleagueA colleagueA) {
this.colleagueA = colleagueA;
}
public void registerColleagueB(ColleagueB colleagueB) {
this.colleagueB = colleagueB;
}
public void registerColleagueC(ColleagueC colleagueC) {
this.colleagueC = colleagueC;
}
public void deliverMess(Colleague colleague,String[] mess){
if(colleague==colleagueA){
if(mess.length>=2){
colleagueB.receiveMess(colleague.getName()+mess[0]);
colleagueC.receiveMess(colleague.getName()+mess[1]);
}
}else if(colleague==colleagueB){
if(mess.length>=2){
colleagueA.receiveMess(colleague.getName()+mess[0]);
colleagueC.receiveMess(colleague.getName()+mess[1]);
}
}else if(colleague==colleagueC){
if(mess.length>=2){
colleagueA.receiveMess(colleague.getName()+mess[0]);
colleagueB.receiveMess(colleague.getName()+mess[1]);
}
}
}
}
3.具体同事(ConcreteColleague)
对应本问题,有ColleagueA,ColleagueB,ColleagueC三个具体同事,其实例分别表示交战的三方,具体代码如下所示:
ColleagueA.java
public class ColleagueA implements Colleague{
ConcreteMediator mediator;//中介者
String name;
public ColleagueA(ConcreteMediator mediator) {
this.mediator = mediator;
mediator.registerColleagueA(this);
}
@Override
public void giveMess(String[] mess) {
mediator.deliverMess(this,mess);
}
@Override
public void receiveMess(String mess) {
System.out.println(name+"收到的信息:");
System.out.println("\t"+mess);
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public String getName() {
return name;
}
}
ColleagueB.java
public class ColleagueB implements Colleague{
ConcreteMediator mediator;//中介者
String name;
public ColleagueB(ConcreteMediator mediator) {
this.mediator = mediator;
mediator.registerColleagueB(this);
}
@Override
public void giveMess(String[] mess) {
mediator.deliverMess(this,mess);
}
@Override
public void receiveMess(String mess) {
System.out.println(name+"收到的信息:");
System.out.println("\t"+mess);
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public String getName() {
return name;
}
}
ColleagueC.java
public class ColleagueC implements Colleague{
ConcreteMediator mediator;//中介者
String name;
public ColleagueC(ConcreteMediator mediator) {
this.mediator = mediator;
mediator.registerColleagueC(this);
}
@Override
public void giveMess(String[] mess) {
mediator.deliverMess(this,mess);
}
@Override
public void receiveMess(String mess) {
System.out.println(name+"收到的信息:");
System.out.println("\t"+mess);
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public String getName() {
return name;
}
}
4.测试程序
Application.java
public class Application {
public static void main(String[] args) {
ConcreteMediator mediator=new ConcreteMediator();//定义中介者
ColleagueA colleagueA=new ColleagueA(mediator);
ColleagueB colleagueB=new ColleagueB(mediator);
ColleagueC colleagueC=new ColleagueC(mediator);
colleagueA.setName("A国");
colleagueB.setName("B国");
colleagueC.setName("C国");
String[] messA={"要求归还曾经抢夺的100斤土豆","要求归还曾经抢夺的20头牛"};
colleagueA.giveMess(messA);
String[] messB={"要求归还曾经抢夺的10只公鸡","要求归还曾经抢夺的15匹马"};
colleagueB.giveMess(messB);
String[] messC={"要求归还曾经抢夺的300斤小麦","要求归还曾经抢夺的50头驴"};
colleagueC.giveMess(messC);
}
}
适用性:
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
19. Memento(备忘录)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
角色说明:
- Originator,发起人角色,一个普通的业务处理类,可以根据自身创建备忘录对象,根据备忘录数据恢复自身。
- Memento,备忘录角色,负责存储发起人的内部数据,在需要时根据备忘录来恢复发起人。
- Caretaker,备忘录管理者,提供保存和获取备忘录的功能。
代码示例:
/**
* 发起者
*/
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento m) {
this.setState(m.getState());
}
}
/**
* 备忘录
*/
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
/**
* 备忘录管理者
*/
public class Caretaker {
private Memento memento;
public void setMemento(Memento m) {
memento = m;
}
public Memento getMemento() {
return memento;
}
}
客户端测试:
public class Client {
public static void main(String[] args) {
Originator or = new Originator();
Caretaker cr = new Caretaker();
or.setState("S0");
System.out.println("初始状态:" + or.getState());
cr.setMemento(or.createMemento()); //保存状态
or.setState("S1");
System.out.println("新的状态:" + or.getState());
or.restoreMemento(cr.getMemento()); //恢复状态
System.out.println("恢复状态:" + or.getState());
}
}
适用性:
- 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
备忘录模式优点
- 提供一种可以恢复状态的机制,可以很方便的将数据恢复到某个历史的状态。
- 实现了内部状态的封装,除了创建它的发起人,其他对象都不能访问这些状态信息。
备忘录模式缺点
- 如果要保存的内部状态信息过多,将会占用比较大的内存资源。
20. Observer(观察者)
观察者模式(Observer Pattern):定义对象之间的一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式是一种对象行为型模式。
UML类图如下:
角色说明:
- Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。
- ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。
- Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。
- ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。
代码示例:
首先需要一个订阅者接口(观察者),该接口有一个 receive
方法,用于接收公众号推送通知
public interface Subscriber {
int receive(String publisher, String articleName);
}
然后是一个微信客户端(具体观察者),实现了 receive
方法
public class WeChatClient implements Subscriber {
private String username;
public WeChatClient(String username) {
this.username = username;
}
@Override
public int receive(String publisher, String articleName) {
// 接收到推送时的操作
System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, publisher, articleName));
return 0;
}
}
发布者类(目标,被观察对象),该类维护了一个订阅者列表,实现了订阅、取消订阅、通知所有订阅者等功能
public class Publisher {
private List<Subscriber> subscribers;
private boolean pubStatus = false;
public Publisher() {
subscribers = new ArrayList<Subscriber>();
}
protected void subscribe(Subscriber subscriber) {
this.subscribers.add(subscriber);
}
protected void unsubscribe(Subscriber subscriber) {
if (this.subscribers.contains(subscriber)) {
this.subscribers.remove(subscriber);
}
}
protected void notifySubscribers(String publisher, String articleName) {
if (this.pubStatus == false) {
return;
}
for (Subscriber subscriber : this.subscribers) {
subscriber.receive(publisher, articleName);
}
this.clearPubStatus();
}
protected void setPubStatus() {
this.pubStatus = true;
}
protected void clearPubStatus() {
this.pubStatus = false;
}
}
微信公众号类(具体目标),该类提供了 publishArticles
方法,用于发布推送,当文章发布完毕时调用父类的通知所有订阅者方法
public class WeChatAccounts extends Publisher {
private String name;
public WeChatAccounts(String name) {
this.name = name;
}
public void publishArticles(String articleName, String content) {
System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
setPubStatus();
notifySubscribers(this.name, articleName);
}
}
测试方法:
public class Test {
public static void main(String[] args) {
WeChatAccounts accounts = new WeChatAccounts("小旋锋");
WeChatClient user1 = new WeChatClient("张三");
WeChatClient user2 = new WeChatClient("李四");
WeChatClient user3 = new WeChatClient("王五");
accounts.subscribe(user1);
accounts.subscribe(user2);
accounts.subscribe(user3);
accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
accounts.unsubscribe(user1);
accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
}
}
适用性:
- 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
21. State(状态模式)
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
示例说明:
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
public interface State {
public void doAction(Context context);
}
public class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString(){
return "Start State";
}
}
public class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString(){
return "Stop State";
}
}
public class Context {
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}
//使用 Context 来查看当状态 State 改变时的行为变化。
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
适用性:
- 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。
- State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
22. Strategy(策略)
策略模式(Strategy Pattern)定义了一组策略,分别在不同类中封装起来,每种策略都可以根据当前场景相互替换,从而使策略的变化可以独立于操作者。
UML类图如下:
传送门:
设计模式之策略模式(常规版&Lambda Function版)_RichardGeek的博客-CSDN博客_lambda 策略模式
举例如下:
比如我们接受告警消息,告警推送的目标可以任意指定,如钉钉、微信、手机短信等等,使用哪种通知方式,会根据用户所绑定的值来选择不同的推送渠道,这些渠道就是不同的策略。
UML类图如下:
角色说明:
Strategy:策略接口,定义执行策略业务逻辑方法doOperation。
StrategyA、StrategyB、StrategyC:实现Strategy接口,重写doOperation方法,自定义具体的执行逻辑。
Context:环境上下类,包含Strategy属性,负责接收不同的Strategy实现类对象,当客户端调用executeStrategy方法时,会执行当前Strategy实现类对象的doOperation方法,从而实现所谓的策略模式。
利用Spring实现策略模式的方法的实现代码示例:
/**
* 策略接口
*/
public interface MessageStrategy {
/**
* 发送信息
*/
String sendMessage();
/**
* 消息类型
* @return
*/
String getMessageType();
}
定义了消息策略接口MessageStrategy,其中包含了两个方法sendMessage和getMessageType,sendMessage用于实现各个渠道消息的推送逻辑,getMessageType用于获取每个渠道的标识。
/**
* 发送QQ消息实现类
*/
@Component
public class QQMessageStrategy implements MessageStrategy{
@Override
public String sendMessage() {
return "发送QQ消息...";
}
@Override
public String getMessageType() {
return "QQ";
}
}
/**
* 发送短信消息实现类
*/
@Component
public class SmsMessageStrategy implements MessageStrategy{
@Override
public String sendMessage() {
return "发送短信消息...";
}
@Override
public String getMessageType() {
return "SMS";
}
}
/**
* 发送微信消息实现类
*/
@Component
public class WeChatMessageStrategy implements MessageStrategy{
@Override
public String sendMessage() {
return "发送微信消息...";
}
@Override
public String getMessageType() {
return "WeChat";
}
}
/**
* 消息策略环境类(策略Bean的工厂)
*/
@Component
public class MessageContext {
private static final Map<String, MessageStrategy> messageStrategyMap = new HashMap<>(16);
/**
* 对象初始化时,将所有策略实现类塞进Map中
* @param messageStrategyList
*/
public MessageContext(List<MessageStrategy> messageStrategyList) {
messageStrategyList.forEach(strategy -> messageStrategyMap.put(strategy.getMessageType(), strategy));
}
/**
* 根据messageType获取对应的消息策略实现类
* @param messageType
* @return
*/
public MessageStrategy getMessageStrategy(String messageType) {
MessageStrategy messageStrategy = messageStrategyMap.get(messageType);
if (messageStrategy == null) {
throw new RuntimeException("messageType inValid!");
}
return messageStrategy;
}
}
定义了消息策略环境类MessageContext,在初始化MessageContext对象时,将实现MessageStrategy接口的所有类对象存储到Map结构中,Key为消息类型(如QQ、WeChat、SMS),Value为对应的消息实现类。通过getMessageStrategy方法,根据messageType从Map中拿到对应的消息策略,隐藏策略的具体实现逻辑。
测试实现举例:
/**
* 测试接口
*/
@RestController
public class TestController {
@Autowired
private MessageContext messageContext;
@PostMapping("/sendMessage")
public String sendMessage(@RequestParam String messageType) {
MessageStrategy messageStrategy = messageContext.getMessageStrategy(messageType);
return messageStrategy.sendMessage();
}
}
23. Visitor(访问者)
访问者模式(Visitor),表示一个作用于对象结构中的各个元素的操作。它使你可以在不改变元素的前提下定义作用于这些元素的新操作。为数据结构中的每个元素提供多种访问方式,它将对数据的操作与数据结构进行分离,是行为模式中最复杂的一种模式。
UML类图
角色说明:
- 对象结构(ObjectStructure)是一个元素集合,存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次,一个是访问者层次结构,另一个是元素层次结构。
- 访问者层次结构提供了抽象访问者(Visitor)和具体访问者(ConcreteVisitor)。抽象访问者声明了访问元素对象的方法,通常为每一种类型的元素对象都提供一个访问方法,而具体访问者可以实现这些访问方法。这些访问方法的设计又有两种,一种是直接在方法名中标明待访问元素对象的类型,如 visitConcreteElementA(ConcreteElementA elementA),还有一种是统一取名为 visit(),通过参数类型的不同来定义一系列重载方法。
public abstract class Visitor { // 统一取名 public abstract void visit(ConcreteElementA elementA); public abstract void visit(ConcreteElementB elementB); // 如果所有访问者对某一类型的元素访问操作都相同 // 则可以将操作代码移到抽象访问者中 public void visit(ConcreteElementC elementC) { ... } }
- 元素层次结构提供了抽象元素类(Element)和具体元素类(ConcreteElementA),抽象元素类一般都声明一个 accept() 方法,用于接受访问者的访问。该方法传入一个抽象访问者 Visitor 类型的参数,在程序运行时确定其具体访问者的类型,并调用具体访问者对象的 visit() 方法实现对元素对象的操作
public class ConcreteElementA implements Element { public void accept(Visitor visitor) { visitor.visit(this); } public void operationA() { // 在具体元素类中可以定义不同类型的元素所特有的业务方法 } }
具体元素类 ConcreteElementA 的 accept() 方法通过调用 Visitor 类的 visit() 方法实现对元素的访问,并以当前对象作为 visit() 方法的参数,这种调用机制也称“双重分派”。正因为使用了双重分派技术,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象传入具体元素对象的 accept() 方法即可,程序运行时将回调在 Visitor 类中定义的 visit() 方法,从而实现不同形式的访问。
对象结构(ObjectStructure)是一个集合,用于存储元素对象并接受访问者的访问。在对象结构中可以使用迭代器对存储在集合中的元素对象进行遍历,并逐个调用每一个对象的 accept() 方法,实现对元素对象的访问操作。
public class ObjectStructure { private ArrayList list = new ArrayList(); public void accept(Visitor visitor) { Iterator i = list.iterator(); while(i.hashNext()) { ((Element)i.next()).accept(visitor); } } public void addElement(Element element) { list.add(element); } public void removeElement(Element element) { list.remove(element); } }
最终在客户端我们需要实例化一个对象结构对象,并向其添加元素对象,再调用 accept() 方法来接受访问者对象的访问。具体访问者类型可以通过配置文件来确定。
public class Client { public static void main(String[] args) { Element elementA = new ElementA(); Element elementB = new ElementB(); Element elementC = new ElementC(); ObjectStructure objectStructure = new ObjectStructure(); objectStructure.addElement(elementA); objectStructure.addElement(elementB); objectStructure.addElement(elementC); Visitor visitor = new ConcreteVisitorA(); objectStructure.accept(visitor); } }
如果需要修改访问者类型,只或者增加新的类型的访问者,只需修改配置文件即可,符合开闭原则。
但如果要增加新的类型的具体元素类,则访问者类需要为其定义新的访问方法,从这一点看又违背了开闭原则。