3结构型模式(设计模式)

Posted 哇咔咔嗒

tags:

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

结构型模式

总体概括:
1、结构型模式关注点:“怎样组合对象/类”,所以我们关注类的组合关系
2、类结构型模式关心类的组合,由多个类可以组合成一个更大的(继承)
3、对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象(组合)
4、根据“合成复用原则”,在系统中尽量使用关联关系来代替继承关系,因此大部分结构型模式都是对象结构型模式
1、适配器模式:两个不兼容接口之间适配的桥梁
2、桥接模式:相同功能抽象化与实现化解耦,抽象与实现可以独立升级
3、过滤器模式:使用不同标准来过滤一组对象
4、组合模式:相似对象进行组合,形成树形结构
5、装饰器模式:向一个现有的对象添加新的功能,同时又不改变其结构
6、外观模式:向现有的系统添加一个新的接口,客户端访问此接口来隐藏系统的复杂性
7、享元模式:尝试重用现有的同类对象,如果未找到匹配的对象,则创建新的对象
8、代理模式:一个类代表另一个类的功能

适配器模式

将一个接口转换为客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者类之间的耦合度比较高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少一些。(可以看做充电器转换器)
适配器模式包含以下主要角色:
目标接口:可以是抽象类或者是接口。客户希望直接用的接口
适配者类:隐藏的转换接口
适配器类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口
package com.example.design.adapter;
/**
 * 1、在原有系统上增加一个适配器。让适配器可以把电影的中文字幕翻译成友人理解的日文字幕
 * 客户调用方法的时候,使用适配器即可
 * 类结构型模式
 * 对象结构型模式
 */
public class JPMovieAdapter implements Player
    public JPMovieAdapter()

    

    @Override
    public String player() 
        return null;
    
package com.example.design.adapter;

/**
 * 电影播放器
 * 阅读器
 */

public class MoviePlayer implements Player
    @Override
    public String player() 
        System.out.println("正在播放:易烊千玺.avi");
        String content="你好";
        System.out.println(content);
        return content;
    
package com.example.design.adapter;

/**
 * 1、系统原有接口player:可以播放电影,并且返回字幕
 */

public interface Player 

    String player();
package com.example.design.adapter;

/**
 * 2、系统原有接口,可以翻译字幕内容
 */

public interface Translator 

    String translate(String content);
package com.example.design.adapter;

/**
 * 翻译器
 */
public class Zh_JPTranslator implements Translator

    @Override
    public String translate(String content) 
        if("你好".equals(content))
            return "空尼记哇";
        
        if("什么".equals(content))
            return "纳尼";
        
        return "*************";
    

以上是公共类,下面有两种方法来实现适配器模式
1、类适配(创建一个包clazz)

package com.example.design.adapter.clazz;

import com.example.design.adapter.Player;
import com.example.design.adapter.Zh_JPTranslator;

public class JPMoviePlayerAdapter extends Zh_JPTranslator implements Player 
    //被适配对象
    private Player target;

    public JPMoviePlayerAdapter(Player target)
        this.target=target;
    
    @Override
    public String player() 
        String player = target.player();
        //转换字幕
        String translate = translate(player);
        System.out.println("日文翻译的::::"+translate);
        return null;
    
package com.example.design.adapter.clazz;

import com.example.design.adapter.MoviePlayer;

public class MainTest 
    public static void main(String[] args) 
        //原系统播放器
        MoviePlayer player=new MoviePlayer();
        JPMoviePlayerAdapter jpMoviePlayerAdapter = new JPMoviePlayerAdapter(player);
        jpMoviePlayerAdapter.player();
    

2、对象适配(创建一个包obj)

package com.example.design.adapter.obj;

import com.example.design.adapter.Player;
import com.example.design.adapter.Translator;
import com.example.design.adapter.Zh_JPTranslator;
/**
 * 组合的方式:对象结构模型,适配器转换到了翻译器功能上
 **/
public class JPMoviePlayerAdapter  implements Player 
    //组合的方式
    //翻译器
    private Translator translator=new Zh_JPTranslator();
    //被适配对象
    private Player target;

    public JPMoviePlayerAdapter(Player target)
        this.target=target;
    
    @Override
    public String player() 
        String player = target.player();
        //转换字幕
        String translate = translator.translate(player);
        System.out.println("日文翻译的::::"+translate);
        return null;
    
package com.example.design.adapter.obj;

