结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adapter),它所包装的对象就是适配者 (Adaptee),即被适配的类。适配器提供客户类需要的接口,适配器的实现就是把客户类的请求 转化为对适配者的相应接口的调用。适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
简单的说,需要实现一个接口,这个接口的功能已经有一个类实现,但这个类却不符合接口的规则,因此使用适配器Adapter包装适配者Adaptee(可以有多个Adaptee),调用Adaptee的方法进行二次封装到Adapter的方法中,使用者调用Adapter的方法,并不关心适配器如何实现,反正Adapter对外能够提供功能即可
模式定义 :适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
适配器的核心在于使用新的类实现旧接口,老接口不能轻易动,新的实现类不符合老接口的规则,因此使用适配器实现老接口,调用新类的方法,相当于对各种实现类进行统合,对外能够通过统一的旧接口提供功能。
Outlet提供电压,AC plug插不进去,使用Adapter进行中间转化,使用者可以利用多态使用AC plug返回一个Adapter,Adapter调用Outlet提供电压。
适配器模式有三种实现方式:
类适配器
Adapter继承功能类实现老接口,通过super调用方法
@Configuration
public class SpringConfiguration {
@Bean
public Outlet220V outlet220V(){
return new Outlet220V();
}
@Bean(name="adapterA")
public AdapterA adapterA(){
return new AdapterA();
}
}
public interface Outlet5V {
//提供5V电压
int outlet5V();
}
//新的实现类,能提供220V电压
public class Outlet220V {
public int outlet220V(){
System.out.println("我被适配器调用,我能输出220V电压");
return 220;
}
}
//类适配器
public class AdapterA extends Outlet220V implements Outlet5V {
@Override
public int outlet5V(){
System.out.println("我是类适配器,继承实现类,实现老接口");
return super.outlet220V()/44;
}
}
public class AdapterPatternTest {
public static void main(String[] args){
ApplicationContext context= new AnnotationConfigApplicationContext(SpringConfiguration.class);
Outlet5V outlet5V=(AdapterA)context.getBean("adapterA",AdapterA.class);
outlet5V.outlet5V();
}
}
对象适配器
内部维护实现对象,调用实现方法实现老接口:
public class AdapterB implements Outlet5V{
private Outlet220V outlet220V=new Outlet220V();
public int outlet5V(){
System.out.println("我是对象适配器,内部维护功能对象,调用实现方法实现老接口");
return outlet220V.outlet220V()/44;
}
}
接口适配
介绍完类适配器和对象适配器,我们再来看看接口适配器,接口适配器相对类适配器和对象适配器而言,接口适配器相对更加灵活,就好比手机适配器中的万能适配器,不管接入的是多少伏的电源,最终都能保证输出电源为5V。
首先,定义一个总的抽象类,并且给予一个默认值(或者直接是接口,目的都是为了定义功能),即提供总的抽象
public abstract class AbstractAdapter {
public int output(){
return 220;
}
}
基于该抽象类重写方法,提供不同的功能
public class Outlet110V extends AbstractAdapter {
@Override
public int output(){
return 110;
}
}
public class Outlet440V extends AbstractAdapter {
@Override
public int output(){
return 440;
}
}
适配器内部维护一个总的抽象对象,提供不同的构造器,提供setter,实现老接口时,必须能判断当前维护的抽象对应的具体实现是哪一个,可使用instanceof或者反射
public class AdapterC implements Outlet5V {
private AbstractAdapter abstractAdapter;
public AdapterC(AbstractAdapter abstractAdapter){
this.abstractAdapter=abstractAdapter;
}
//这里是接口适配器,内部维护总的抽象,可以是接口,也可以是抽象类,对外提供方法,因此必须有办法判断
//具体是哪一个实现,可使用instanceof,也可以使用反射读取信息
@Override
public int outlet5V(){
System.out.println("我是接口适配器,内部维护一个总抽象,根据instanceof判断当前是哪一个实现");
if(abstractAdapter instanceof Outlet110V)return abstractAdapter.output()/22;
else if(abstractAdapter instanceof Outlet440V)return abstractAdapter.output()/88;
return 5;
}
}
将抽象部分与它的实现部分分离,使它们都可以独立地变化。将抽象化(Abstraction)与实现化 (Implementation)脱耦,使得二者可以独立地变化。
假设有两个接口A与B,A有6个具体实现,B有4个具体实现,现在必须组合A,B以提供组合式的更强的功能,共有6*4=24个具体提供,为实现解耦,现在将某一个接口抽象化,将A变为抽象类,内部维护一个B并提供setter,A的具体实现都要继承A,因为A内部维护一个B并提供setter,因此A不关心具体的B是哪一个,调用功能即可,只需6+4=10个具体提供。
模式结构 :
• Abstraction:抽象类,内部维护一个Implementor并提供setter
• RefinedAbstraction:扩充抽象类
• Implementor:实现类接口
• ConcreteImplementor:具体实现类
抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
• 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
• 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增 加的系统,桥接模式尤为适用。
//实现接口与具体实现
public interface VideoStream {
public void videoStream();
}
public class AVIVideoStream implements VideoStream {
@Override
public void videoStream(){
System.out.println("提供AVI视频流");
}
}
public class MP4VideoStream implements VideoStream {
@Override
public void videoStream(){
System.out.println("提供mp4视频流");
}
}
//抽象类与扩充实现
public abstract class VideoPlay {
private VideoStream videoStream;
public VideoPlay(VideoStream stream){
this.videoStream=stream;
}
public VideoStream getVideoStream(){
return videoStream;
}
public void play(){
}
}
public class Aplay extends VideoPlay {
public Aplay(VideoStream stream){
super(stream);
}
@Override
public void play(){
System.out.println("获取视频流");
super.getVideoStream().videoStream();
System.out.println("A play");
}
}
public class Bplay extends VideoPlay {
public Bplay(VideoStream videoStream){
super(videoStream);
}
@Override
public void play(){
System.out.println("获取视频流");
super.getVideoStream().videoStream();
System.out.println("B play");
}
}
public class BridagePatternTest {
public static void main(String[] args){
VideoPlay play=new Aplay(new AVIVideoStream());
play.play();
play=new Aplay(new MP4VideoStream());
play.play();
play=new Bplay(new AVIVideoStream());
play.play();
play=new Bplay(new MP4VideoStream());
play.play();
}
}
/*
获取视频流
提供AVI视频流
A play
获取视频流
提供mp4视频流
A play
获取视频流
提供AVI视频流
B play
获取视频流
提供mp4视频流
B play
*/
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象,组合多个对象形成树形结构以表示“整体-部分”的结构层次。
模式结构:
• Component: 抽象构件
• Leaf: 叶子构件
• Composite: 容器构件
• Client: 客户类
也就是整体与部分的模式,极其类似于文件与文件夹,文件夹里面可以有子文件夹,也可以有文件,对于这样的抽象树形结构,定义一个抽象构件Component,客户端调用抽象构件,这个抽象构件可以是文件也可以是文件夹,如果是文件(Leaf)直接执行操作,如果是文件夹(Composite)则调用内部维护的抽象构件
Component列表递归遍历。
即定义一个公共抽象类,该公共抽象的子类可以是具体实现,也可以是抽象容器,是容器的话内部就需要维护抽象类的集合列表以供遍历
//公共抽象Component
public abstract class File {
public abstract void read();
public abstract void write();
public abstract void add(File file);
public abstract void remove(File file);
}
//具体实现
public class TextFile extends File {
@Override
public void read(){
System.out.println("读文本文件");
}
@Override
public void write(){
System.out.println("写文本文件");
}
@Override
public void add(File file){
}
@Override
public void remove(File file){
}
}
public class VideoFile extends File{
@Override
public void read(){
System.out.println("读视频文件");
}
@Override
public void write(){
System.out.println("写视频文件");
}
@Override
public void add(File file){
}
@Override
public void remove(File file){
}
}
//抽象容器,可继续递归
public class Folder extends File {
//维护文件列表
private List<File> fileList=new ArrayList<>();
@Override
public void read(){
}
@Override
public void write(){
}
@Override
public void add(File file){
fileList.add(file);
}
@Override
public void remove(File file){
fileList.remove(file);
}
public List<File> getFileList(){
return fileList;
}
}
public class CompositePatternTest {
public static void main(String[] args){
Folder folder=new Folder();
Folder subfolder=new Folder();
subfolder.add(new TextFile());
folder.add(new VideoFile());
folder.add(subfolder);
dfs(folder);
}
public static void dfs(File file){
if(file instanceof Folder){
for(File file1:((Folder) file).getFileList()){
dfs(file1);
}
}
file.read();
}
}
组合模式非常适用于可递归描述的树形数据结构,如目录解析,XML解析
可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。缺点就是变得更加抽象,很难对新增构件的类型进行限制。
一般有两种方式可以实现给一个类或对象增加行为:
• 继承机制,使用继承机制是给现有类添加功能的一种有效途径, 通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机。
• 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。
装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。装饰者内部维护原始对象,通过构造方法或者setter进行注入,对原始对象的方法进行重写改造,提供增强后的功能,即成为包装器Wrapper。客户端通过调用装饰器以获得原始对象增强后的功能,Java.io中的BufferInputStream等缓冲流是典型的装饰器模式。
模式结构:
• Component: 抽象构件
• ConcreteComponent: 具体构件
• Decorator: 抽象装饰类
• ConcreteDecorator: 具体装饰类
在单一职责原则下,一个类不宜实现过多的功能,一个类能提供其基本功能,但在某些条件下又需要实现一些扩展功能,此时便可以使用继承或者装饰器模式,在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 装饰器内部维护原始对象,增加逻辑以增加功能。
public interface Cipher {
String encrypt(String plainText);
}
/**
* 自由实现,只提供简单加密
*/
public class SimpleCipher implements Cipher {
@Override
public String encrypt(String plainText){
StringBuilder res=new StringBuilder();
for(char c:plainText.toCharArray()){
res.append((int) c);
}
return res.toString();
}
}
/**
* 装饰器,增强功能
*/
public class CipherDecorator implements Cipher {
private Cipher cipher;
public CipherDecorator(Cipher cipher){
this.cipher=cipher;
}
public void setCipher(Cipher cipher) {
this.cipher = cipher;
}
@Override
public String encrypt(String plainText){
return cipher.encrypt(plainText);
}
public String complexEncrypt(String plainText){
StringBuilder res=new StringBuilder(encrypt(plainText));
res.reverse();
return res.toString();
}
}
public class DecoratorPatternTest {
public static void main(String[] args){
Cipher cipher=new SimpleCipher();
System.out.println(cipher.encrypt("HelloWorld"));
CipherDecorator cipherDecorator=new CipherDecorator(cipher);
System.out.println(cipherDecorator.complexEncrypt("HelloWorld"));
}
}
/*结果为
7210110810811187111114108100
0018014111117811180180110127
*/
装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
• 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
• 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
• 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
装饰模式的缺点
• 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
• 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。又称为门面模式。
模式结构:
• Facade: 外观角色
• SubSystem:子系统角色
引入一个外观对象,为子系统提供统一入口,即提供一个统一的访问门面,Facade内部维护子对象,通过方法返回系统,客户端不用显式声明,而是通过该门面进行中间调用
public class Fan {
public void on(){
System.out.println("风扇开");
}
public void off(){
System.out.println("风扇关");
}
}
public class Light {
public void on(){
System.out.println("灯开");
}
public void off(){
System.out.println("灯关");
}
}
public class Facade {
private Fan fan=new Fan();
private Light light=new Light();
public Fan getFan() {
return fan;
}
public Light getLight() {
return light;
}
}
public class FacadePatternTest {
public static void main(String[] args){
Facade facade=new Facade();
facade.getFan().on();
facade.getLight().on();
}
}
/*
风扇开
灯开
*/
外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系。
其缺点在于不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。 外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。
即Flyweight Pattern,将具有相同性质的某个成员集合称为共享内容,如字母a-z都是字母,组合起来就是一个具有抽象共同特征的集合,外部使用时需要这些细颗粒度的元素组合成一个新的对象(字符串),为避免系统消耗,创建一个享元工厂,内部维护一个享元池,这个享元池就是细颗粒度的集合,当需要字母时通过工厂获取,工厂先判断池中是否有字母,如果存在直接返回就好,如果不存在则创建并返回。
划分颗粒度,将共享内容维护在一个池,外部使用时先判断池中有没有,有则返回,没有则创建并返回,享元模式的目的就是使用共享技术来实现大量细粒度对象的复用,享元工厂与享元池是其核心。
模式结构:
• Flyweight: 抽象享元类 ,享元对象(细颗粒度)的接口定义
• ConcreteFlyweight: 具体享元类,享元对象(细颗粒度)的具体实现
• UnsharedConcreteFlyweight: 非共享具体享元类 ,不使用享元模式时的实现,多次复用细颗粒度状态
• FlyweightFactory: 享元工厂类,内部维护享元池,维护细颗粒度集合
享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能
/**
* 享元对象的接口定义
*/
public interface NetworkDevice {
String getType();
}
public class Switch implements NetworkDevice{
@Override
public String getType() {
return "Switch";
}
}
public class Hub implements NetworkDevice {
private String type="Hub";
@Override
public String getType() {
return type;
}
}
public class DeviceFactory {
private static List<NetworkDevice> deviceList=new ArrayList<>();
public static NetworkDevice getNetworkDevice(String type){
for(NetworkDevice networkDevice:deviceList){
if(networkDevice.getType().equals(type)){
return networkDevice;
}
}
if(type.equals("Switch"))deviceList.add(new Switch());
else deviceList.add(new Hub());
return deviceList.get(deviceList.size()-1);
}
}
public class FlyweightPattern {
public static void main(String[] args){
System.out.println(DeviceFactory.getNetworkDevice("Hub").getType());
System.out.println(DeviceFactory.getNetworkDevice("Switch").getType());
}
}
/*
Hub
Switch
*/
享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
享元模式适用情况包括:一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费; 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中(也就是将大对象进行细粒度分解,使用对象时要按需注入数据);多次重复使用享元对象。
在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机,即给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
代理模式包含如下角色:
• Subject: 抽象主题角色,可以是抽象类也可以是接口,定义抽象功能
• Proxy: 代理主题角色
• RealSubject: 真实主题角色
静态代理的一般模式为
静态代理就是通过构造一个代理类,内部维护一个代理对象,为该对象的方法提供增强过后的功能,或根据需要设置功能的访问权,静态代理只能是一个具体的类有一个代理角色,功能不强。
public class TestDemo {
interface IService{
void say();
}
static class A implements IService{
public void say(){
System.out.println("I am A");
}
}
static class Proxy implements IService{
private A a;
public Proxy(A a){
this.a=a;
}
public void say(){
System.out.println("我是代理,我先加点东西");
a.say();
System.out.println("我是代理,方法调用结束后加点东西");
}
}
public static void main(String[] args){
System.out.println("正常实现A");
A a=new A();
a.say();
System.out.println("代理强化A");
Proxy aProxy=new Proxy(a);
aProxy.say();
}
}
/*
执行结果为:
正常实现A
I am A
代理强化A
我是代理,我先加点东西
I am A
我是代理,方法调用结束后加点东西
*/
代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度,保护代理可以控制对真实对象的使用权限,代理模式应用的主要类型有:
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。
• 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一 个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
• Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象 被用到的时候才被克隆。
• 保护(Protect or Access)代理:控制对一个对象的访问,可以给 不同的用户提供不同级别的使用权限。
• 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空 间,以便多个客户端可以共享这些结果。
• 防火墙(Firewall)代理:保护目标不让恶意用户接近。
• 同步化(Synchronization)代理:使几个用户能够同时使用一个对 象而没有冲突。
• 智能引用(Smart Reference)代理:当一个对象被引用时,提供 一些额外的操作,如将此对象被调用的次数记录下来等。
动态代理:
说实话,静态代理的功能一点也不强,如果A,B,C...有许多类都需要在方法前加一点东西,不可能为每一个类都写一个对应的代理进行强化,如果能动态生成就好了。有需求必然有变化,java为应对需求推出了类java.lang.reflect.Proxy同时对动态代理设置了一定的规范。
首先,将需要切入的方法抽取出来放在接口中,A照样实现接口从而实现方法。
然后,写一个通用代理类,代理类Proxy内部不在维护A,而是维护一个Object,该代理类要实现通用的代理接口InvocationHandler,该接口只有一个方法invoke()。Proxy内部维护Object,这个Object就可以代表任何上述接口,想要执行接口方法JVM就会自动调用内部invoke()方法。
invoke有三个参数:Object proxy,JVM自动生成的对应接口对象,一般不用管它、Method method:要切入的方法、Object args:切入的方法的参数
invoke()返回一个Object,在invoke()内部要使用method.invoke(obj,args)调用内部维护的Object的原始方法,然后返回
最后想用代理时创建代理即可,要创建代理,简单,调用(XXX)Proxy.newProxyInstance()即可,直接创建代理并进行强制类型转换
该方法也有三个参数:ClassLoader loader:任意上述接口对象的类加载器、Class>[] interfaces:任意上述接口对象的数组、InvocationHandler h:代理
简而言之,动态代理就是在运行时动态生成指定接口的代理用于强化接口方法,就是动态生成静态代理,在内部使用时,每次通过Proxy.newProxyInstance()指定接口与通用代理类,通用代理类的构造器传入具体的接口实现类作为参数。JVM内部自动生成一个与指定接口绑定的代理类,名为ProxyX(X是自增数字),该代理类被传入通用代理类的invoke方法中,同时将绑定到的实现接口的具体类的方法method与方法参数args一并传入invoke,然后执行invoke()中的代码,要调用原方法,须使用method.invoke(obj,args),返回一个Object作为结果,返回该结果即可
public class TestDemo {
interface IA{
void sayA();
}
interface IB{
void sayB();
}
static class A implements IA{
public void sayA(){
System.out.println("I am A");
}
}
static class B implements IB{
public void sayB(){
System.out.println("I am B");
}
}
static class MyProxy implements InvocationHandler {
private Object obj;
public MyProxy(Object obj){
this.obj=obj;
}
public Object invoke(Object proxy, Method method,Object[] args){
System.out.println("我是自动创建的代理"+proxy.getClass().getName());
System.out.println("我是代理,我需要处理方法"+method.getName());
if(args!=null)System.out.println("我是代理,该方法有参数"+args.length+"个");
try{
Object result=method.invoke(obj,args);//执行原始object方法
return result;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args){
IA a=(IA) Proxy.newProxyInstance(IA.class.getClassLoader(),new Class<?>[]{IA.class},new MyProxy(new A()));
a.sayA();
IB b=(IB)Proxy.newProxyInstance(IB.class.getClassLoader(),new Class<?>[]{IB.class},new MyProxy(new B()));
b.sayB();
}
}
结果为:
我是自动创建的代理mapper.$Proxy0
我是代理,我需要处理方法sayA
I am A
我是自动创建的代理mapper.$Proxy1
我是代理,我需要处理方法sayB
I am B