结构型设计模式01-装饰模式

news2024/11/30 12:54:57

✨作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

装饰模式

1、 问题引入

要实现一个简单的个人形象系统,使用控制台输出的形式,简单说明搭配着装

Person

package com.shier.decorate;

/**
 * @author Shier
 * CreateTime 2023/4/16 22:30
 */
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void wearTShirts() {
        System.out.print("大T恤");
    }

    public void wearBigTrouser() {
        System.out.print("垮裤");
    }

    public void wearSneakers() {
        System.out.print("球鞋");
    }

    public void wearSuit() {
        System.out.print("西装");
    }

    public void wearTie() {
        System.out.print("领带");
    }

    public void wearLeatherShoes() {
        System.out.print("皮鞋");
    }

    public void wearBdk() {
        System.out.print("背带裤");
    }

    public void show() {
        System.out.println(name + "的装扮");
    }

}

测试类:

/**
 * @author Shier
 * CreateTime 2023/4/16 22:35
 */
public class DecorateTest {
    public static void main(String[] args) {
        Person person = new Person("Ikun");
        person.show();
        System.out.print("第一套装扮:");
        person.wearBigTrouser();
        person.wearBdk();

        System.out.println();
        System.out.print("第二套装扮:");
        person.wearTie();
        person.wearSuit();
        person.wearLeatherShoes();
    }
}

输出结果:

image-20230416224149866

问题来了:新的需求出现就要修改原来的Person类,这样就违背了开闭原则

2、 改进程序

修改后的程序结构图如下:

image-20230416224454204

将服饰抽象成一个类,让那么其他服饰类去继承这个服饰抽象类

Person类:

/**
 * @author Shier
 * CreateTime 2023/4/16 22:30
 */
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println(name + "的装扮");
    }
}

服饰抽象类:

/**
 * @author Shier
 * CreateTime 2023/4/16 22:46
 */
public abstract class Finery {
    public abstract void show();
}

Bdk 类

/**
 * @author Shier
 * CreateTime 2023/4/16 22:47
 */
public class Bdk extends Finery{
    @Override
    public void show() {
        System.out.print("背带裤");
    }
}

其他的服饰类一样,在此不多写了,都是重复的

客户端如下:

public class DecorateTest {
    public static void main(String[] args) {
        Person person = new Person("Ikun");
        person.show();
        System.out.print("第一套装扮:");
        Finery tShirts = new TShirts();
        Finery bigTrouser = new BigTrouser();
        Finery sneakers = new Sneakers();
        tShirts.show();
        bigTrouser.show();
        sneakers.show();
        System.out.println();
        System.out.print("第二套装扮:");
        Suit suit = new Suit();
        Bdk bdk = new Bdk();
        suit.show();
        bdk.show();
    }
}

输出的结构如下:

image-20230416225249019

如上的程序还是有问题的,什么问题呢?你看在客户端处都在调用很多的show方法,重复代码过多,最重要的是这些show执行的顺序。

那么我们就需要把所需的功能按正确的顺序串联起来进行控制 ,下面就用装饰模式去修改它

3、装饰模式

装饰模式(Decorator Pattern)是一种结构型设计模式。它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式是作为现有的类的一个包装,创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。装饰器模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。装饰器模式针对的问题是动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。装饰模式不改变接口的前提下,增强所考虑的类的性能。总之,装饰模式可以让我们动态地给一个对象添加一些额外的职责,而不需要在继承体系中创造许多子类。可以看做是一种对继承的补充或者替代方案。

3.1 装饰模式的结构图

image-20230416230107814

  • Component:定义一个对象接口,可以给这些对象动态地添加职责
  • ConcreteComponent:定义了一个具体的对象,也可以给这个对象添加一些职责
  • Decorator:装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无须知道Decorator的存在的
  • ConcreteDecorator:具体的装饰对象,起到给Component添加职责的功能

如果只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同样道理,如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

3.2 原理

装饰模式是利用SetComponent来对对象进行包装,每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。

在装饰模式中引入了装饰类,装饰类和被装饰类都要实现同一个接口或者继承同一个父类。具体来说,装饰类继承自抽象构件类,并且包含一个指向具体构件对象的引用,它包含了一个与抽象构件类相同的接口,即在装饰模式中,所有的装饰器都需要实现被装饰者实现的接口。因此客户端可以针对抽象构件角色进行编程,而不需要关心具体构件的类型。

