zoukankan      html  css  js  c++  java
  • 设计模式:装饰器模式

    装饰器模式概述

    装饰器模式,也称为包装器模式,指在不改变原有对象的基础上,动态给一个对象添加一个额外的职责。

    使用场景:

    • 用于扩展一个类的功能,或者给一个类添加职责
    • 动态给一个对象添加功能,这些功能可以动态地被撤销
    • 需要为一些平行的兄弟类改装功能

    场景一

    星巴克里面卖多种饮料,拿铁,咖啡,每一种饮料都有自己的价格。

    为解决需求,设计一个饮料抽象类:

    public abstract class Beverage {
    
        private String name;
        
        private int cost;
    
        public void setName() {
            this.name = "饮料";
        }
    
        String getName() {
            return name;
        }
    
        Beverage(String name) {
            this.name = name;
        }
    
        //价格
        public int cost(){
            return cost;
        }
    }
    

    具体饮料:拿铁和咖啡继承饮料抽象

    public class Coffee extends Beverage{
    
        public Coffee() {
            super("咖啡");
        }
    
        @Override
        public int cost() {
            return 10;
        }
    }
    
    public class Latte extends Beverage{
    
        public Latte() {
            super("拿铁");
        }
    
        @Override
        public int cost() {
            return 12;
        }
    }
    

    测试:

        public static void main(String[] args) {
            Beverage beverage = new Coffee();
            Beverage beverage2 = new Latte();
    
            System.out.println(beverage.getName()+" : "+beverage.cost());
            System.out.println(beverage2.getName()+" : "+beverage2.cost());
        }
    

    image-20201226202630096

    场景二

    现在需求变化了,现在星巴克不仅有拿铁,咖啡,可能在拿铁和咖啡中加入巧克力,糖,坚果,花生等调料,并且每份调料分别计算价格。

    那我们可以创建出 巧克力拿铁糖拿铁坚果拿铁花生拿铁巧克力糖拿铁巧克力花生拿铁等等类,会造成类爆炸的情况,996程序员也写不完,007也难,并且每加一种调料,工作量也是成倍增加。

    所以我们改进写法:

    把每一份调料作为成员变量放入饮料中。修改饮料类:

    public abstract class Beverage {
    
        private boolean chocolate = false;//巧克力
    
        private boolean sugar  = false;//糖
    
        private boolean nut  = false;//坚果
    
        private boolean peanut  = false;//花生
    
        private String name;
    
        private int cost;
    
        public void setName() {
            this.name = "饮料";
    
        }
    
        String getName() {
            if(this.chocolate){
                name = name + "+巧克力";
            }
            if(this.sugar){
                name = name + "+糖";
            }
            if(this.nut){
                name = name + "+坚果";
            }
            if(this.peanut){
                name = name + "+花生";
            }
            return name;
        }
    
        Beverage(String name) {
            this.name = name;
        }
    
        //价格
        public int cost(){
            if(this.chocolate){
                cost = cost + 2;
            }
            if(this.sugar){
                cost = cost + 3;
            }
            if(this.nut){
                cost = cost + 4;
            }
            if(this.peanut){
                cost = cost + 5;
            }
            return cost;
        }
    
        public void setChocolate(boolean chocolate) {
            this.chocolate = chocolate;
        }
    
        public void setSugar(boolean sugar) {
            this.sugar = sugar;
        }
    
        public void setNut(boolean nut) {
            this.nut = nut;
        }
    
        public void setPeanut(boolean peanut) {
            this.peanut = peanut;
        }
    }
    

    以咖啡类为例,修改cost方法:

    public class Coffee extends Beverage {
    
        public Coffee() {
            super("咖啡");
        }
    
        @Override
        public int cost() {
            return 10 + super.cost();
        }
    }
    

    测试:

        public static void main(String[] args) {
            Beverage beverage = new Coffee();
            beverage.setChocolate(true);
            beverage.setNut(true);
    
            System.out.println(beverage.getName()+" : "+beverage.cost());
        }
    

    image-20201226201718060

    需求我们完美实现了,饮料类中加多份调料,并计算出价格。

    场景三

    • 业务一:随着星巴克业务的变化和客户的需求,需要加入奶油这一调料。我们这时候势必需要修改饮料类的cost方法和getName方法,而我们作为客户端是不能修改代码的,这违反了开闭原则。
    • 业务二:有的客户需要在咖啡中加入两份巧克力,两份巧克力总不能和一份的一样价钱吧!而我们写的成员变量作为布尔值,无法体现出同一调料的数量,势必需要改变源代码以适应新的需求,又违反开闭原则。

    这时候,救世主“装饰器模式”诞生了!

    重新修改饮料类,不再需要各种调料判断:

    public abstract class Beverage {
    
        private String name;
    
        private int cost;
    
        public void setName() {
            this.name = "饮料";
    
        }
        String getName() {
    
            return name;
        }
    
        Beverage(String name) {
            this.name = name;
        }
    
        //价格
        public abstract int cost();
    }
    

    并抽象出调料类:

    //调料
    public abstract class Seasoning extends Beverage{//1.继承Beverage
    
        protected Beverage beverage;//2.需要有一个Beverage的成员变量
    
        Seasoning(Beverage beverage) {//3.构造器需要依赖Beverage父类
            super("调料");
            this.beverage = beverage;
        }
    }
    

    写两个调料的实现类:巧克力和糖(花生类和坚果类不写了)

    public class Chocolate extends Seasoning{
    
        private int cost = 2;
    
        Chocolate(Beverage beverage){
            super(beverage);
        }
    
        @Override
        public int cost() {
            return beverage.cost() + this.cost;
        }
    
        @Override
        String getName() {
            return beverage.getName() + "+巧克力";
        }
    }
    
    public class Sugar extends Seasoning{
    
        private int cost = 3;
    
        Sugar(Beverage beverage) {
            super(beverage);
        }
    
        @Override
        public int cost() {
            return beverage.cost() + this.cost;
        }
    
        @Override
        String getName() {
            return beverage.getName() + "+糖";
        }
    }
    

    测试:

        public static void main(String[] args) {
            Beverage beverage ;
            beverage  = new Coffee();
    
            beverage = new Sugar(beverage);//这里一层装饰一层(包粽子一样)
            beverage = new Chocolate(beverage);
            beverage = new Sugar(beverage);
            beverage = new Chocolate(beverage);
            beverage = new Chocolate(beverage);
    
            System.out.println(beverage.getName()+" : "+beverage.cost());
        }
    

    测试结果:

    image-20201226204939970

    这里,我们实现了上述需求。以后无论增加什么调料或者饮料,我们都只需要继承Seasoning或者Beverage,调料如何加,加几份,我们也能实现,并且无需修改源码。上述就是装饰器模式的具体实现

    装饰器模式可以灵活扩展,但所有类都来自同一个祖宗Beverage。

    uml类图:

    image-20201226205528209

    装饰器模式的通用写法

    //抽象组件
    public abstract class Component {
        public abstract void operation();
    }
    
    //装饰器抽象
    public abstract class Decorator extends Component{
    
        protected Component component;
    
        public Decorator(Component component) {
            this.component = component;
        }
    
        public void operation(){
            component.operation();
        }
    }
    
    //具体组件
    public class ConcreteComponent extends Component{
        @Override
        public void operation() {
            System.out.println("ConcreteComponent operation");
        }
    }
    
    //具体装饰器
    public class ConcreteDecorator extends Decorator{
        public ConcreteDecorator(Component component) {
            super(component);
        }
    
        public void operation(){
            System.out.println("first");
            super.operation();
            System.out.println("last");
        }
    }
    
    

    测试:

        public static void main(String[] args) {
            Component component = new ConcreteDecorator(new ConcreteComponent());
            component.operation();
        }
    

    jdk中的装饰器模式

    jdk中的io相关的流所用的就是最明显的装饰器模式。InputStream就是我们上述的饮料,而FilterInputStream就是我们上述的调料。

    image-20201226210535458

  • 相关阅读:
    显卡信息
    统一处理labelme标注的json文件名
    Qt窗口设置最大高度
    未定义av_image_get_buffer_size
    AVPixelFormat
    树结构系列开篇:聊聊如何学习树结构?
    PriorityQueue 是线性结构吗?90%的人都搞错了!
    硬核!史上最全的工厂模式文章,从零到一全面讲解!
    如何从分类层面,深入理解设计模式?
    程序员重构入门指南
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/14194211.html
Copyright © 2011-2022 走看看