浅谈设计模式(二)
文章目录
目录
浅谈设计模式(二)
文章目录
一.Decorator装饰器模式
二.ChainOfResponsibility责任链和Observer观察者
三.Composite组合模式和Flyweight享元模式
四.代理模式Proxy
一.Decorator装饰器模式
装饰器模式顾名思义,就是对某个对象或者方法进行装饰。如果使用继承的话,扩展起来非常麻烦。所以引入装饰者模式使用聚合代替继承
示例:需求坦克大战游戏 有坦克,子弹,碉堡,围墙等现在需要对游戏里面的各种物体加装饰,比如外围加方框的子弹,尾巴冒烟的坦克,或者外围带方框的坦克等等
首先我们要定一个抽象的超类GameObject包括所有的游戏物体,所有的物体都有一个方法打印
import lombok.Data;
@Data
public abstract class GameObject {
private String name;
public abstract void print();
}
然后定义他的子类坦克,子弹等等,
import lombok.Data;
@Data
public class Tanke extends GameObject{
private int size;
@Override
public void print(){
System.out.println(this.size+"的"+super.getName());
}
public Tanke(String name,int size) {
super.setName(name);
this.size = size;
}
}
接下来要封装一个抽象的装饰器了,装饰器首先要是游戏物体的子类因为他是对游戏物体的装饰,而且要聚合游戏物体
public abstract class Decorator extends GameObject{
protected GameObject gameObject;
public Dacorator(GameObject gameObject) {
this.gameObject = gameObject;
}
}
接下来要有具体实现的装饰器,比如带尾巴的装饰器加方框的装饰器
public class FangDecorator extends Dacorator{
public FangDecorator(GameObject gameObject) {
super(gameObject);
}
@Override
public void print() {
System.out.println("外围加方块");
gameObject.print();
}
}
public class WeibaDecorator extends Dacorator {
public WeibaDecorator(GameObject gameObject) {
super(gameObject);
}
@Override
public void print() {
System.out.println("加尾巴");
gameObject.print();
}
}
最后在客户端调用
public class TestDecorator {
public static void main(String[] args) {
Tanke tanke1=new Tanke("坦克",634);
Dacorator d1=new FangDecorator(tanke1);
Dacorator d2=new WeibaDecorator(d1);
d2.print();
}
}
执行结果
二.ChainOfResponsibility责任链和Observer观察者
之所以把责任链模式和观察者模式放在一起是以为他们两个模式之间存在联系,并且可以放在一起串起来使用。
责任链模式解决的问题是将请求进行一系列的处理,并且能做到在链条的任意一环可以终止处理操作,观察者模式其实就是个事件处理器。我们可以把一个观察者当成责任链上的一环对事件进行监听。这样两种模式就结合到了一起。
我们还是先提出一个需求然后用代码实现帮助大家理解这两种模式。现在有一个小孩儿,我们需要对他进行监听。看他是不是饿了,并且是不是哭了,如果醒了哭了需要爸爸先喂如果喂饱了不哭了事件终止,如果还是继续哭就需要妈妈抱起来哎拍拍。需求明确了我们来用代码进行实现
首先需要抽象出来一个孩子类
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Data
public class Child {
private boolean isCry=true;
private boolean isHungry=false;
}
接下来需要定义一个Event接口用来定义事件发生器
import lombok.Data;
@Data
public abstract class Event<T> {
private T source;
abstract String wakeupEvent();
abstract boolean isBaoleEvent(T t);
}
然后定义一个孩子醒了的事件处理类
import lombok.Data;
@Data
public class ChildWakeUpEvent extends Event<Child>{
// 小孩的位置在哪里哭
private String loc;
private String time;
@Override
public String wakeupEvent() {
if("bed".equals(loc)){
return "抱起来";
}else if("chuangtai".equals(loc)){
return "抱到床上去";
}
return "";
}
@Override
boolean isBaoleEvent(Child child) {
return child.isHungry();
}
public ChildWakeUpEvent(String loc, String time,Child child) {
this.loc = loc;
this.time = time;
super.setSource(child);
}
}
具体实例域有孩子哭的位置如果在床上哭就抱起来,如果在窗台哭就抱到床上。
然后定义一个Observer接口封装观察者
public interface Observer {
void wakeupEventHandle(Event e);
}
需要定义两个观察者实现一个妈妈一个爸爸
public class DadObserver implements Observer {
public static String feed(boolean isHungry){
System.out.println("Dad feeding");
if(isHungry) return "不吃了";
else{
return "继续哭";
}
}
@Override
public void wakeupEventHandle(Event childWakeUpEvent) {
String isHungry="";
Child c=(Child)childWakeUpEvent.getSource();
if ("抱起来".equals(childWakeUpEvent.wakeupEvent()))
isHungry=feed(childWakeUpEvent.isBaoleEvent(c));
if( "不吃了".equals(isHungry))
c.setCry(false);
else if("继续哭".equals(isHungry)){
c.setCry(true);
}
}
}
public class MomObserver implements Observer {
public static void bao(){
System.out.println("妈妈拍拍");
}
public static void pai(){
System.out.println("妈妈抱抱");
}
@Override
public void wakeupEventHandle(Event e) {
if("抱到窗台".equals(e.wakeupEvent())){
bao();
}
if("抱起来".equals(e.wakeupEvent())){
pai();
}
}
}
爸爸的工作就是喂宝宝如果不饿了宝宝就不吃了,如果没喂饱就继续哭就需要妈妈来哄
妈妈的工作也很简单,如果是抱起来的就要拍拍,如果在床上就需要抱抱来哄宝宝开心
最后我们需要定义一个责任链条将爸爸和妈妈两个观察者串起来并定义执行逻辑
interface Filter{
void doFilter(Event event);
}
static class ChildFilerChain implements Filter{
//观察者集合
private List<Observer> observers=new ArrayList<>();
public ChildFilerChain add(Observer o){
observers.add(o);
return this;
}
@Override
public void doFilter(Event event){
for(int i=0;i<observers.size();i++){
Observer observer=observers.get(i);
observer.wakeupEventHandle(event);
Child c=(Child)event.getSource();
if(!c.isCry){
break;
}
}
}
}
将观察者添加到责任链当中去,遍历观察者集合如果不哭了就跳出去不执行了,如果继续哭就接着执行链条上其他的观察者的操作
最后是客户端的调用
Child c=new Child();
//创建一个宝宝在床上哭的事件
ChildWakeUpEvent cwEvent=new ChildWakeUpEvent("bed",new Date().toString(),c);
ChildFilerChain cfFilter=new ChildFilerChain();
DadObserver dadObserver=new DadObserver();
MomObserver momObserver=new MomObserver();
//将爸爸妈妈两个观察者放入链条当中去
cfFilter.add(dadObserver).add(momObserver);
//如果哭了就执行责任链操作
if(c.isCry){
cfFilter.doFilter(cwEvent);
}
很简单的一个例子最后执行的结果是
当
isCry=true;//哭了
isHungry=false//不饿
Dad feeding
当
isCry=true;//哭了
isHungry=true//饿了
Dad feeding
妈妈抱抱
三.Composite组合模式和Flyweight享元模式
这两个模式比较简单,Composite是专门处理树形结构的模式,类似于目录结构。而享元模式,则是将无数小对象放到池中共享,当用到的时候直接取到相应的对象。比如我们常用的word软件当打印一个A B 字母的时候如果一直new会有很多个小对象生成,所以我们先生成放到池中用的时候直接取。享元模式即是共享。还有一个例子是String对象,也是应用到了享元模式。看下面的代码
String a="abc";
String b="abc";
String c=new String("abc");
String d=new String("abc");
System.out.println(a==b);
System.out.println(a==c);
System.out.println(c==d);
System.out.println(c.intern()==d.intern());
第一个a==b输出的是true可以看出两个变量的引用是一致的在内存中的位置是一致的
第二个和第三个输出是false,因为new出来的对象的引用是不一样的,即在内存中新生成了一块地址。但是最后一个调用intern方法以后输出是true说明他们两个对象引用指向的地址还是一个。这就说明了只要是值一致的,最后String对象都会指向池中的同一个对象这就是享元模式。享元模式经常和组合模式结合在一起使用会把经常结合在一起的组合也放到池中去
四.代理模式Proxy
代理模式分为静态代理和动态代理,也是之前最困扰我的一个模式。
代理就是我们要对一个对象进行处理的时候不直接访问这个对象,而是通过代理去做这件事。这样做的好处就是不需要更改原对象的情况下就可以对对象进行处理。代理分为静态代理和动态的代理。
1. 静态代理:就是我们在程序运行之前就定义好了一个代理对象,然后规定好代理需要做的事情。一切都是很明确的我们来举个例子。还是那Moveable接口来举例,定义一个moveable接口
public interface Moveable {
void move();
}
我们有一个move方法
然后定义一个具体的实现类 Tanke,我们move方法在执行的时候睡眠随机的时间来模拟方法执行时间,现在我们需要对Tanke的move进行监控。需求是统计出来方法执行的时间。又
import java.util.Random;
public class Tanke implements Moveable{
@Override
public void move(){
System.out.println("tanke move kakaka");
try{
Thread.sleep(new Random().nextInt(10000));
}catch (Exception e){
e.getStackTrace();
}
}
}
或者是在方法执行前后加上日志输出。这时候我们直接去修改Tanke类明显是不好的。同样使用继承的话,如果做不同的处理会产生很多类。设计原则也是要求尽量不使用继承。所以我们使用聚合代替继承来定义我们的代理类。
class TimeProxy implements Jumpable,Moveable{
private Moveable m;
public TimeProxy(Moveable m) {
this.m = m;
}
@Override
public void move() {
long startTime=System.currentTimeMillis();
m.move();
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
}
}
我们将Moveable 聚合到代理类中这样凡是Moveable的实现类我们都可以进行代理,而且可以将不同的代理进行嵌套(这里其实就是我们前面说的装饰器模式),我们在定义一个日志处理代理
class LogProxy implements Jumpable,Moveable{
private Moveable m;
public LogProxy(Moveable m) {
this.m = m;
}
@Override
public void move() {
System.out.println("move start");
m.move();
System.out.println("move end");
}
}
然后在客户端调用
TimeProxy timeProxy=new TimeProxy(new Tanke()); timeProxy.move(); LogProxy logProxy=new LogProxy(new Tanke()); logProxy.move();
我们也可以将时间代理器嵌套至日志内
LogProxy logProxy1=new LogProxy(timeProxy); logProxy1.move();
2.动态代理
接下来着重介绍一个动态代理模式,也是springAOP中应用到的模式。他与静态代理的区别是,在程序运行的时候才生成代理对象通过反射去执行代理过程。使用JDK内部Proxy类进行代理处理我们将上面的例子改成动态代理的模式
Tanke tanke=new Tanke();
Moveable m=(Moveable)Proxy.newProxyInstance(Tanke.class.getClassLoader(), new Class[]{Moveable.class, Moveable.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime=System.currentTimeMillis();
Object o=method.invoke(tanke,args);
long endTime=System.currentTimeMillis();
System.out.println(endTime-startTime);
return o;
}
});
m.move();
执行的结果跟上面的效果是一样的。只不过我们并没有定义一个具体的代理对象,而是在程序运行的时候动态生成的我们来解释一下这个执行过程,也是JDK动态代理的执行过程
我们先来解释一下参数
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader:是加载类的加载器,我们可以用被代理对象的加载器也可以用被代理对象的父加载器,但是不可以用代理对象的平行加载器。
Class<?>[] interfaces:生成代理对象需要实现的接口,类型是个数组格式可以实现多个接口
InvocationHandler:最后是一个InvocationHandler对象封装的是我们需要对被代理对象作出的操作也是代理需要处理的内容,即我们在静态代理中定义的代理对象。
我们根据JDK源码可以看出来JDK在运行的时候实际是给我们生成了一个Proxy0继承至Proxy对象,由于我们指定了Proxy对象实现的接口,所以在生成的Proxy对象中要实现move方法,而它的move方法是通过它父类中的InvocationHandler h变量(h变量我们在生成Proxy对象的时候指定的)去调用的invoke方法。这也就解释了为什么我们在生成了Moveable m 对象之后调用move方法会执行我们invoke里面的代码。在我们自己定义的invoke方法中有这样一句 Object o=method.invoke(tanke,args); 因为我们拿到了Method对象是move方法,而这句代码我们传入了被代理对象tanke,所以就会执行tanke对象的move方法这样就是整个实现动态代理的过程。可能有人会有疑问在调用invoke方法的时候Object proxy 参数是哪个对象,为什么没有用到。它是我们需要代理的对象么,no,不是的它是JDK动态生成的Proxy对象也就是 Moveable m 这个m,我们这个例子中没有用到这个对象,但是有时候我们在处理的过程中是需要调用Proxy对象其他方法的这样这个参数就需要用到了。Method method, Object[] args 这两个参数就很简单了,是需要执行的方法和执行方法所需要的参数。
而JDK生成Porxy对象的过程,当我们通过追溯源码可以发现是通过asm类库生成的对象。有兴趣的可以研究一下asm类库才会真正搞明白动态代理。java之所以能被称之为动态语言就是因为asm类库,因为反射只能查询到但是无法操作二进制。只有通过asm类库才能动态的操纵二级制。