代理模式
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到的角色有:
抽象角色:声明真实对象和代理对象的共同接口;
代理角色:代理对象角色内部含有对真实对象的引用,从而可以 *** 作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象 *** 作时,附加其他的 *** 作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
以下以客户需要向银行存钱为例:
1.静态代理:
代理模式需要一个业务接口,代理类和被代理类都要实现这个接口,代理类将被代理类包含其中并在代理类的相应接口方法前后添加自己的方法
代码:
//抽象角色:
public interface CunQian {
void addMoney();
}
//真实角色:实现了CunQian的addMoney()方法。
public class Customer implements CunQian {
@Override
public void addMoney() {
System.out.println("存500万");
}
}
//代理角色:
public class Worker implements CunQian {
private Customer customer;
@Override
public void addMoney() {
System.out.println("接过客户的钱");
customer.addMoney();
System.out.println("存钱完成");
}
public Worker(Customer customer){
this.customer=customer;
}
}
//客户端调用:
public class Test {
public static void main(String[] args) {
Customer customer=new Customer();
CunQian worker= new Worker(customer);
worker.addMoney();
}
}
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
2.动态代理Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
(1). Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。
在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args代表的时参数
(2).Proxy:该类即为动态代理类,其中主要包含以下内容:
Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。(参见文献3)
在使用动态代理类时,我们必须实现InvocationHandler接口,以第一节中的示例为例:
//抽象角色:
public interface CunQian {
void addMoney();
}
//真实角色:实现了CunQian的addMoney()方法。
public class Customer implements CunQian {
@Override
public void addMoney() {
System.out.println("存500万");
}
}
//代理角色:
public class Worker implements InvocationHandler {
private Object object;
public Worker(Object object){
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("将钱接过来");
method.invoke(object,args);
System.out.println("存钱结束");
return null;
}
该代理类的内部属性为Object类,实际使用时通过该类的构造函数Worker (Object obj)对其赋值;此外,在该类还实现了invoke方法,该方法中的"method.invoke(obj,args)" 其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应 *** 作所需的参数。通过动态代理类,我们可以在调用之前或之后执行一些相关 *** 作。
客户端:
代码:
public static void main(String[] args) {
CunQian customer=new Customer();
Class c=customer.getClass();
Worker w=new Worker(customer);
CunQian o = (CunQian) Proxy.newProxyInstance(c.getClassLoader(),
c.getInterfaces(), w);
o.addMoney();
}
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
Class>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler:指定动态处理器,执行目标对象的方法时,
会触发事件处理器的方法(简单来说就是代理对象)
通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系。
3.CGLIB代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
因为采用的是继承,所以不能对final修饰的类进行代理。
跟jdk动态代理区别,第一点是可以省略目标接口。 第二点代理工厂使用的是
net.sf.cglib.proxy.Enhancer来帮助我们生成代理对象的
代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑
客户类:
package proxy.cglib;
/**
* @author yuyongli
* @Date 2020/11/19
*/
public class Yuyongli {
public void cunqian(){
System.out.println("存5000万");
}
public void quQian(){
System.out.println("取5000万");
}
}
代理类:
package proxy.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author yuyongli
* @Date 2020/11/19
*/
public class BankWorker implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
Object o1 = methodProxy.invokeSuper(o, objects);
return o1;
}
}
调用:
package proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
/**
* @author yuyongli
* @Date 2020/11/19
*/
public class Test {
public static void main(String[] args) {
Enhancer enhancer=new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Yuyongli.class);
//设置代理类
enhancer.setCallback(new BankWorker());
//创建被代理类
Yuyongli o = (Yuyongli) enhancer.create();
o.cunqian();
}
}
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
适配器模式适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁
举个简单的例子,当我们给手机充电时,由于手机充电口是5V,而插座提供的是220V交流电,因此我们通常需要使用充电器将220V交流电转换成可供手机充电用的5V直流电,这个充电器就是一个适配器
意图:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
Ø 类适配:创建新类,继承源类,并实现新接口
class adapter extends oldClass implements newFunc{}
Ø 对象适配:创建新类持源类的实例,并实现新接口,例如
class adapter implements newFunc { private oldClass oldInstance ;}
Ø 接口适配:创建新的抽象类实现旧接口方法。例如
abstract class adapter implements oldClassFunc { void newFunc();}
类适配示例:
类适配器模式:
一般手机充电器输出的直流电压为5V,我们把交流电220V称为源,希望得到的直流电5V称为目标,而充电器即为适配器。
电源:
public class DianYuan {
public int ac(){
return 220;
}
}
目标:
public interface Dc {
void getDc();
}
适配器:
public class ChongDianQi extends DianYuan implements Dc {
@Override
public void getDc() {
int ac = ac();
System.out.println("这是手机所需要的直流电"+ac/44);
}
}
测试:
public static void main(String[] args) {
ChongDianQi chongDianQi=new ChongDianQi();
chongDianQi.getDc();
}
可以看到,类适配器是通过继承源类,实现目标接口的方式实现适配的。但是,由于Java单继承的机制,这就要求目标必须是接口,有一定的局限性。
对象的适配器模式对象适配器,不是继承源类,而是依据关联关系,持有源类的对象,这也隐藏了源类的方法。在这里,适配器和源类的关系不是继承关系,而是组合关系。
public class DianYuan {
public void ac(){
System.out.println("电源220v");
}
}
目标:
public interface Dc {
void getDc();
//对象的适配器模式
public class ChongDianQi1 implements Dc {
//
private DianYuan dy=new DianYuan();
@Override
public void getDc() {
int ac=dy.ac();
System.out.println("得到的电压"+ac/44);
}
}
接口的适配器模式:
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些;
此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
//目标接口,有多个方法
public interface IDCOutput {
public int output5V();//手机
public int output12V();//电脑 12v
public int output20V();//苹果电脑 20v
}
//中间类,空实现所有方法,这是一个抽象类
public abstract class DefaultAdapter implements IDCOutput {
@Override
public int output5V() {
return 0;
}
@Override
public int output12V() {
return 0;
}
@Override
public int output20V() {
return 0;
}
}
//我的mac电源适配器只需要实现20V的方法即可
public class MacAdatper extends DefaultAdapter {
private DianYuan dy=new DianYuan();
@Override
public int output20V() {
return dy.ac()/11;
}
public static void main(String[] args) {
MacAdatper adatper = new MacAdatper();
System.out.println("mac电脑电压:" + adatper.output20V());
}
}
//输出结果:
//mac电脑电压:20
总结:
- 类适配器模式,继承源类,实现目标接口。
- 对象适配器模式,持有源类的对象,把继承关系改变为组合关系。
- 接口适配器模式,借助中间抽象类空实现目标接口所有方法,适配器选择性重写。
适配器模式的优缺点:
优点:
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、增加了类的透明度。
4、灵活性好。
缺点:
1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
三种适配器模式的应用场景:Ø 类的适配器模式:当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
Ø 对象的适配器模式:当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
Ø 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
策略模式完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。
在软件系统中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。
策略模式:
意图:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
策略模式包含如下角色:
Ø Context: 环境类
Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
Ø Strategy: 抽象策略类
Ø ConcreteStrategy: 具体策略类
示例:我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
StrategyPatternDemo,我们的演示类使用 Context 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。
//抽象策略角色,是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
public interface Strategy {
public int yunsuan(int a,int b);
}
//用于实现抽象策略中的 *** 作,即实现具体的算法
public class StrategyPlus implements Strategy {
@Override
public int yunsuan(int a, int b) {
return a*b;
}
}
//Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
public class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public Context(Strategy strategy){
this.strategy=strategy;
}
public int yunsuan(int a,int b){retur
n strategy.yunsuan(a,b);
}
}
public static void main(String[] args) {
int a=1;
int b=2;
Context context=new Context(new StrategyAdd());
// int yunsuan= context.yunsuan(a,b);
// System.out.println(yunsuan);
//
// context.setStrategy(new StrategyMiuns());
// int yunsuan2=context.yunsuan(a,b);
// System.out.println(yunsuan2);
context.setStrategy(new StrategyPlus());
}
策略模式优缺点:
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
策略模式使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
观察者模式观察者模式——监听者模式
观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知
结构图:
该模式包含四个角色
Ø 抽象被观察者角色:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
Ø 抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
Ø 具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。
Ø 具体观察者角色:实现抽象观察者角色所需要的更新接口,一边使本身的状态与制图的状态相协调
示例:
有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息
1.抽象观察者 定义接口微信公众号更新信息时通知自己
2.具体观察者 微信用户 接收推送的消息
3.抽象被观察者 定义用户关注公众号 取消关注公众号 给用户推送消息
4.具体被观察者者 实现方法
/**
* 观察者
*/
public interface Observe {
void update(String message);
}
/
**
* 观察者
*/
public class WeiXinUser implements Observe {
private String name;
@Override
public void update(String message) {
System.out.println(name+"-"+message);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
被观察者:
**
* 被观察者
*/
public interface Subject {
void attach(Observe observe);
void remove(Observe observe);
void sendMessage(String message);
}
/**
* 被观察者
*/
public class WeiXinGongzhongHao implements Subject {
private List userList=new ArrayList<>();
@Override
public void attach(Observe observe) {
userList.add(observe);
}
@Override
public void remove(Observe observe) {
userList.remove(observe);
}
@Override
public void sendMessage(String message) {
//给所有的关注者都发送消息
for (Observe observe : userList) {
observe.update(message);
}
}
}
测试:
public class Test {
public static void main(String[] args) {
//创建用户
WeiXinUser user=new WeiXinUser();
user.setName("张三");
WeiXinUser user1=new WeiXinUser();
user1.setName("李四");
WeiXinGongzhongHao weiXinGongzhongHao=new WeiXinGongzhongHao();
weiXinGongzhongHao.attach(user);
weiXinGongzhongHao.attach(user1);
weiXinGongzhongHao.sendMessage("xxx更新了文章");
}
}
观察者模式优缺点:
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
使用场景:
Ø 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
Ø 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
Ø 一个对象必须通知其他对象,而并不知道这些对象是谁。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)