import com.example.design.adapter.MoviePlayer;

/**
 * 系统原来拥有的类都不能修改,新建一个类
 */
public class MainTest 
    public static void main(String[] args) 
        //原系统播放器
        MoviePlayer player=new MoviePlayer();
        JPMoviePlayerAdapter jpMoviePlayerAdapter = new JPMoviePlayerAdapter(player);
        jpMoviePlayerAdapter.player();
    
用到适配器的场景
1、Tomcat如何将request流转换为标准request
2、Spring AOP中的AdvisorAdapter是什么:增强适配器
    前置,后置,返回,结束,Advisor(通知方法)
3、SpringMVC中经典的HandlerAdapter
4、springBoot中WebMvcConfigurerAdapter为什么存在又取消

桥接模式

1、将抽象与实现解耦,使两者都可以独立变化
2、在现实生活中,某些类具有两个或多个维度的变化,如图形可按照形状分,又可按照颜色分。如何设类似于Photoshop这样的软件,能画出不同形状和不同颜色的图形呢?如果用继承方式,m种形状和n种颜色的图形就有m×n种,不但对应的子类很多,而且扩展困难。不同颜色和字体的文字,不同品牌和功率的汽车。
3、桥接将继承转为关联,降低类之间的耦合度,减少代码量。

桥接模式主要包含以下主要角色
1、抽象化角色:定义抽象类,丙班韩一个对实现化对象的引用
2、扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
3、实现化角色:定义实现化角色的接口,供扩展抽象化角色调用
4、具体实现化角色:给出实现化角色接口的具体实现

package com.example.design.bridge;

/**
 * 1、抽象手机类
 *    手机有各种销售渠道价格都不一样
 */
public abstract class AbstractPhone 
    //桥接在此(设计期间就得想好)
    //分离渠道【桥接的关注点】
//【把引起此类变化的维度直接抽取出来,通过组合的方式连接起来】
    AbstractSale sale;

    /**
     * 当前手机的描述
     * @return
     */
    abstract String getPhone();

    public void setSale(AbstractSale sale) 
        this.sale = sale;
    
package com.example.design.bridge;

/**
 * 抽象销售渠道
 */
public class AbstractSale 
    private String type;
    private Integer price;
    public AbstractSale(String type,Integer price)
        this.type=type;
        this.price=price;
    
    String getSaleInfo()
        System.out.println();
        return "渠道"+type+"---->"+"价格"+price;
    
package com.example.design.bridge;

public class Iphone extends AbstractPhone
    @Override
    String getPhone() 
        return "IPhone:" +sale.getSaleInfo();
    
package com.example.design.bridge;

public class MiPhone extends AbstractPhone
    @Override
    String getPhone() 
        return "小米";
    
package com.example.design.bridge;

/**
 * 线下渠道
 */
public class OfflineSale extends AbstractSale
    public OfflineSale(String type, Integer price) 
        super(type, price);
    
package com.example.design.bridge;

/**
 * 线上渠道
 */
public class OnlineSale extends AbstractSale
    public OnlineSale(String type, Integer price) 
        super(type, price);
    
package com.example.design.bridge;

public class MainTest 
    public static void main(String[] args) 
        Iphone iphone = new Iphone();
        iphone.setSale(new OfflineSale("线下",9999));
        String phone = iphone.getPhone();
        System.out.println(phone);
    
输出:
Connected to the target VM, address: 127.0.0.1:63584, transport: socket
IPhone:渠道线下---->价格9999
Disconnected from the target VM, address: 127.0.0.1:63584, transport: socket
Process finished with exit code 0
什么场景用到?
1、当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
2、当一个系统不希望使用继承或者因为多层次导致系统类的个数急剧增加时。
3、当一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性时
4、InputStreamReader桥接模式+适配器

装饰器模式

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

package com.example.design.decorator;

/**
 * 抽象构建
 */
public interface ManTikTok 
    void tiktok();
package com.example.design.decorator;

public class LeiTikTok implements ManTikTok
    @Override
    public void tiktok() 
        System.out.println("雷丰阳,tiktok。。。");
    
package com.example.design.decorator;

/**
 * 抽象装饰器
 * 抖音直播装饰器
 */
public interface TikTokDecorator extends ManTikTok
    void enanle();
package com.example.design.decorator;

/**
 * 美颜装饰器
 * 装饰谁?
 * 装饰器只关心增强这个类的方法
 */
