设计模式——结构性设计模式

结构性设计模式

针对类与对象的组织结构。(白话:类与对象之间的交互的多种模式

类/对象适配器模式

当需要传入一个A类型参数,但只有B类型类时,就需要一个A类型的适配器装入B类的数据,来将B数据转成A类型,然后作为参数传入

适配器,在生活中又称转换器。现在的手机基本都割去了3.5mm的耳机接口,此时只有有线耳机,要听歌就需要一个转换器将3.5mm接口转成手机有的type-c的接口

类适配器(不建议)

继承需要转变的类

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我们没有35MM类型的手机接口,只有type-c的手机接口,那这里该填个type-c。所以需要一个转接口将35MM转为type-c接口
    }

    public static void test(typeC typec){   //现在我们需要调用test方法,但是test方法需要类型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//接口
public interface typeC {    //typeC接口也想听歌
    String listen();
}

//父类
public class 35MM{	
    public String listenMusic(){
		return "有线耳机听歌!"	//因为只有有线耳机,所以只有35MM才能听歌
    }
}

//子类作适配器 继承35MM,实现type-C接口
public class Adapter extends 35MM implements typeC{
    
    @Override
    public String listen() {  //现在不再继承35MM,仅实现typeC接口
        return super.listenMusic();
    }
}

对象适配器

将需要转变的类实例化,并用作与适配器类的构造方法

因为类适配器会占用一个继承位,而java又是单继承的。如果typeC不是接口而是抽象类的话就用不了了。所以提出对象适配器:

//主方法
public class Main {
    public static void main(String[] args) {
        TestSupplier supplier = new TestSupplier();
      	test( ? );   //我们没有35MM类型的手机接口,只有type-c的手机接口,那这里该填个type-c。所以需要一个转接口将35MM转为type-c接口
    }

    public static void test(typeC typec){   //现在我们需要调用test方法,但是test方法需要类型是typeC
        System.out.println("成功得到:"+typec.listen());
    }
}

//接口
public interface typeC {    //typeC接口也想听歌
    String listen();
}

//父类
public class 35MM{	
    public String listenMusic(){
		return "有线耳机听歌!"	//因为只有有线耳机,所以只有35MM才能听歌
    }
}

//子类作适配器 继承35MM,实现type-C接口
public class Adapter implements typeC{	//现在不再继承35MM,仅实现typeC接口
    35MM 35mm;	//实例化需要转变的类
    public Adapter(35MM 35mm){	//将实例化的对象用于构造对象
		this.35mm = 35mm;
    }
    
    @Override
    public String listen() {   //接着实现listen方法,直接使用typeC提供的实现
        return 35mm.listenMusic();
    }
}

桥接模式

配置自定义

同一种产品有着不同的配置,就像手机有:运行内存 4 6 8g,存储内存:64 128 256g,芯片:骁龙 A系列 麒麟 联发科 猎户座。不能每一种配置都写一个类就太麻烦了,所以有了桥接模式,可以通过多个类桥接成一个产品类。

优势:可以通过多个维度来自由设定配置

这里以华为手机举例:(小知识——华为手机是用自家的麒麟芯片)

//第一层类:继承该类可以自定义芯片类型
public abstract class AbstractPhone {
	private Size size; //这里是描述存储内存。由于举例简单点方便看得懂就不写运行内存了
    
    public AbstractPhone(Size size){
        this.size = size;
    }
    
    public abstract String getType();	//这里的类型是指芯片类型
}

//接口及实现类
public interface Size{
    String getSize();
}
public class 256G implements Size{
    @Override
    public String getSize() {
        return "256g内存";
    }
}

//第二层类:继承该类可以自定义芯片类型和存储内存的尺度大小
public abstract class RefinedAbstractPhone extends AbstractPhone{
    protected RefinedAbstractPhone(Size size) {
        super(size);
    }
    
    public String getSize(){   //添加尺寸维度获取方式
        return size.getSize();
    }
}

//产品类:继承第二层类,然后自定义存储内存大小和芯片种类
public class HUAWEI extends RefinedAbstractPhone{
    protected HUAWEI(Size size){	//构造方法指定具体存储内存大小
        super(size);
    }
    
    @Override
    public String getType() {
        return "华为手机";   //返回手机品牌类型
    }
}

//主方法
public static void main(String[] args) {
	HUAWEI huawei = new HUAWEI(new 256G());
    System.out.println(huawei.getType());
    System.out.println(huawei.getSize());
}

组合模式

对多个组件进行统一一样的操作

组合模式实际上就是将多个组件进行组合,让用户可以对它们进行一致性处理。比如我们的文件夹,一个文件夹中可以有很多个子文件夹或是文件。

它就像是一个树形结构一样,有分支有叶子,而组合模式则是可以对整个树形结构上的所有节点进行递归处理,比如我们现在希望将所有文件夹中的文件的名称前面都添加一个前缀,那么就可以使用组合模式。

image

组合模式的示例如下,这里我们就用文件和文件夹的例子来讲解:

/**
 * 首先创建一个组件抽象,组件可以包含组件,组件有自己的业务方法
 */
public abstract class Component {
    public abstract void addComponent(Component component);    //添加子组件
    public abstract void removeComponent(Component component);   //删除子组件
    public abstract Component getChild(int index);   //获取子组件
    public abstract void test();   //执行对应的业务方法,比如修改文件名称
}

接着我们来编写两种实现类:文件夹实现类,文件实现类

public class Directory extends Component{   //目录可以包含多个文件或目录

    List<Component> child = new ArrayList<>();   //这里我们使用List来存放目录中的子组件

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

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

    @Override
    public Component getChild(int index) {
        return child.get(index);
    }

    @Override
    public void test() {
        child.forEach(Component::test);   //将继续调用所有子组件的test方法执行业务
    }
}
public class File extends Component{   //文件就相当于是树叶,无法再继续添加子组件了

    @Override
    public void addComponent(Component component) {
        throw new UnsupportedOperationException();   //不支持这些操作了
    }

    @Override
    public void removeComponent(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int index) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void test() {
        System.out.println("文件名称修改成功!"+this);   //具体的名称修改操作
    }
}

最后,我们来测试一下:可以看到我们对最外层目录进行操作后,会递归向下处理当前目录和子目录中所有的文件

public static void main(String[] args) {
    Directory outer = new Directory();   //新建一个外层目录
    Directory inner = new Directory();   //新建一个内层目录
    outer.addComponent(inner);
    outer.addComponent(new File());   //在内层目录和外层目录都添加点文件,注意别导错包了
    inner.addComponent(new File());
    inner.addComponent(new File());
    outer.test();    //开始执行文件名称修改操作
}

装饰模式

通过B类 实现对A类方法执行前后,分别多执行一些操作。类似于AOP

适用:业务功能前后实现一些操作。如:在支付前提醒是否需要支付xxx元。

//顶层抽象类
public abstract class Base {   //顶层抽象类,定义了一个test方法执行业务
    public abstract void test();
}

//业务实现类
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是业务方法");   //具体的业务方法
    }
}

