设计模式初识结构型模式(Structural Pattern)

Posted 三笠·阿卡曼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式初识结构型模式(Structural Pattern)相关的知识,希望对你有一定的参考价值。

为什么要使用结构型模式

结构型模式关注点在于"如何组合对象/类",更关注类之间的组合关系;

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的(继承);
  • 对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另外一个类的实例对象(组合);

根据"合成复用原则",在系统中进来使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式

  • 适配器模式(Adapter Pattern):两个不兼容接口之间适配的桥梁;
  • 桥接模式;
  • 过滤器模式;
  • 组合模式:相似对象进行组合,形成树形结构
  • 装饰器模式:向一个现有的对象添加新的功能,同时又不改变其结构;
  • 外观模式:向现有的系统添加一个接口,客户端访问此接口来隐藏系统的复杂性;
  • 享元模式;
  • 代理模式:一个类代表的是另外一个类的功能;

适配器模式(Adapter Pattern)

将一个接口转换成客户希望的另外一个接口,适配器模式使得接口不兼容的那些类可以一起工作;
别名也可以是Wrapper,包装器;
适配器模式(Adapter)包含以下角色:

  • 目标(Target)接口:可以是抽象类或接口,客户希望直接使用的接口;
  • 适配者(Adaptee)类:隐藏的转换接口;
  • 适配器(Adapter)类:是一个转换器,通过集成或引用适配者的对象,将适配者接口转换成目标接口;

例子

写一个关于放电影与字幕搭配的例子,本身放电影的接口与打印字幕的接口不相匹配,结果加一个适配器转换类即可完成适配
电影接口

package com.vleus.design.structural.adapter;

/**
 * @author vleus
 * @date 2021年06月01日 22:45
 */
public interface Player {

    String play();
}

电影实现类

package com.vleus.design.structural.adapter;

/**
 * @author vleus
 * @date 2021年06月01日 22:46
 */
public class MoviePlayer implements Player{
    @Override
    public String play() {
        System.out.println("正在播放: 小电影.avi");
        String content = "你好";
        System.out.println(content); //打印字幕
        return content;
    }
}

字幕接口

package com.vleus.design.structural.adapter;

/**
 * 系统原有接口,可以翻译文字内容
 */
public interface Translator {

    String translate(String contents);
}

字幕接口实现类

package com.vleus.design.structural.adapter;

/**
 * @author vleus
 * @date 2021年06月01日 22:52
 */
public class Zh_JPTranslator implements Translator{
    @Override
    public String translate(String contents) {
        if ("你好".equals(contents)) {
            return "空你几哇";
        }

        if ("不要啊".equals(contents)) {
            return "雅蠛蝶";
        }

        return "******";
    }
}

通过集成字幕类完成适配器转换

package com.vleus.design.structural.adapter.clazz;

import com.vleus.design.structural.adapter.Player;
import com.vleus.design.structural.adapter.Zh_JPTranslator;

/**
 * @author vleus
 * @date 2021年06月01日 22:50
 * @desc 通过继承的方式适配转换翻译器的功能上
 */
public class JpMoviePlayerAdapter extends Zh_JPTranslator implements Player {

    private Player target;

    public JpMoviePlayerAdapter(Player target) {
        this.target = target;
    }

    @Override
    public String play() {

        String play = target.play();

        //转换字幕
        String translate = translate(play);
        System.out.println("日文:"  + translate);

        return play;
    }
}

通过组合的方式实现(推荐)

package com.vleus.design.structural.adapter.obj;

import com.vleus.design.structural.adapter.Player;
import com.vleus.design.structural.adapter.Translator;
import com.vleus.design.structural.adapter.Zh_JPTranslator;

/**
 * @author vleus
 * @date 2021年06月01日 22:50
 * @desc 通过继承的方式适配转换翻译器的功能上
 */
public class JpMoviePlayerAdapter implements Player {

    private Player target;

    //组合的方式实现适配器效果
    private Translator translator = new Zh_JPTranslator();

    public JpMoviePlayerAdapter(Player target) {
        this.target = target;
    }

    @Override
    public String play() {

        String play = target.play();

        //转换字幕
        String translate = translator.translate(play);
        System.out.println("日文:"  + translate);

        return play;
    }
}

适配器模式的场景

  • Tomcat如何将Request转为标准Request;
    • tomcat,Reuquest接口;
    • servletReuqest接口;
  • Spring AOP中的AdvisiorAdapter是什么?
  • Spring MVC经典的HandlerAdapter是什么?
  • SpringBoot中WebMvcConfigurerAdapter为什么存在又取消?

桥接模式

概念:将抽象与实现解耦,使得两者都可以独立化;
桥接将集成转换为关联,降低类之间的耦合度,减少代码量;


桥接模式通俗的解释为,就是一个类对象比如有两个属性,其中一个属性是不变的就不说了,如果另外一个属性是经常变化的,使用桥接就十分灵活,
拿上面手机举例:一个手机,一个手机的销售渠道和价格是经常发生变化的,对于不同的手机在正常思维条件下就要创建不同排列组合下的渠道价格类,就很多,因此可以进行桥接处理
手机类抽象化

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:30
 */