public class MeiYanDecorator implements TikTokDecorator
        private ManTikTok manTikTok;
        public MeiYanDecorator(ManTikTok manTikTok)
            this.manTikTok=manTikTok;
        

    @Override
    public void tiktok() 
         //开启美颜
        enanle();
        //我开始直播
        manTikTok.tiktok();
    

    /**
     * 定义的增强功能
     */
    @Override
    public void enanle() 
        System.out.println("看这个美女=====");
        System.out.println("花花花。。。。。");
    
package com.example.design.decorator;

public class MainTest 
    public static void main(String[] args) 
        ManTikTok manTikTok=new LeiTikTok();

        MeiYanDecorator meiYanDecorator = new MeiYanDecorator(manTikTok);
        meiYanDecorator.tiktok();
    
装饰器的角色
1、抽象构建角色:
   定义一个抽象接口以规范准备接收附加责任的对象
2、具体构建角色
   实现抽象结构,通过装饰角色为其添加一些职责
3、抽象装饰角色
   继承抽象构建,并包含具体构建的实例,可以通过其子类扩展具体构建的功能
4、具体装饰角色
   实现抽象装饰的相关方法,并给具体构建对象添加附加的责任
装饰器的使用场景
1、springSession中如何进行session与redis关联
2、MybatisPlus提取了QueryWrapper
3、Spring中BeanWrapper
4、Spring Webflux中的WebHandlerDecorator
总结:
已存在的类,在某一天某个功能使用的时候发现不够,就可以使用装饰器

代理模式

代理模式:给某一个对象提供一个代理,并由代理对象控制对原对象的引用,对象结构型模式,这种也是静态代理

代理模式包含如下角色:
Subject:抽象主体角色(抽象类与接口)
Proxy:代理主体角色(代理对象类)
PealSubJect:真实主体角色(被代理对象类)

静态代理

package com.example.design.proxy.statics;

/**
 * 抽象主体。被代理角色
 */
public interface ManTikTok 
    void tiktok();
package com.example.design.proxy.statics;

/**
 * Subject 主体
 *
 */
public class LeiTikTok implements ManTikTok 
    @Override
    public void tiktok() 
        System.out.println("雷丰阳,tiktok。。。");
    
package com.example.design.proxy.statics;

/**
 * 代理一般都是和被代理对象属于同一个接口
 */
public class TikTokProxy implements ManTikTok 
    //被代理对象
    private ManTikTok manTikTok;
    public TikTokProxy(ManTikTok manTikTok)
        this.manTikTok=manTikTok;
    

    /**
     * 代理可更改,可替换
     */
    @Override
    public void tiktok() 
        //增强功能
        System.out.println("渲染直播间。。。。。");
        System.out.println("课程只要666,仅此一天");
        manTikTok.tiktok();
    
package com.example.design.proxy.statics;

/**
 * 静态代理就是装饰器
 * 一般说装饰器模式是代理模式的一种
 */
public class MainTest 
    public static void main(String[] args) 
        TikTokProxy tikTokProxy = new TikTokProxy(new LeiTikTok());
        tikTokProxy.tiktok();
    
静态代理的缺点:
每一种不同的被代理类出现时,要创建不同的静态代理类

动态代理

package com.example.design.proxy.dynamic;

public interface ManTikTok 
    void tiktok();
package com.example.design.proxy.dynamic;

public class LeiTikTok implements ManTikTok,SaleTikTok
    @Override
    public void tiktok() 
        System.out.println("雷丰阳,tiktok。。。。。。。。");
    

    @Override
    public void sale() 
        System.out.println("雷丰阳的课,只要666.。。。。。");
    

    public void haha()
        System.out.println("雷丰阳哈哈哈哈哈。。。。。");
    
package com.example.design.proxy.dynamic;

public interface SaleTikTok 
    void sale();
package com.example.design.proxy.dynamic;

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

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)
        /**
         * newProxyInstance的三个参数
         * lassLoader loader, 当前被代理对象的类加载器
         * Class<?>[] interfaces,当前被代理对象所实现的所有接口
         * InvocationHandler h,当前被代理对象执行目标方法的时候,我们使用h可以定义拦截增强方法
         */
        Object o = Proxy.newProxyInstance(
                t.getClass().getClassLoader(),
                t.getClass().getInterfaces(),
                new JdkTikTokProxy(t));
        return (T) o;
    

    /**
     * 定义目标方法的拦截逻辑
     * 只要代理对象调了任何方法invoke都会被执行
     * @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 invoke;
    
package com.example.design.proxy.dynamic;

/**
 * 动态代理模式
 * 代理对象和目标对象的相同之处在于都是同一个接口
 */