//装饰业务类(这里的构造方法参数是需要传入实现业务类对象)
public class Decorator extends Base{   //装饰者需要将装饰目标组合到类中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //这里暂时还是使用目标的原本方法实现
    }
}

//具体实现装饰业务类
public class DecoratorImpl extends Decorator{   //装饰实现

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //对原本的方法进行装饰,我们可以在前后都去添加额外操作
        System.out.println("装饰方法:我是操作前逻辑");
        super.test();
        System.out.println("装饰方法:我是操作后逻辑");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //将Base实现装饰一下
    Decorator outer = new DecoratorImpl(decorator);  //装饰者还可以嵌套,此时是装饰两次

    decorator.test();	//装饰一次:装饰前——业务方法——装饰后

    outer.test();	//装饰两次:装饰前——装饰前——业务方法——装饰后——装饰后
}

代理模式

和装饰模式代码一模一样,但核心是思想不同

装饰模式和代理模式:

  1. 结构相同:都实现同一个接口/抽象类
  2. 作用不同:
    • 装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能,增强后你还是你,只不过被强化了而已;
    • 代理模式强调要让别人帮你去做事情,以及添加一些本身与你业务没有太多关系的事情(记录日志、设置缓存等)重点在于让别人帮你做。

代理模式一般代码:

//顶层抽象类
public abstract class Base {   //顶层抽象类,定义了一个test方法执行业务
    public abstract void test();
}

//业务实现类
public class BaseImpl extends Base{
    @Override
    public void test() {
        System.out.println("我是业务方法");   //具体的业务方法
    }
}

//代理业务类(这里的构造方法参数是需要传入实现业务类对象)
public class Decorator extends Base{   //代理者需要将代理目标组合到类中

    protected Base base;

    public Decorator(Base base) {
        this.base = base;
    }

    @Override
    public void test() {
        base.test();    //这里暂时还是使用目标的原本方法实现
    }
}

//具体实现代理业务类
public class DecoratorImpl extends Decorator{   //代理实现

    public DecoratorImpl(Base base) {
        super(base);
    }

    @Override
    public void test() {    //对原本的方法进行代理,我们可以在前后都去添加额外操作
        System.out.println("装饰方法:我是操作前逻辑");
        super.test();
        System.out.println("装饰方法:我是操作后逻辑");
    }
}

//主方法
public static void main(String[] args) {
    Base base = new BaseImpl();
    Decorator decorator = new DecoratorImpl(base);  //将Base实现代理一下
    Decorator outer = new DecoratorImpl(decorator);  //代理者还可以嵌套,此时是代理两次

    decorator.test();	//代理一次:代理前——业务方法——代理后

    outer.test();	//代理两次:代理前——代理前——业务方法——代理后——代理后
}

