结构型设计模式

结构性设计模式

代理模式

AOP(Aspect Oriented Programming)面向切面编程,通过将分散在各处的代码抽取出来,在运行编译的时候,将抽取的代码应用到需要执行的地方。

通常包含以下几个AOP术语:

  • Aspect(切面):要插入到指定系统功能位置的需要实现的类,即将一个类作为切面类,在需要切入的位置执行。
  • Joinpoint(连接点):切入点处的处理
  • Piontcut(切入点):是切面类切入到程序流程的方法,切入点指的是类或方法名。比如要将一个方法应用到所有以某个字符串开头的方法中,那么这个方法便是切点。
  • Advice(增强处理):是切入点所要执行的具体代码
    在Spring AOP中,需要使用代理类对目标类进行处理,有JDK动态代理和CGlib动态代理。
    实现AOP需要的步骤有以下:第一,创建切面类,在其中添加需要的advice处理方法。第二,实现代理类:一般情况下都会使用反射实现动态代理,创建代理类,获取目标类以及实例化切面类和目标类,将切面类的advice增强根据需要调用在目标类的不同位置。第三,便是应用,将创建代理和目标类,将目标类传入代理类的方法中并返回增强后的代理类。
  • JDK和CGlib的异同:
    JDK通过java.lang.reflect.Proxy类实现,调用Proxy类中的newProxyInstance返回代理对象。在

public class JDKProxy implements InvocationHandler {
    /** 需要代理的目标对象 */
    private Object targetObject;
 
    /**
     * 将目标对象传入进行代理
     */
    public Object newProxy(Object targetObject) {
        this.targetObject = targetObject;
        //返回代理对象
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), this);
    }
 
    /**
     * invoke方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 一般我们进行逻辑处理的函数比如这个地方是模拟检查权限
        checkPopedom();
        // 设置方法的返回值
        Object ret = null;
        // 调用invoke方法,ret存储该方法的返回值
        ret  = method.invoke(targetObject, args);
        return ret;
    }
 
    /**
     * 模拟检查权限的例子
     */
    private void checkPopedom() {
        System.out.println("======检查权限checkPopedom()======");
    }
}

Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
Proxy是reflect包下专门用于创建代理类的类,newProxyInstance需要传入三个参数,分别是类加载器,目标对象所有接口的Class和调用程序处理(InvocationHandler)。JDK的代理对象是基于目标对象的所有接口实现的的,所以需要传入所有接口。但是,除了实现还得对每个方法进行前后的增强处理,所以通过调用处理器的invoke方法获取到每个方法的Method,并在前后自定义增强处理。

 // 调用invoke方法,ret存储该方法的返回值
        ret  = method.invoke(targetObject, args);
        return ret;

之所以JDK代理对象时需要原生类实现接口,是因为如果原生类无接口,那么getInterfaces()就会为空值报错。

注意代理生成的对象一般由接口接收,代理对象是Proxy的实例,不能用另一个接口实例去接收一个代理类。

CGLB动态代理

public class CGLibProxy implements MethodInterceptor {
    /** CGLib需要代理的目标对象 */
    private Object targetObject;
 
    public Object createProxyObject(Object obj) {
        this.targetObject = obj;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        Object proxyObj = enhancer.create();
        // 返回代理对象
        return proxyObj;
    }
 
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        Object obj = null;
        // 过滤方法
        if ("addUser".equals(method.getName())) {
            // 检查权限
            checkPopedom();
        }
        obj = method.invoke(targetObject, args);
        return obj;
    }
 
    private void checkPopedom() {
        System.out.println("======检查权限checkPopedom()======");
    }
}

通过Enhancer 处理继承原生类生成代理对象,而调用的方法与jdk一样,通过method.invoke调用。
对比选择:jdk代理在jdk版本不断升级过程后效率在不断提升,尤其是在jdk1.8后比cglb快很多,在此之前jdk代理比cglb慢些。

Spring AOP使用的一些限制条件:jdk代理的对象必须拥有接口,cglib代理则不能代理定义了final字段的对象,因为无法继承含final字段的对象。

AspectJ也是AOP思想的一种框架,拥有自己的编译器,在编译时提供代码横向weaving,通过ams的底层字节码技术在编译打包后就将增强代码增加到代理对象中。spring aop整合的aspectj主要是借鉴了注解的功能。
此外,Spring AOP在风格上借鉴了Aspect的风格

两者对比:
1.spring 实现aop基于两种代理模式JDK和CGLB。前者主要基于反射创建代理对象,后者基于继承创建对象。
2.spring切入点只能支持到方法层,暂不支持切入到字段,而AspectJ支持到字段,粒度更细,下面是官网的原话:

Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.

3.AspectJ是侵入式(invasiveness)的增强,在编译后增强代码会和目标一起在编译期生成,而spring aop可以在运行过程中进行增强,属于非侵入式(non-invasiveness),spring aop保证了业务代码的整洁性。

AOP注解使用