public class MainTest 
    public static void main(String[] args) 
        /**
         * 动态代理要求被代理对象必须有接口,因为JDK创建动态代理的时候,需要必须传接口
         */
        ManTikTok leiTikTok = new LeiTikTok();
        ManTikTok proxy = JdkTikTokProxy.getProxy(leiTikTok);
        proxy.tiktok();
        ((SaleTikTok)proxy).sale();
        //不能代理被代理对象自己的方法,因为porxy只能转成接口类
        /**
         * Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.example.design.proxy.dynamic.LeiTikTok
         *  at com.example.design.proxy.dynamic.MainTest.main(MainTest.java:16)
         *  类型转换异常
         */
        //((LeiTikTok)proxy).haha();
    
输出:
真正执行被代理方法
雷丰阳,tiktok。。。。。。。。
返回值null
真正执行被代理方法
雷丰阳的课,只要666.。。。。。
返回值null

cglib动态代理

package com.example.design.proxy.cglib;

public class LeiTikTok 
    public void tiktokHaha()
        System.out.println("雷丰阳,tiktok。。。。。。");
    
package com.example.design.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib继承本类
 */
public class CglibProxy 

    //为任意对象创建代理
    public static<T> T createProxy(T t)
        //1、创建一个增强器
        Enhancer enhancer = new Enhancer();
        //2、设置要增强哪个类的功能,增强器为这个类动态创建一个子类
        enhancer.setSuperclass(t.getClass());
        /**
         * new MethodInterceptor()为方法拦截器,会拦截到父类的所有方法
         */
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() 
            @Override
            public Object intercept(Object obj,
                                    Method method,  //为了能获取到原方法的一些元数据信息
                                    Object[] args,
                                    MethodProxy proxy) throws Throwable 
                //编写拦截的逻辑
                System.out.println("cglib上场了=====");
                //当前方法的信息
                String name = method.getName();
                System.out.println("==========:::"+name);
                //目标方法执行
                Object invoke = proxy.invokeSuper(obj, args);
                return invoke;
            
        );
        //创建出代理对象
        Object o = enhancer.create();
        return (T) o;
    
package com.example.design.proxy.cglib;

/**
 * 引入cglib依赖
 */
public class CglibTest 
    public static void main(String[] args) 
        LeiTikTok leiTikTok = new LeiTikTok();
        LeiTikTok proxy = CglibProxy.createProxy(leiTikTok);
        proxy.tiktokHaha();

    

组合模式

把一组相似的对象当做一个单一的对象,如树形菜单
package com.example.design.composite;

import lombok.Data;

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

@Data
public class Menu 
    private Integer id;
    private String name;
    public Menu(Integer id,String name)
        this.id=id;
        this.name=name;
    
    //组合模式关注点
    private List<Menu> childs=new ArrayList<>();
    //提供添加层级的方法
    void addChildMenu(Menu menu)
        childs.add(menu);
    
    //层级遍历的方法
    void printMenu()
        System.out.println(name);
        if(childs.size()>0) 
            for (Menu child : childs) 
                child.printMenu();
            
        
    
package com.example.design.composite;

public class MainTest 
    public static void main(String[] args) 
        Menu root=new Menu(1,"系统管理");
        Menu role=new Menu(2,"角色管理");
        root.addChildMenu(role);
        role.addChildMenu(new Menu(6,"固定角色"));
        role.addChildMenu(new Menu(7,"临时授予"));

        Menu menu3 = new Menu(3, "用户管理");
        root.addChildMenu(menu3);
        menu3.addChildMenu(new Menu(4,"临时用户"));
        menu3.addChildMenu(new Menu(5,"注册用户"));
        root.printMenu();
    

外观模式

外观模式又叫做门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而时使这些子系统更加容易被访问的模式

package com.example.design.facade;

public class Edu 
    public void assignSchool(String name)
        System.out.println(name+",你的孩子明天去清华大学报道吧。。。");
    
package com.example.design.facade;

public class Police 
    public void resgister(String name)
        System.out.println(name+"已办理落户");
    
package com.example.design.facade;

public class Social 
    public void handleSocial(String name)
        System.out.println(name+"你的社保关系已经转移。。。。");
    