装饰模式通过组合的方式,将装饰类和被装饰类进行组合,从而实现动态地添加新的功能。在运行时,装饰类动态地为被装饰类添加新的行为或者修改原有的行为,这样就可以在不修改现有代码的情况下,对需要修改的代码进行扩展和修改。这种方式比继承更加灵活和可控,避免了类爆炸问题,同时也实现了代码的复用。

3.3 使用装饰模式改进以上程序

再次修改的代码结构UML图如下:

image-20230416230740254

ICharactor接口(Component)

/**
 * @author Shier
 * CreateTime 2023/4/16 23:08
 * 人物形象接口
 */
public interface ICharacter {
    public void show();
}

Person类(ConcreteComponent)

/**
 * @author Shier
 * CreateTime 2023/4/16 22:30
 */
public class Person implements ICharacter{
    private String name;

    public Person(String name) {
        this.name = name;
    }
    public void show() {
        System.out.println(name + "的装扮");
    }
}

Finery类(Decorator):

/**
 * @author Shier
 * CreateTime 2023/4/16 22:46
 */
public  class Finery implements ICharacter {
    private ICharacter character;

    public void decorate(ICharacter iCharacter) {
        this.character = iCharacter;
    }

    public  void show(){
        if (this.character !=null){
            this.character.show();
        }
    }
}

具体服饰类(ConcreteDecorator):

/**
 * @author Shier
 * CreateTime 2023/4/16 22:47
 */
public class Bdk extends Finery {
    @Override
    public void show() {
        System.out.print("背带裤");
        super.show();
    }
}

每一个服饰类都一样的,不再过多写出来

测试类如下:

/**
 * @author Shier
 * CreateTime 2023/4/16 22:35
 */
public class DecorateTest {
    public static void main(String[] args) {
        Person kun = new Person("Ikun");
        System.out.print("第一套装扮:");
        // 穿球鞋的Ikun
        Sneakers sneakers = new Sneakers();
        sneakers.decorate(kun);
        // 在穿球鞋的Ikun基础上,再给他穿一条背带裤
        Bdk bdk = new Bdk();
        bdk.decorate(sneakers);
        bdk.show();
        System.out.println();
        System.out.print("第二套装扮:");
        // 穿西装的ikun
        Suit suit = new Suit();
        suit.decorate(kun);
        // 在穿西装的ikun基础上再传一个皮鞋
        LeatherShoes leatherShoes = new LeatherShoes();
        leatherShoes.decorate(suit);
        // 在穿西装和皮鞋的基础上带零领带
        Tie tie = new Tie();
        tie.decorate(leatherShoes);
        tie.show();

    }
}

测试结构如下:

image-20230416232052010

到此就实现了装饰模式去打扮ikun

如果还有新的需求过来,比如说ikun现在向买一个草帽带一下,只要增加一个草帽类,再去测试类当中给ikun穿上。ikun很满意

草帽类如下:

/**
 * @author Shier
 * CreateTime 2023/4/16 23:23
 */
public class Strawhat extends Finery {
    public void show() {
        System.out.print("草帽");
        super.show();
    }
}

测试类:

image-20230416232553668

测试结果如下:

image-20230416232513146

4、商场收银程序升级

装饰模式有一个重要的优点,把类中的装饰功能从类中搬移去除,这样可以简化原有的类。

所有商场系统的UML如改成如下:

image-20230416233049004

ISale接口(Component)

/**
 * @author Shier
 * CreateTime 2023/4/16 23:32
 */
public interface ISale {
    public double acceptCash(double price, int num);
}

CashSuper原来是抽象类,改成普通类,但实现ISale接口。

/**
 * @author Shier
 * CreateTime 2023/4/10 21:51
 */
public class CashSuper implements ISale {
    private ISale sale;

    // 装饰对象
    public void decorate(ISale iSale) {
        this.sale = iSale;
    }

    public double acceptCash(double price, int num) {
        double result = 0d;
        if (this.sale != null) {
            // 若装饰对象存在,则执行装饰对象的算法
            result = this.sale.acceptCash(price, num);
        }
        return result;
    }
}

CashNormal,相当于ConcreteComponent,是最基本的功能实现,也就是单价×数量的原价算法。

/**
 * @author Shier
 * CreateTime 2023/4/10 21:54
 *
 * 正常收费,原价返回
 */
public class CashNormal implements ISale {
    // 原价
    public double acceptCash(double price,int num){
        return price * num;
    }
}

另外两个CashSuper的子类算法,都在计算后,再增加一个super.acceptCash(result, 1)返回。