spring支持注解实现AOP。其主要的注解有如下:

  • @Aspect:将一个类标识为切面类。类中声明切入点,增强方法等。
  • @Pointcut:切入点。通过AspectJ风格的表达式匹配被切入对象的方法,当执行匹配的方法时,会对其执行使用了该切入点的增强方法。
  • @Before:在方法执行前增强。传入的切入点匹配的方法执行前,先执行该注解所在的方法。
  • @After:方法执行结束后进行增强。
  • @AfterReturning:方法执行后返回结果时。
  • @Around:环绕通知,在方法执行前(在@Before之前)增强,在方法执行后(在@AfterReturning后@After之前)增强
  • @JoinPoint:可以获取到织入对象方法的一些信息。
  • @ProceedingJoinPoint:是JoinPoint的子接口,额外可以调用目标方法,并修改传入的参数的值。

享元模式

  • 定义:运用共享技术来共享大量细粒度的对象。
  • 思想
  • 何时应用:当要出现大量相同的对象时,或对象的创建销毁耗性能且之后会用,那么可以通过重复使用以创建好的对象。
  • 内部状态和外部状态:指享元对象的共享部分和非共享部分。

注意点:如果对象在使用后已无法再被其他对象使用,那么不应该作为可共享的对象。
应用场景:String常量池,数据库连接池,Integer类等。
代码

public abstract class Enemy {
    public abstract void attack();
    public abstract void flee();
    public abstract void beDefeated();
}
public class Solider extends Enemy {
    @Override
    public void attack() {
        System.out.println("Solider is attacking");
    }

    @Override
    public void flee() {
        System.out.println("Solider is fleeing");

    }

    @Override
    public void beDefeated() {
        System.out.println("Solider is defeated");

    }
}
public class General extends Enemy {
    @Override
    public void attack() {
        System.out.println("General is attacking");
    }

    @Override
    public void flee() {
        System.out.println("General is fleeing");
    }

    @Override
    public void beDefeated() {
        System.out.println("General is defeated");
    }
}
public class EnemyFactory {
    private Map<Class,Enemy> enemy = new HashMap<>();
    public Enemy getEnemyByType(Class type){
        if (!enemy.containsKey(type)){
            try {
                Object o = type.newInstance();
                enemy.put(type, (Enemy) o);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }
        return enemy.get(type);
        }

}
public class FlyWeightTest {
    public static void main(String[] args) {
        EnemyFactory factory = new EnemyFactory();
        Enemy enemyByType = factory.getEnemyByType(General.class);
        Enemy enemyByType1 = factory.getEnemyByType(General.class);
        Enemy enemyByType2 = factory.getEnemyByType(Solider.class);
        enemyByType.attack();
        enemyByType2.attack();
        enemyByType1.flee();
    }
}

组合模式

  • 定义:又称部分对象模式。它创建了对象的组合树形结构。使得用户对单个对象和组合对象的访问具有一致性,即组合模式让调用者以一致的方式使用个别对象和组合对象。
  • 思想: 从组合对象结构中,尤其是具有层次型的属性结构中,将需要的一致性操作抽成抽象父类,让每一层次进行继承,并在类中组合抽象类的线性表,这样可通过类似于装饰者模式的不断嵌套调用,进行从上到下的层次化处理,比如遍历处理等。不过这种抽象组合会让本身具有明显上下级关系的调用在抽象类的作用下没有被强制性约束,可任意嵌套搭配。

学校结构
在这里插入图片描述
代码案例

public class University extends Component {
    private List<Component> components = new ArrayList<>();

    @Override
    public void add(Component component) {
        components.add(component);
    }

    @Override
    public void remove(Component component) {
        components.remove(component);
    }

    @Override
    public void print() {
        System.out.println("新余皇家学院");
        for (Component c :
                components) {
            c.print();
        }
    }
}
public class College extends Component {
    private List<Component> components = new ArrayList<>();

    @Override
    public void add(Component component) {
        components.add(component);
    }

    @Override
    public void remove(Component component) {
        components.remove(component);
    }

    @Override
    public void print() {
        System.out.println("数学与计算机学院");
        for (Component c :
                components) {
            c.print();
        }
    }
}
public class SoftwareEngineer2 extends Component {
    @Override
    public void print() {
        System.out.println("软工二班");
    }
}
public abstract class Component {
    //只有重写了该方法才可调用
    public  void add(Component component){
        throw new UnsupportedOperationException();
    }