package com.example.design.facade;

public class WeiXinFacade 
    Police police =new Police();
    Edu edu=new Edu();
    Social social=new Social();

    public  void handlerXxx(String name)
        police.resgister(name);
        edu.assignSchool(name);
        social.handleSocial(name);
    
package com.example.design.facade;

public class MainTest 
    public static void main(String[] args) 
        WeiXinFacade weiXinFacade = new WeiXinFacade();
        weiXinFacade.handlerXxx("易烊千玺");
    

享元模式

1、享元模式,运用共享技术有效的支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。对象结构型。
2、在享元模式中可以共享的相同内容称为内部状态,而那些需要外部环境来设置的不能共享的内容称为外部状态,由于区分了内部状态和外部状态,由此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
3、在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池用于存储具有相同内部状态的享元对象。
享元模式包含如下角色:
1、抽象享元类
2、具体享元类
3、非共享具体享元类
4、享元工厂类
package com.example.design.flyweight;

public abstract class AbstractWaitressFlyweight 

    //能否服务
    boolean canServer =true;
    //canServer是改变的,调用时改变

    //正在服务  享元的不可共享属性留给外部进行改变的接口
    abstract void server();

    //服务完成  享元的不可共享属性留给外部进行改变的接口
    abstract void end();

    public boolean isCanServer() 
        return canServer;
    
package com.example.design.flyweight;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 具体享元类
 */
@AllArgsConstructor
public class BeautifulWaitress extends AbstractWaitressFlyweight
    //工号
    String id;
    //名字
    String name;
    //年龄
    int age;
    //以上是不变的

    @Override
    void server() 
        System.out.println("工号:"+id +";"+name+";"+age+" 正在为您服务");
        //改变外部状态
        this.canServer=false;
    

    @Override
    void end() 
        System.out.println("工号:"+id +";"+name+";"+age+" 服务结束");
        this.canServer=true;
    
package com.example.design.flyweight;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 足道店,相当于享元工厂
 *       店里有很多服务员
 */
public class ZuDao 
    private static Map<String,AbstractWaitressFlyweight> pool=new HashMap<>();

    //享元,池子中有对象
    static 
        BeautifulWaitress beautifulWaitress = new BeautifulWaitress("1111","张三",18);
        pool.put(beautifulWaitress.id,beautifulWaitress);
        BeautifulWaitress beautifulWaitress2 = new BeautifulWaitress("2222","李四",22);
        pool.put(beautifulWaitress2.id,beautifulWaitress2);
    
    public void addWaitress(AbstractWaitressFlyweight abstractWaitressFlyweight)
        pool.put(UUID.randomUUID().toString(),abstractWaitressFlyweight);
    

    public static AbstractWaitressFlyweight getWaitress(String name)
        AbstractWaitressFlyweight abstractWaitressFlyweight = pool.get(name);
        if (abstractWaitressFlyweight==null)
            for (AbstractWaitressFlyweight value : pool.values()) 
                //当前共享对象能否使用
                if (value.isCanServer())
                    return value;
                
            
            return null;
        
        return abstractWaitressFlyweight;
    
package com.example.design.flyweight;

public class MainTest 
    public static void main(String[] args) 
        AbstractWaitressFlyweight a1 = ZuDao.getWaitress("");
        if (a1!=null) 
            a1.server();
        
        System.out.println(a1);
        AbstractWaitressFlyweight a2 = ZuDao.getWaitress("");
        if(a2!=null) 
            a2.server();
            a2.end();
        

        System.out.println(a2);
        AbstractWaitressFlyweight a3 = ZuDao.getWaitress("");
        if(a3!=null) 
            a3.server();
        
        System.out.println(a3);
    
输出:
工号:1111;张三;18 正在为您服务
com.example.design.flyweight.BeautifulWaitress@7229724f
工号:2222;李四;22 正在为您服务
工号:2222;李四;22 服务结束
com.example.design.flyweight.BeautifulWaitress@4c873330
工号:2222;李四;22 正在为您服务
com.example.design.flyweight.BeautifulWaitress@4c873330
Disconnected from the target VM, address: 127.0.0.1:50532, transport: socket

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

结构型模式之 桥接模式

MVVM模式下,ViewModel和View,Model有啥区别

MVVM模式下,ViewModel和View,Model有啥区别

设计模式-桥接模式Bridge

结构型模式--桥接模式

Atitit 项目的主体设计与结构文档 v3