public abstract class AbstractPhone {

    //桥接发生在此,设计期间想好
    //真正会引起此类变化的一个维度直接抽取出来,通过组合的方式接起来
    AbstractSale sale; //分离渠道
    /**
     * 当前手机的描述:拍照手机、性能手机等
     * @return
     */
    abstract String getPhone();

    public void setSale(AbstractSale sale) {
        this.sale = sale;
    }
}

不同品牌手机实现
小米手机

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:35
 */
public class MiPhone extends AbstractPhone{

    @Override
    String getPhone() {
        return "MiPhone";
    }
}

苹果手机

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:35
 */
public class IPhone extends AbstractPhone{
    @Override
    String getPhone() {
        return "IPhone" + sale.getSaleInfo();
    }
}

桥接类

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:32
 */
public abstract class AbstractSale {

    private String type;

    private Integer price;

    public AbstractSale(String type, Integer price) {
        this.type = type;
        this.price = price;
    }

    String getSaleInfo() {
        return "渠道:" + type + "===>,价格:" + price;
    }
}

桥接类实现:线上销售及价格

package com.vleus.design.structural.bridge;

/**
 * 线上渠道
 * @author vleus
 * @date 2021年06月01日 23:34
 */
public class OnlineSale extends AbstractSale{
    public OnlineSale(String type, Integer price) {
        super(type, price);
    }
}

线下销售及价格

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:34
 * @desc 线下渠道
 */
public class OfflineSale extends AbstractSale{
    public OfflineSale(String type, Integer price) {
        super(type, price);
    }
}

测试类

package com.vleus.design.structural.bridge;

/**
 * @author vleus
 * @date 2021年06月01日 23:36
 */
public class MainTest {

    public static void main(String[] args) {

        IPhone iPhone = new IPhone();
        iPhone.setSale(new OnlineSale("线上",9999));

        String phone = iPhone.getPhone();
        System.out.println(phone);
    }
}

装饰器模式(Decorator/Wrapper(包装)Pattern)

  • 适配器模式是连接两个类,装饰器是增强一个类;
  • 向一个现有的对象添加新的功能,同时又不改变其结构,属于对象结构模式;
  • 创建一个装饰器类,用来包装原有的类,并在保持方法签名完整性前提下,提供额外的功能;

    利用tiktok直播这个例子举例,普通直播就是tiktok直播,在此基础上加入装饰器,美颜直播

抽象构建tiktok平台

package com.vleus.design.structural.decorator;

/**
 * @author vleus
 * @date 2021年06月02日 22:04
 */
public interface TikTok {

    void tiktok();
}

tiktok平台实现,王冰冰直播

package com.vleus.design.structural.decorator;

/**
 * @author vleus
 * @date 2021年06月02日 22:04
 */
public class BingTikTok implements TikTok {
    @Override
    public void tiktok() {
        System.out.println("王冰冰,tiktok....");
    }
}

抽象装饰器接口,继承原有的抽象构建平台tiktok

package com.vleus.design.structural.decorator;

/**
 * @author vleus
 * @date 2021年06月02日 22:07
 * @desc tiktok直播装饰器
 */
public interface TikTokDecorator extends TikTok{

    void enableMeiYan();
}

美颜实现类,实现美颜的装饰器接口

package com.vleus.design.structural.decorator;

/**
 * @author vleus
 * @date 2021年06月02日 22:09
 * @desc 美颜装饰器
 */
public class MeiYanDecorator implements TikTokDecorator{
	
	//传入具体的需要进行装饰的对象
    private BingTikTok bingTikTok;

    public MeiYanDecorator(BingTikTok bingTikTok) {
        this.bingTikTok = bingTikTok;
    }

    @Override
    public void tiktok() {
        //开启美颜效果
        enableMeiYan();
		//老本行,直播
        bingTikTok.tiktok();
    }

    /**
     * 定义的增强功能
     */
    @Override
    public void enableMeiYan() {
        System.out.println("看这个美女....");
        System.out.println("花花花花花....");
    }
}

测试类

package com.vleus.design.structural.decorator;

/**
 * @author vleus
 * @date 2021年06月02日 22:06
 */
public class MainTest {

    public static void main(String[] args) {
        BingTikTok bingTikTok = new BingTikTok();
//
//        bingTikTok.tiktok();

        MeiYanDecorator meiYanDecorator = new MeiYanDecorator(bingTikTok);
        meiYanDecorator.tiktok();

    }
}

装饰器模式的场景

  • SpringSession中如何进行session与redis关联?HttpRequestWrapper
    • session:数据本身存在内存
    • session:处理之后存在redis;
    • HttpRequuestWrapper:
  • Spring中的BeanWrapper是做什么的?
  • SpringWebflux中的WebHandlerDecorator?

代理模式(Proxy Pattern),重中之重

