聊聊装饰模式

news2024/11/26 4:51:25

缘起

某日,阳光明媚,绿草花香。Leader突然找到了小明:“小明,如果让你将一个人的穿着使用代码来实现,你该怎么完成呢?”

小明一听,回答道:“Leader,这个不难,马上就完事儿。”,于是第一版就出炉了。

Person类

public class Person {

    private String name;

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

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

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

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

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

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

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

}

客户端

Person x = new Person("小明");
System.out.println("第一种装扮:");
x.wearBigTrouser();
x.wearSneakers();
x.wearSuit();
x.show();


System.out.println("第二种装扮:");
x.wearTie();
x.wearBigTrouser();
x.wearTShirts();
x.show();

看完后,Leader又问道:“那如果我要增加新的装束呢?”

小明想了想:“改改Person类就行了,哎不对,这样就违背开放-封闭原则了,我再改下,应该是把这些装饰全都写成子类就好了”。

于是,第二版出炉

代码结构图

在这里插入图片描述

  • Person类
public class Person {

    private String name;

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

    public void show() {
        System.out.println("装扮的" + name);
    }
}
  • 服饰抽象类
public abstract class Finery {
    
    public abstract void show();
    
}
  • 各种服饰子类
public class TShirts extends Finery {
    @Override
    public void show() {
        System.out.println("T恤");
    }
}

public class BigTrouser extends Finery {
    @Override
    public void show() {
        System.out.println("垮裤");
    }
}

public class Sneakers extends Finery {
    @Override
    public void show() {
        System.out.println("垮裤");
    }
}
...

客户端

Person x = new Person("小明");
System.out.println("第一种装扮:");
Finery dtx = new TShirts();
Finery trouser = new BigTrouser();
Finery sneakers = new Sneakers();

dtx.show();
trouser.show();
sneakers.show();
x.show();

完成后,Leader看了下,问道:“你下面这段代码,各种show()调用不是显得很麻烦吗,我们可以在内部全部组装完毕后,再展示出来。但是要注意这跟建造者模式是不同的哦,建造者模式要求建造的过程必须是稳定的,而我们这个例子,穿衣的过程顺序可是不能够保证的,比如我先穿T恤,我还能先穿垮裤。方案都是不固定的。需要把所需的功能按正确的顺序串联起来进行控制,那么就需要用到装饰模式

装饰模式

装饰模式:动态给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类,更加灵活。

结构图

在这里插入图片描述

Component是定义一个对象接口,可以给这些对象动态添加职责.

Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的.

ConcreteDecorator:就是具体的装饰对象,起到给Component添加职责的功能

看下如何实现:

  • Component
public abstract class Component {
    
    public abstract void operation();
}
  • ConcreteComponent
public class ConcreteComponent extends Component {
    @Override
    public void operation() {
        System.out.println("具体对象操作");
    }
}
  • Decorator
public class Decorator extends Component {

    // 装饰一个component对象
    protected Component component;
    public void setComponent(Component component) {
        this.component = component;
    }
    @Override
    public void operation() {
        if (component != null) {
            component.operation();
        }
    }
}
  • ConcreteDecoratorA
public class ConcreteDecoratorA extends Decorator {

    private String addedState; // 本类独有字段,区别于B类

    @Override
    public void operation() {
        super.operation();
        this.addedState = "具体装饰对象A的独有操作";
        System.out.println(this.addedState);
    }
}
  • ConcreteDecoratorB
public class ConcreteDecoratorB extends Decorator {
    @Override
    public void operation() {
        super.operation();   // 先运行原有Component的功能
        this.addedBehavior();   // 运行本类独有功能
    }
    private void addedBehavior() {
        System.out.println("具体装饰对象B的独有操作");
    }
}
  • 客户端
ConcreteComponent c = new ConcreteComponent();
ConcreteDecoratorA d1 = new ConcreteDecoratorA();
ConcreteDecoratorB d2 = new ConcreteDecoratorB();

d1.setComponent(c);	// 先用d1包装c
d2.setComponent(d1); // 再用d2包装d1
d2.operation();	// 最终执行d2的operation()

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

如果只有一个ConcreteComponent类,没有Component抽象类,那么Decorator类是可以是ConcreteComponent类的一个子类。

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

小明第三版代码

  • ICharacter(Component)
// 人物形象接口
public interface ICharacter {
    void show();
}
  • Person类(ConcreteComponent)