public class CashRebate extends CashSuper {
    private double moneyRebate = 1d;

    //初始化时必需输入折扣率。八折就输入0.8
    public CashRebate(double moneyRebate) {
        this.moneyRebate = moneyRebate;
    }

    //计算收费时需要在原价基础上乘以折扣率
    public double acceptCash(double price, int num) {
        double result = price * num * this.moneyRebate;
        return super.acceptCash(result, 1);
    }
}

image-20230416234052249

重点在CashContext类,因为涉及组合算法,所以用装饰模式的方式进行包装,这里需要注意包装的顺序,先打折后满多少返多少,与先满多少返多少,再打折会得到完全不同的结果。

/**
 * @author Shier
 * CreateTime 2023/4/11 22:55
 */
public class CashContext {
    /**
     * CashSuper对象
     */
    private ISale cashSuper;

    /**
     * 通过构造方法,传入具体的收费策略
     */
    public CashContext(int cashType) {
        //根据用户输入,将对应的策略对象作为参数传入CashContent对象中
        switch (cashType) {
            case 1:
                cashSuper = new CashNormal();
                break;
            case 2:
                cashSuper = new CashRebate(0.8d);
                break;
            case 3:
                cashSuper = new CashRebate(0.7d);
                break;
            case 4:
                cashSuper = new CashReturn(300d, 100d);
                break;
            case 5:
                // 先打8折,再满300反100
                CashNormal cn = new CashNormal();
                CashReturn cr = new CashReturn(300d, 100d);
                CashRebate cr2 = new CashRebate(0.8d);
                cr.decorate(cn);
                cr2.decorate(cr);
                this.cashSuper = cr2;
                break;
            case 6:
                // 先满200反50,再打七折
                CashNormal cn2 = new CashNormal();
                CashRebate cr3 = new CashRebate(0.7d);
                CashReturn cr4 = new CashReturn(200d, 40d);
                cr3.decorate(cn2);
                cr4.decorate(cr3);
                this.cashSuper = cr4;
                break;
            default:
                break;
        }
    }

    /**
     * 根据不同的收费策略返回不同的结构
     */
    public double getResult(double price, int num) {
        return cashSuper.acceptCash(price, num);
    }
}

客户端不用改变

最终测试结果如下:

image-20230416235135253

5、装饰模式总结

优点

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  3. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

适用环境:

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  3. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)

用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。

缺点:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

适用环境:

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  2. 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
  3. 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/572032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【快应用】响应式布局适配横竖屏或折叠屏

【关键词】 响应式布局、折叠屏、横竖屏 【问题背景】 当前开发者在开发快应用时,往往将designWidth设置为设备屏幕的宽度,这时,应用的内容会随着设备宽度的变大而拉伸显示,导致在大屏、横屏、折叠屏展开时显示效果不好。 在折…

PMP考试应该要如何备考?如何短期通过PMP?

我从新考纲考完下来,3A通过了考试,最开始也被折磨过一段时间,但是后面还是找到了方法,也算有点经验,给大家分享一下吧。 程序猿应该是考PMP里面人最多的,毕竟有一个30大坎,大部分人还是考虑转型…

微信小程序button按钮设置宽度无效

button按钮设置宽度无效 背景: 在开发小程序的过程中,遇到了button按钮设置宽度无效的问题 微信客户端 7.0 开始,UI 界面进行了大改版。小程序也进行了基础组件的样式升级,涉及的组件有 button,icon,radio,checkbox,switch,sli…

手把手教你在昇腾平台上搭建PyTorch训练环境

PyTorch是业界流行的深度学习框架,用于开发深度学习训练脚本,默认运行在CPU/GPU上。在昇腾AI处理器上运行PyTorch业务时,需要搭建异构计算架构CANN(Compute Architecture for Neural Networks)软件开发环境&#xff0c…

《花雕学AI》36:探索Aski AI——集成问答、写作和绘画功能的强大AI平台

引言:人工智能是当今时代的最热门和最有前途的技术之一,它可以帮助人类解决各种复杂和有趣的问题,提高生活和工作的效率和质量。然而,人工智能的应用还面临着许多挑战和局限,比如数据的稀缺和质量、算法的复杂性和可解…

CompletableFuture详解-初遇者-很细

目录 一、创建异步任务 1. supplyAsync 2. runAsync 3.获取任务结果的方法 二、异步回调处理 1.thenApply和thenApplyAsync 2.thenAccept和thenAcceptAsync 2.thenRun和thenRunAsync 3.whenComplete和whenCompleteAsync 4.handle和handleAsync 三、多任务组合处理 1…