概念:代理模式(Proxy Pattern),给某一个对象提供一个代理,并由代理对象控制对原对象的引用,结构型模式,这种也是静态代理;
代理模式主要角色:

  • Subject:抽象主体角色(抽象类或接口);
  • Proxy:代理主体角色(代理对象类);
  • RealSubject:真实主体角色(被代理对象类);

静态代理

装饰器模式就是静态代理,是属于代理模式的一种
抽象接口

package com.vleus.design.structural.proxy.statics;

//抽象主体Subject
public interface TikTok {

    void tiktok();
}

实际对象类

package com.vleus.design.structural.proxy.statics;

/**
 * @author vleus
 * @date 2021年06月02日 22:38
 */
public class BingTiktok implements TikTok{
    @Override
    public void tiktok() {
        System.out.println("王冰冰,直播....");
    }
}

代理对象类

package com.vleus.design.structural.proxy.statics;

import com.vleus.design.structural.decorator.BingTikTok;

/**
 * @author vleus
 * @date 2021年06月02日 22:40
 * @desc 静态代理类,代理一般都是和被代理对象实现同一个接口
 */
public class TikTokProxy implements TikTok{

    private BingTikTok bingTikTok; //被代理对象

    public TikTokProxy(BingTikTok bingTikTok) {
        this.bingTikTok = bingTikTok;
    }


    @Override
    public void tiktok() {

        System.out.println("杭州分冰正在直播。。。");
        bingTikTok.tiktok();
    }
}

测试类

package com.vleus.design.structural.proxy.statics;

import com.vleus.design.structural.decorator.BingTikTok;

/**
 * @author vleus
 * @date 2021年06月02日 22:44
 */
public class MainTest {

    public static void main(String[] args) {

        //静态代理,就是装饰器,装饰器模式是代理模式的一种
        TikTokProxy tikTokProxy = new TikTokProxy(new BingTikTok());
        tikTokProxy.tiktok();

    }
}

jdk动态代理

JDK动态代理要求被代理的对象必须实现接口!!!
因为jdk动态代理的时候通过getInterfaces就知道了被代理对象有什么行为!!!
创建动态代理类,必须实现InvocationHandler接口

package com.vleus.design.structural.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author vleus
 * @date 2021年06月02日 22:50
 */
public class JdkTikTokProxy<T> implements InvocationHandler {

    private T target;
    //接收被代理的对象
    public JdkTikTokProxy(T target) {
        this.target = target;
    }

    /**
     * 获取被代理对象的代理对象
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T getProxy(T t) {

        /**
         * ClassLoader loader: 当前被代理对象的类加载器
         * Class<?>[] interfaces: 当前被代理对象所实现的所有接口
         * InvocationHandler h:当前被代理对象执行目标方法的时候,使用h可以定义拦截增强方法
         */

        Object o = Proxy.newProxyInstance(t.getClass().getClassLoader(),
                                          t.getClass().getInterfaces(),
                                          new JdkTikTokProxy(t));
        return (T)o;
    }

    /**
     * 定义目标方法的拦截逻辑,每个方法都会进来
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy,
                         Method method,
                         Object[] args) throws Throwable {

        System.out.println("真正执行被代理对象的方法");
        Object invoke = method.invoke(target, args);
        System.out.println("返回值: " + invoke);
        return null;
    }
}

测试类

package com.vleus.design.structural.proxy.dynamic;

import com.vleus.design.structural.proxy.statics.BingTiktok;
import com.vleus.design.structural.proxy.statics.TikTok;

/**
 * @author vleus
 * @date 2021年06月02日 22:50
 * @desc 动态代理模式
 *
 * 代理对象和目标对象的相同点在于都是同一个接口
 *
 */
public class MainTest {

    public static void main(String[] args) {

        TikTok bingTikTok = new BingTiktok();

        TikTok proxy = JdkTikTokProxy.getProxy(bingTikTok);

        proxy.tiktok();
    }
}

代理模式总结

场景:

  • Mybatis的mapper是什么?
    • 动态代理
    • Mybatis,实现MapperProxy
  • Seata的DataSourceProxy是什么?
  • DruidDataSource存在的Proxy模式?

组合模式(Composite Pattern)

概念:把一组相似的对象当作一个单一的对象。如:树形菜单;

package com.vleus.design.structural.composite;

import java.util.ArrayList;
import java.util.List;

/**
 * @author vleus
 * @date 2021年06月03日 22:53
 */
public class Menu {

    private Integer id;

    private String name;

    public 

以上是关于设计模式初识结构型模式(Structural Pattern)的主要内容,如果未能解决你的问题,请参考以下文章

编程思想设计模式结构模式Structural代理模式Proxy

编程思想设计模式结构模式Structural享元模式flyweight

九 合成(Composite)模式 --结构模式(Structural Pattern)

七适配器(Adapter)模式--结构模式(Structural Pattern)

结构型设计模式 Structural Patterns :适配器 Adapter(C++ 实现)

结构型设计模式 Structural Patterns :适配器 Adapter(Python 实现)