public class Person implements ICharacter {

    private String name;

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

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

}
  • Finery类(Decorator)
public class Finery implements ICharacter {
    
    protected ICharacter component;

    public void setComponent(ICharacter component) {
        this.component = component;
    }

    @Override
    public void show() {
        if (this.component != null) {
            component.show();
        }
    }
}
  • 具体服饰类(ConcreteDecorator),其余代码省略
public class TShirts extends Finery {
    @Override
    public void show() {
        System.out.println("T恤");
        super.show();
    }
}
  • 客户端
Person x = new Person("小明");
System.out.println("装扮");

Sneakers sneakers = new Sneakers();		// 生成球鞋实例
TShirts tShirts = new TShirts();		// 生成T恤实例
BigTrouser bigTrouser = new BigTrouser();// 生成垮裤实例

sneakers.setComponent(x);			// 球鞋装饰小明
tShirts.setComponent(sneakers);		// T恤装饰有球鞋装饰的小明
bigTrouser.setComponent(tShirts);	// 垮裤装饰有球鞋和T恤装饰的小明

bigTrouser.show();	// 展示形象

Leader看后很满意:“你这样后面如果再增加新的服饰时,只需要这样”

public class Strawhat extends Finery {

    @Override
    public void show() {
        System.out.println("草帽");
        super.show();
    }
}

简单工厂+策略+装饰模式实现

Leader又问道:“还记得上次的商场收银功能吗,如果我再增加在打8折的基础上,再满300返100或者别的,改动要求尽量的小,你如何实现呢?”

小明想了想,开始码代码。增加一个新的算法类

public class CashReturnRebate extends CashSuper {

    private double moneyRebate = 1d;
    private double moneyCondition = 0d; // 返利条件
    private double moneyReturn = 0d;    // 返利值

    public CashReturnRebate(double moneyRebate, double moneyCondition, double moneyReturn) {
        this.moneyRebate = moneyRebate;
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double price, int num) {
        double result = price * num * this.moneyRebate;
        if (moneyCondition > 0 && result >= moneyCondition) {
            result = result - Math.floor(result / moneyCondition) * moneyReturn;
        }
        return result;
    }
}
  • 再去修改CashContext类,增加一个先打折再满减的算法实例对象。
public class CashContext {
    private CashSuper cs;

    // 通过构造方法传入收费策略
    public CashContext(int cashType) {
        CashSuper cs = null;
        switch (cashType) {
            case 1:
                this.cs = new CashNormal();//正常收费
                break;
            case 2:
                this.cs = new CashRebate(0.8d); // 八折
                break;
            case 3:
                this.cs = new CashRebate(0.7d); // 七折
                break;
            case 4:
                this.cs = new CashReturn(300d, 100d);    // 满300返100
                break;
            case 5:
                this.cs = new CashReturnRebate(0.8d, 300d, 100d);
                break;
        }
    }

    public CashContext(CashSuper cs) {
        this.cs = cs;
    }
    public double getResult(double price, int num) {
        return cs.acceptCash(price, num);
    }
}

Leader看了后,又问:“你有没有发现,你新增的CashReturnRebate类和CashReturn和CashRebate有大量重复代码呢?”

小明看后,:“确实是哎,等于是把这两个类的代码合并写了一遍,变成了CashReturnRebate类,等一下,刚说的装饰模式好像能解决这个问题。我先试下”

在这里插入图片描述

  • ISale
public interface ISale {
    double acceptCash(double price, double num);
}
  • CashSuper改成普通类,实现ISale接口
public class CashSuper implements ISale {
    
    protected ISale component;

    public void decorate(ISale component) {
        this.component = component;
    }

    @Override
    public double acceptCash(double price, double num) {
        
        double result = 0d;
        if (this.component != null) {
            result = this.component.acceptCash(price, num);
        }
        return result;
    }
}
  • CashNormal类扮演ConcreteComponent角色,也是最基本的功能实现,单价*数量的原价算法
public class CashNormal implements ISale {

    @Override
    public double acceptCash(double price, double 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;
    }

    @Override
    public double acceptCash(double price, double num) {
        double result = price * num * moneyRebate;
        return super.acceptCash(result, num);
    }
}
public class CashReturn extends CashSuper {

    private double moneyCondition = 0d; // 返利条件
    private double moneyReturn = 0d;    // 返回金额