Git的安装及基础命令

一. 安装Git 首先请前往Git官网去下载最新的安装包:https://git-scm.com/download/win 运行下载好的 .exe 文件,一路next即可。 右击桌面出现以下两个就算是成功。 安装完成后,需要设定用户名和邮箱来区分不同的用户。右击屏幕,选择“Git Bash Here”…

​Lambda表达式详解​-初遇者-很细

目录 Lambda简介 对接口的要求 Lambda 基础语法 Lambda 语法简化 Lambda 表达式常用示例 lambda 表达式引用方法 构造方法的引用 lambda 表达式创建线程 遍历集合 删除集合中的某个元素 集合内元素的排序 Lambda 表达式中的闭包问题 Lambda简介 Lambda 表达式是 JD…

骑行,为日益冷漠的人际关系加点温度

随着社会的发展和人们生活水平的提高,越来越多的年轻人、老年人和中年人开始关注健康和运动。而骑行作为一种健康、环保、经济实惠的运动方式,受到越来越多人的喜爱。本文将从社会面探讨这些话题对于不同人群的影响。 首先,骑行对身体有着多方…

狂飙,从功能测试转到自动化测试,我的测试之路涨了20k...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 Python自动化测试&…

《四》Git 中的远程仓库

SSH 登录: 每个远程仓库都有两种地址:HTTPS 和 SSH。如果是 HTTPS 的地址,每次 push 的时候都要输入用户名和密码以校验身份。如果 SSH 的方式,就不再需要每次都输入用户名和密码了。 cd ~ 进入用户的家目录,执行 ss…

ChatGPT在智能外呼机器人领域的应用

随着人工智能技术的不断发展,自然语言处理(NLP)技术也逐渐成为各行各业的热门技术。其中,ChatGPT技术是近年来备受关注的技术之一。ChatGPT技术是一种基于自然语言处理和深度学习的人工智能技术,它可以处理自然语言文本,实现自动化…

Maven 概述及下载安装

一、为什么要学习 Maven 我们构建一个项目需要用到很多第三方的类库,就需要引入大量的jar包,并且Jar包之间的关系错综复杂,缺少任何一个Jar包都会导致项目编译失败。Maven 能帮助我们下载及管理依赖。 本地项目代码开发完成后,我…

如何在华为OD机试中获得满分?Java实现【字母组合】一文详解

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

Android:如何从源码编译OpenCV4Android库

原文摘自知乎网友稚晖的文章《如何从源码编译OpenCV4Android库》 https://blog.csdn.net/LateLinux/article/details/111149544 我在这里根据自己的经验,增加一些备注。 1.需要的工具和源码: opencv4.1(opencv4.6也可以编译通过&#xff09…

跟随林曦,做自己的“生活家”

时代在以加速度的方式变化,让人难以从容。而当我们陷于横向的比较系统,权衡着卷、躺时,也有人在探寻另一条纵向的路——向古人学习,以传统美学关照和滋养当下生活。      立夏之际,水墨画家林曦的新作《无用之美》…

数据结构【链表】看完还怕拿不下链表?

✨Blog:🥰不会敲代码的小张:)🥰 🉑推荐专栏:C语言🤪、Cpp😶‍🌫️、数据结构初阶💀 💽座右铭:“記住,每一天都是一個新的開始&#x1…

推荐5款提高生活和工作效率的好帮手

在这个数字化时代,软件工具已经深深地影响和改变了我们的生活和工作。有着各种各样的软件工具,它们都可以在特定的领域内让我们变得更加高效,完成复杂的任务。选择一款适合你的软件工具,不但可以极大地释放生产力,也可以让生活变得更加便捷。 1.桌面图标管理工具——TileIconi…

阿里开源!集成了 AIGC 的免费数据库工具:Chat2DB

今天推荐的这个项目是「Chat2DB」,一款开源免费的数据库客户端工具,支持 Windows、Mac 本地安装,也支持服务器端部署,Web 网页访问。 和传统的数据库客户端软件 Navicat、DBeaver 相比 Chat2DB 集成了 AIGC 的能力,能…

基于LLMs的多模态大模型(PALM-E,ArtGPT-4,VPGTrans )

这个系列已经更文一些了,如果有新的文章会继续补充: 基于LLMs的多模态大模型(Visual ChatGPT,PICa,MM-REACT,MAGIC)基于LLMs的多模态大模型(Flamingo, BLIP-2,KOSMOS-1&…