    public void remove(Component component){
        throw  new UnsupportedOperationException();
    }
    public abstract  void print();
}
public class CompositeTest {
    public static void main(String[] args) {
        SoftwareEngineer se = new SoftwareEngineer();
        SoftwareEngineer2 se2  =new SoftwareEngineer2();
        se.print();

        College college = new College();
        college.add(se);
        college.add(se2);
        University university = new University();
        university.add(college);
        university.print();
    }
}

装饰者模式

定义:动态地将新功能以附加地形式扩展到对象上。

思想:让装饰物对主体进行不断包装在内部,而装饰物本身可再作为主体被包装,而装饰过程,则利用了递归调用进行处理起到想要的装饰效果。装饰物与主体某些属性和功能上在高层抽象上的本质是相同的。它与传统的思想有较大区别,装饰物不再组合或聚合于装饰主体,而是反过来,将主体放入装饰物中,进行不断嵌套来降低传统实现的扩展难度。

何时应用:当需要对某一属性,或某一功能进行分层次或者说多样化处理并使用时,可以考虑使用该模式。

如何抽象:装饰者模式符合OCP原则,那怎样选择要装饰的主体呢?例如这里的Drink的属性price,经过多次装饰而被改变。装饰物可以调用Drink提供的公共方法,但无法知道底层的真正实现类LongBlack的其它不同属性和方法,所以,那些希望添加额外功能的数据(但不会改变本身内容),可以作为装饰主体的属性并提供给装饰物处理的公共方法,以让装饰物类进行额外的处理。

内核:因为Decorator继承了Drink,所以Decorator本质上和装饰对象Coffee一样,只是实现有区别而已,目的是为了让包装了主体的装饰物能继续作为主体被进行包装。
但即便在Drink层面相同,也依旧要区分现实生活中主体和装饰的联系:主体可单独存在,而装饰依赖于主体,这是装饰者模式应用时的重要注意项。比如这里的巧克力不能脱离咖啡而存在。

经典例题:星巴克咖啡
在这里插入图片描述
java中应用装饰者模式场景
FilterInputStream实现了InputStream,并将其组合在内部。
代码案例
抽象装饰物

public class Decorator extends Drink {
    private Drink drink;

    public Decorator(Drink drink){
        this.drink = drink;
    }

    @Override
    public float cost() {
        return super.getPrice()+drink.cost();
    }
    public String getDes(){//
        return super.des+" "+super.getPrice()+"&&"+drink.getDes();
    }
}

装饰主体的抽象

public abstract class Drink {
    public String des;
    private float price = 0.0f;
    public abstract float cost();

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}

装饰主体的中间抽象

public abstract class Coffee extends Drink{
    public float cost(){
        return super.getPrice();
    }
}

真正的装饰主体

public class LongBlack extends Coffee {
    public LongBlack(){
        setDes("美式咖啡");
        setPrice(5.0f);
    }
}

装饰物

public class Milk extends Decorator {

    public Milk(Drink drink) {
        super(drink);
        setDes("牛奶");
        setPrice(2.0f);
    }
}
   public static void main(String[] args) {
        Drink longBlack = new LongBlack();
        Milk milk = new Milk(longBlack);
        Chocolate chocolate = new Chocolate(milk);
        System.out.println(chocolate.getDes());
        System.out.println(chocolate.cost());
        }

适配器模式

简单来说,让原本两个不能兼容使用的接口,通过适配器,将一个接口转换成可以实现另一个接口功能的模式。
1.解决什么场景的问题?
当调用者需要的数据类型或格式与我们提供的不一致时,可通过适配器将数据先进行转化处理,再返回给调用者。
2.它的优缺点是什么,局限性?
3.它Java中哪里使用到了?
InputStream的字符流转字节流等。SpringMVC的DispatchServlet中调用的HandlerAdapter。
4.UML类图展示下
在这里插入图片描述
代码:
电脑有一个双角的插头,但只提供了一个三孔插座,那么通过一个适配器,将保持原电脑继续使用双角插头,将三孔插座转换为双孔插座。

public class Computer {
//电脑自带的双脚插头
    private DoubleSocket socket;

    public void setSocket(DoubleSocket socket) {
        this.socket = socket;
    }

    public static void main(String[] args) {
        Computer computer = new Computer();
        //使用适配器并适配一个三孔插座。
        computer.setSocket(new Adapter(new TripleSocketImpl()));
        computer.socket.getPower();

    }
}

双脚插头接口,获取能源方法

public interface DoubleSocket {
    public  void getPower();
}

适配器实现双脚插头的方法,并聚合三孔插座。

public class Adapter implements DoubleSocket {
    private TripleSocket tripleSocket;

    public Adapter(TripleSocket tripleSocket) {
        this.tripleSocket = tripleSocket;
    }

    @Override
    //这里通过调用三孔插座的方法获取电力
    public void getPower() {
        tripleSocket.getTriplePower();
    }
}

三孔插座接口

public interface TripleSocket {
    public void getTriplePower();
}

三孔插座实例

public class TripleSocketImpl implements TripleSocket {
    @Override
    public void getTriplePower() {
        System.out.println("三角插座提供电源中");
    }
}

这里成功的保留了原computer继续调用doubleSocket接口方法的不变,让其能使用到TripleSocket的方法。现实中往往涉及到数据的转换调用,例如获取到链表时而对方需要的是数组,那么就可以在适配器中的方法编写代码转化,再返回给对方。

个人总结
在框架中,一般适配器类命名以Adapter结尾,而适配器的接口一般会聚合在某一实例中,此外,适配器中的处理方法,也一定会有具体的适配对象的处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值