    // 返利收费,初始化时必须输入返利条件和返利值
    // 如300返100,moneyCondition = 300 , moneyReturn = 100
    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double price, double num) {
        double result = price * num;
        if (moneyCondition > 0 && result >= moneyCondition) {
            result = result - Math.floor(result / moneyCondition) * moneyReturn;
        }
        return super.acceptCash(result, num);
    }
}

重点在CashContext类,因为涉及组合算法,所以用装饰模式的方式进行包装,这里需要注意包装的顺序,先打折后满多少返多少,和先满多少再打折多少是两种不同的意思。

public class CashContext {
    private ISale cs;

    // 通过构造方法传入收费策略
    public CashContext(int cashType) {
        CashSuper cs = null;
        switch (cashType) {
            case 1:
                this.cs = new CashNormal();//正常收费
                break;
            case 2:
                this.cs = new CashRebate(0.8d); // 八折
                break;
            case 3:
                this.cs = new CashRebate(0.7d); // 七折
                break;
            case 4:
                this.cs = new CashReturn(300d, 100d);    // 满300返100
                break;
            case 5:
                CashNormal cn = new CashNormal();
                CashReturn cr1 = new CashReturn(300d, 100d);
                CashRebate cr2 = new CashRebate(0.8d);

                cr1.decorate(cn);
                cr2.decorate(cr1);
                this.cs = cr2;
                break;
        }
    }

    public CashContext(CashSuper cs) {
        this.cs = cs;
    }
    public double getResult(double price, int num) {
        return cs.acceptCash(price, num);
    }
}

客户端算法依旧不用改变。

总结

起初的设计中,当系统需要新的功能时,再向旧的类中添加新的代码。这些新的代码通常是装饰了原有类的核心职责或主要行为。

在主类中加入新的字段或方法,增加了主类的复杂度。这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式提供了一个很好的方案,它把每个要装饰的功能放在单独的类中,并且让这个类包装它所要装饰的对象,因此当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择、按顺序的使用装饰功能包装对象了。

优点就是,将类中的装饰功能从类中移除,可以简化原有的类。

有效把类的核心职责和装饰功能都区分开,而且可以去除相关类中重复的装饰行为。

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

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

相关文章

【PyQt学习篇 · ④】:QWidget - 尺寸操作

文章目录 QWidget简介QWidget大小位置操作案例一案例二 QWidget尺寸限定操作案例 内容边距案例 QWidget简介 在PyQt中,QWidget是一个基本的用户界面类,用于创建可见的窗口组件。QWidget可以包含多种类型的子组件,如QPushButton、QLabel、QLi…

matlab 中的基本绘图指令与字符串操作指令

字符串指令 创建字符串 使用单引号将字符序列括起来创建字符串使用单引号创建的字符串是一个字符数组,每个字符都被视为一个独立的元素 可以通过索引访问每个字符使用双引号创建的字符串是一个字符串数组,整个字符串被视为一个元素 无法通过索引访问单个…

Linux shell编程学习笔记17:for循环语句

Linux Shell 脚本编程和其他编程语言一样,支持算数、关系、布尔、字符串、文件测试等多种运算,同样也需要进行根据条件进行流程控制,提供了if、for、while、until等语句。 之前我们探讨了if语句,现在我们来探讨for循环语句。 Li…

海外问卷调查是怎么做的?全方位介绍!

橙河这样说,相信大家应该不难理解。 国外问卷调查目前主要有三种形式:口子查、站点查和渠道查。橙河自己做的是渠道查。 站点查是最早的问卷形式,意思是我们需要登录到问卷网站上,就可以做问卷了。但想要在网站上做问卷&#xf…

YOLO轻量化改进 , 边缘GPU友好的YOLO改进算法!

在本文中,作者根据现有先进方法中各种特征尺度之间缺少的组合连接的问题,提出了一种新的边缘GPU友好模块,用于多尺度特征交互。此外,作者提出了一种新的迁移学习backbone采用的灵感是来自不同任务的转换信息流的变化,旨…

《Attention Is All You Need》阅读笔记

论文标题 《Attention Is All You Need》 XXX Is All You Need 已经成一个梗了,现在出现了很多叫 XXX Is All You Need 的文章,简直标题党啊,也不写方法,也不写结果,有点理解老师扣论文题目了。 作者 这个作者栏太…

Yolo-Z:改进的YOLOv5用于小目标检测