实现代理模式除了和装饰模式一样的代码情况外还有两种实现方式:【因为都是动态代理所以生成的代理类是看不到的】

  1. JDK提供的动态代理:我们不再需要手动编写继承关系创建代理类,它能够在运行时通过反射机制为我们自动生成代理类:【只能代理接口】

    //接口
    public interface Subject {  //JDK提供的动态代理只支持接口
        void test();
    }
    
    //接口实现类
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是测试方法!");
        }
    }
    
    //创建动态代理的处理逻辑(就是执行业务前后的方法编写在里面)
    public class TestProxy implements InvocationHandler {    //代理类,需要实现InvocationHandler接口
    
        private final Object object;   //这里需要保存一下被代理的对象,下面需要用到
    
        public TestProxy(Object object) {
            this.object = object;
        }
    
        @Override   //此方法就是调用代理对象的对应方法时会进入,这里我们就需要编写如何进行代理了
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         		//method就是调用的代理对象的哪一个方法,args是实参数组
            System.out.println("代理的对象:"+proxy.getClass());   //proxy就是生成的代理对象了,我们看看是什么类型的
            Object res = method.invoke(object, args);   //在代理中调用被代理对象原本的方法,因为你是代理,还是得执行一下别人的业务,当然也可以不执行,但是这样就失去代理的意义了,注意要用上面的object
            System.out.println("方法调用完成,返回值为:"+res);   //看看返回值是什么
            return res;   //返回返回值
        }
    }
    
  2. Spring在使用的CGLib框架代理。

    maven依赖:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
    

    代码实现:

    //接口
    public interface Subject {  //JDK提供的动态代理只支持接口
        void test();
    }
    
    //接口实现类
    public class SubjectImpl implements Subject{
    
        @Override
        public void test() {
            System.out.println("我是测试方法!");
        }
    }
    
    //创建动态代理的处理逻辑(就是执行业务前后的方法编写在里面)
    public class TestProxy implements MethodInterceptor {  //首先还是编写我们的代理逻辑
    
        private final Object target;   //这些和之前JDK动态代理写法是一样的
    
        public TestProxy(Object target) {
            this.target = target;
        }
    
        @Override   //我们也是需要在这里去编写我们的代理逻辑
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("现在是由CGLib进行代理操作!"+o.getClass());
            return method.invoke(target, objects);   //也是直接调用代理对象的方法即可
        }
    }
    
    //主方法
    public static void main(String[] args) {
        SubjectImpl subject = new SubjectImpl();
    
        Enhancer enhancer = new Enhancer();   //增强器,一会就需要依靠增强器来为我们生成动态代理对象
        enhancer.setSuperclass(SubjectImpl.class);    //直接选择我们需要代理的类型,直接不需要接口或是抽象类,SuperClass作为代理类的父类存在,这样我们就可以按照指定类型的方式去操作代理类了
        enhancer.setCallback(new TestProxy(subject));  //设定我们刚刚编写好的代理逻辑
    
        SubjectImpl proxy = (SubjectImpl) enhancer.create();   //直接创建代理类
    
        proxy.test();   //调用代理类的test方法
    }
    

外观模式

可以理解为门面模式,将需要通过操作多个类实现的一个功能封装到一个类中,便于使用

当每个功能是一个系统,完成一个业务需要多个功能时就需要分别调用多个系统,此时就可以将一个业务需要使用的多个系统封装成一个门面系统,只要调用该门面系统即可完成该业务。

image

举例:比如现在我们设计了三个子系统,分别是排队、结婚、领证,正常情况下我们是需要分别去找这三个部门去完成的,但是现在我们通过门面统一来完成

//系统一
public class SubSystemA {
    public void test1(){
        System.out.println("排队");
    }
}

//系统二
public class SubSystemB {
    public void test2(){
        System.out.println("结婚");
    }
}

//系统三
public class SubSystemC {
    public void test3(){
        System.out.println("领证");
    }
}

//门面
public class Facade {

    SubSystemA a = new SubSystemA();
    SubSystemB b = new SubSystemB();
    SubSystemC c = new SubSystemC();

    public void marry(){   //红白喜事一条龙服务
        a.test1();
        b.test2();
        c.test3();
    }
}

//主方法
public static void main(String[] args) {
    Facade facade = new Facade();
    facade.marry();
}

享元模式

核心是共享。当A类方法里写了一个方法,B类中需要同样的方法就可以直接创建A类对象来调用方法或者通过一个方法工厂类收集各个方法,然后B类通过工厂类调用A类方法

举例:通过享元工厂类实现共享方法

//A类
public class DBUtil {
    public void selectDB(){
        System.out.println("我是数据库操作...");
    }
}

//享元工厂
public class DBUtilFactory {
    private static final DBUtil UTIL = new DBUtil();   //享元对象被存放在工厂中

    public static DBUtil getFlyweight(){   //获取享元对象
        return UTIL;
    }
}

//B类
public class UserService {   //用户服务

    public void service(){
        DBUtil util = DBUtilFactory.getFlyweight();   //通过享元工厂拿到DBUtil对象
        util.selectDB();    //该干嘛干嘛
    }
}

posted @ 2022-08-17 04:52  不吃紫菜  阅读(634)  评论(2编辑  收藏  举报