目录 一、前言 二、背景 三、新思路 四、实验分析 论文地址:2112.11798.pdf (arxiv.org) 一、前言 随着自动驾驶汽车和自动驾驶赛车越来越受欢迎,对更快、更准确的检测器的需求也在增加。 虽然我们的肉眼几乎可以立即提取上下文信息,即…

Arhas 常用命令

watch 函数执行数据观测: location 会有三种值 AtEnter,AtExit,AtExceptionExit。 对应函数入口,函数正常 return,函数抛出异常。 result 表示观察表达式的值: {params,returnObj,throwExp} eg: 查看是某个方法的参…

探索Apache HttpClient超时时间如何设定?

目录 一、Apache HttpClient模拟POST请求,调用第三方接口1、发起POST请求:2、模拟服务端3、通过postman测试一下4、Apache HttpClient 二、HTTP超时时间1、众所周知,HTTP使用的是TCP/IP 协议。2、TCP/IP超时时间设置3、HTTP连接超时时间如何设…

笔记Kubernetes核心技术-之Controller

2、Controller 2.1、概述 在集群上管理和运行容器的对象,控制器(也称为:工作负载),Controller实际存在的,Pod是抽象的; 2.2、Pod和Controller关系 Pod是通过Controller实现应用运维,比如:弹…

前馈神经网络处理二分类任务

此文建议看完基础篇再来,废话不多说,进入正题 目录 1.神经元 1.1 活性值 1.2 激活函数 1.2.1 Sigmoid函数 1.2.2 Relu函数 2.基于前馈神经网络的二分类任务 2.1 数据集的构建 2.2 模型的构建 2.2.1 线性层算子 2.2.2 Logistic算子 2.2.3 层的串行组合…

FL Studio 21.2.0.3842中文破解版发布啦,支持 Cloud 在线采样库和 AI 音乐制作功能

好消息!FL Studio 21.2 在 10 月 26 日正式发布啦,它新增了 FL Cloud 在线采样库和 AI 音乐制作功能,还提供音乐分发到 Spotify、Apple Music 等主要音乐平台的服务。此外,还有新的音频分离功能、自定义波形颜色和新的合成器 Kepl…

改进YOLOv3!IA-YOLO:恶劣天气下的目标检测

恶劣天气条件下从低质量图像中定位目标还是极具挑战性的任务。现有的方法要么难以平衡图像增强和目标检测任务,要么往往忽略有利于检测的潜在信息。本文提出了一种新的图像自适应YOLO (IA-YOLO)框架,可以对每张图像进行自适应增强,以提高检测…

Windows一键添加命名后缀(文件)

温馨提示:使用前建议先进行测试和原文件备份,避免引起不必要的损失。 (一)需求描述 之前老板让我给大量文件添加命名前缀,如今为了防患于未然,我决定把添加命名后缀的功能也实现一下,虽然这与添…

EASYX键盘交互

eg1:使用键盘的上下左右按钮控制小球的上下左右移动 #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #include <conio.h> #define PI 3.14int main() {// 键盘交互initgraph(800, 600);setorigin(400, 300);set…

Linux网卡

网卡 网卡&#xff08;Network Interface Card&#xff0c;NIC&#xff09;是一种计算机硬件设备&#xff0c;也称为网络适配器或网络接口控制器。一个网卡就是一个接口 网卡组成和工作原理参考https://blog.csdn.net/tao546377318/article/details/51602298 每个网卡都拥有唯…

Mac删除照片快捷键ctrl加什么 Mac电脑如何批量删除照片

Mac电脑是很多人喜欢使用的电脑&#xff0c;它有着优美的设计、高效的性能和丰富的功能。如果你的Mac电脑上存储了很多不需要的照片&#xff0c;那么你可能会想要删除它们&#xff0c;以节省空间和提高速度。那么&#xff0c;Mac删除照片快捷键ctrl加什么呢&#xff1f;Mac电脑…

双目视觉计算三维坐标

一、原理 双目视觉的基本原理&#xff0c;以及公式推导&#xff0c;我参考的b站上的视频&#xff0c;链接如下&#xff1a; 2-线性相机模型-Linear Camera Model-Camera Calibration_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Q34y1n7ot/?p2&spm_id_from333.…

链表加法与节点交换:数据结构的基础技能

目录 两两交换链表中的节点单链表加一链表加法使用栈实现使用链表反转实现 两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点…

13年测试老鸟,性能压测-死锁定位分析/内存溢出实例(超详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 死锁问题定位与分…