zoukankan      html  css  js  c++  java
  • 结构型模式之适配器模式、桥接模式与装饰器模式(一)

    一、基本介绍

      结构型模式(Structural Pattern)关注如何将现有类或对象组织在一起形成更加强大的结构。分为两种:1,类结构型模式:关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系;2,对象结构型模式:关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法,更符合“合成复用原则”。

      具体的结构型模式可分为:

    • 适配器模式(Adapter Pattern)
    • 桥接模式(Bridge)
    • 装饰者模式(Decorator)
    • 组合模式(Composite Pattern)
    • 外观模式(Facade)
    • 享元模式(Flyweight Pattern)
    • 代理模式(Proxy)

    二、适配器模式

    1,基本介绍

      适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要的目的是兼容性,让原本接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。

      适配器模式属于结构型模式,主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

    2,工作原理

    • 适配器模式:将一个类的接口转换成另一个种接口,让原本接口不兼容的类可以兼容
    •  从用户的角度看不到被适配者,是解耦的
    • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
    • 用户接收到反馈结果,感觉只是和目标接口交互

    3,类适配器模式

    a)类适配器模式介绍

      Adapter类,通过继承src类,实现dst类接口,完成src -> dst的适配。

    b)类适配器应用实例

     需求

      以生活中充电器为例,充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的目标dst(即目标)是5V直流电

     思路分析(类图)

        

     代码实现

    public class Voltage220V {
        public int output220v() {
            int src = 220;
            System.out.println("原始电压=" + src + "伏");
            return src;
        }
    }
    
    public interface IVoltage5V {
        public int output5v();
    }
    
    public class VoltageAdapter extends Voltage220V implements IVoltage5V {
        @Override
        public int output5v() {
            int v220 = output220v();
            return v220 / 44;
        }
    }
    
    public class Phone {
        public void chrging(IVoltage5V iVoltage5V) {
            if (iVoltage5V.output5v() == 5) {
                System.out.println("电压5V,可以充电");
            } else {
                System.out.println("电压" + iVoltage5V.output5v() + "V,无法充电");
            }
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            System.out.println("==========类适配器模式=========");
            Phone phone = new Phone();
            phone.chrging(new VoltageAdapter());
        }
    }

    c)注意事项与细节

    • 缺点:Java是单继承机制,所以类适配器需要继承src,故而dst必须是接口,有一定局限性
    • src类的方法在Adapter中都会暴露出来,也增加了使用的成本
    • 优点:由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了

    4,对象适配器模式

    a)对象适配器模式介绍

      基本思路和类的适配器模式相同,只是将Adapter类做修改,不再继承src类,而是持有src类的实例(聚合),以解决兼容性的问题。即:持有src类,实现dst类接口,完成src -> dst的适配。

      即根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。对象适配器模式是适配器模式中常用的一种

    b)对象适配器应用实例

     需求

      同类适配器

     思路分析(类图)

      

     代码实现

      除了VoltageAdapter类之外,其他的类与类适配器相同

    public class VoltageAdapter implements IVoltage5V {
    
        private Voltage220V voltage220V;
    
        public VoltageAdapter(Voltage220V voltage220V) {
            this.voltage220V = voltage220V;
        }
    
        @Override
        public int output5v() {
            int v220 = voltage220V.output220v();
            return v220 / 44;
        }
    }

    c)注意事项与细节

    • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。对象适配器根据合成复用原则,使用聚合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不在要求dst必须时接口。
    • 使用成本更低,更灵活

    5,接口适配器模式

    a)接口适配器模式介绍

    • 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。
    • 适用于一个接口不想使用其他所有的方法的情况

    b)接口适配器模式实例

     类图

      

     代码实现

    public interface InterfaceAdapter {
        public void m1();
        public void m2();
        public void m3();
        public void m4();
    }
    
    public abstract class AbsAdapter implements InterfaceAdapter {
        @Override
        public void m1() {}
    
        @Override
        public void m2() {}
    
        @Override
        public void m3() {}
    
        @Override
        public void m4() {}
    }
    
    public class Client {
        public static void main(String[] args) {
            AbsAdapter absAdapter = new AbsAdapter() {
                @Override
                public void m1() {
                    System.out.println("test absAdapter m1()");
                }
            };
            absAdapter.m1();
        }
    }

    6,适配器模式的注意事项和细节

    • 三种命名方式,是根据src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
    • 类适配器:以类给到Adapter里,也就是将src当做类,继承
    • 对象适配器:以对象给到Adapter里,也就是将src作为一个对象,持有
    • 接口适配器:以接口给到Adapter里,也就是将src作为一个接口,实现
    • Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作

    三、桥接模式

    1,手机操作问题

      现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网、打电话等),如图

      

     传统方式解决手机操作问题

     类图

      

     分析

    • 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
    • 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
    • 解决方案:桥接模式

    2,桥接模式

    a)基本介绍

      桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。它是一种结构型设计模式。

      Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。

    b)原理类图

      

     原理类图说明

    • Client类:桥接模式的调用者
    • 抽象类(Abstraction):维护了Implementor/即它的实现类ConcreteImplementorA/B,二者是聚合关系,Abstraction充当桥接
    • RefinedAbstraction:是Abstraction抽象类的子类
    • Implementor:行为实现类的接口
    • ConcreteImplementorA/B:行为的具体实现类
    • 从UML图:这里的抽象类和接口是聚合的关系,其实也是调用和被调用关系

    c)解决手机操作问题

     类图

      

     代码

    public interface Brand {
        void open();
    
        void call();
    
        void close();
    
    }
    
    public class Xiaomi implements Brand{
        @Override
        public void open() {
            System.out.println("小米手机开机");
        }
    
        @Override
        public void call() {
            System.out.println("小米手机打电话");
        }
    
        @Override
        public void close() {
            System.out.println("小米手机关机");
        }
    }
    
    public abstract class Phone {
        private Brand brand;
    
        public Phone(Brand brand) {
            this.brand = brand;
        }
    
        protected void open() {
            this.brand.open();
        }
    
        protected void call() {
            this.brand.call();
        }
    
        protected void close() {
            this.brand.close();
        }
    
    }
    
    public class FoldedPhone extends Phone{
    
        public FoldedPhone(Brand brand) {
            super(brand);
        }
    
        @Override
        public void open() {
            super.open();
            System.out.println("折叠样式手机");
        }
    
        @Override
        protected void call() {
            super.call();
            System.out.println("折叠样式手机");
        }
    
        @Override
        protected void close() {
            super.close();
            System.out.println("折叠样式手机");
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            FoldedPhone foldedPhone = new FoldedPhone(new Xiaomi());
            foldedPhone.open();
        }
    }

    3,注意事项

    • 实现了抽象和实现部分的分离,从而极大的提高了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统
    • 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它部分由具体业务来完成
    • 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
    • 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者对抽象进行设计和编程
    • 桥接模式要求正确识别出系统中两个独立变化的维度(抽象和实现),因此其使用范围有一定的局限性 

    4,常用的场景

    • JDBC驱动
    • 银行转账系统

      • 转账分类: 网上转账,柜台转账,AMT 转账
      • 转账用户类型:普通用户,银卡用户,金卡用户...
    • 消息管理
      • 消息类型:即时消息,延时消息
      • 消息分类:手机短信,邮件消息,QQ 消息...

    四、装饰者模式

    1,咖啡订单问题

    • 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
    • 调料:Milk、Soy(豆浆)、Chocolate
    • 要求在扩展性的咖啡种类时,具有良好的扩展性、改动方便、维护方便
    • 使用OO的方式来计算不同种类咖啡的费用:单品咖啡、单品+调料组合等

    2,解决方案

    a)方案一

     类图分析

      1)Drink 是一个抽象类,表示饮料

       -description 就是对咖啡的描述, 比如咖啡的名字

       -cost()方法就是计算费用,Drink 类中做成一个抽象方法.

      2)Decaf 就是单品咖啡,继承 Drink, 并实现了 cost()

      3)Espress && Milk 就是单品咖啡+调料, 这个组合很多

     问题:

      这样设计会有很多类,当我们增加一个单品咖啡,或者一个新的调料时,类的数量就会倍增,就会出现类爆炸

    b)方案二

     

     说明: milk,soy,chocolate 可以设计为 Boolean,表示是否要添加相应的调料.

     方案二问题分析

      1) 方案 2 可以控制类的数量,不至于造成很多的类

      2) 在增加或者删除调料种类时,代码的维护量很大

      3) 考虑到用户可以添加多份调料,可以将 hasMilk 返回一个对应 int

      4) 改进:考虑使用 装饰者 模式

    3,装饰者模式

    a)基本介绍

      装饰者模式(Decorator):动态地将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP)。

    b)原理

    •  装饰者模式就像打包一个快递
      • 主体:陶瓷、衣服(Component) //被装饰者
      • 包装:报纸填充、纸板、木板(Decorator)//装饰者
    • Component:主体,类似前面的Drink
    • ConcreteComponent:具体的主体,比如前面的各种咖啡
    • Decorator:装饰者,比如各种调料。装饰者里聚合了一个Component,也就是ConcreteComponent是可以放到装饰者里面的(装饰者里面可以包含被装饰者)
    • ConcreteDecorator:具体的装饰者,比如前面的各种调料

    c)完成咖啡订单问题

     uml类图

      代码

    //饮料父类
    public abstract class Drink {
        private String description;
    
        private float price = 0.0f;
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public float getPrice() {
            return price;
        }
    
        public void setPrice(float price) {
            this.price = price;
        }
    
        //消费由子类具体实现
        public abstract float cost();
    }
    
    //咖啡
    public class Coffee extends Drink{
        @Override
        public float cost() {
            return super.getPrice();
        }
    
        @Override
        public String getDescription() {
            return super.getDescription() + ":" + cost();
        }
    }
    //美式咖啡
    public class LongBlack extends Coffee{
        public LongBlack() {
            setDescription("美式...黑咖啡");
            setPrice(2.0f);
        }
    }
    //装饰类,调料
    public class Decorator extends Drink{
        Drink drink;
    
        public Decorator(Drink drink) {
            this.drink = drink;
        }
    
        @Override
        public float cost() {
            return drink.cost() + super.getPrice();
        }
    
        @Override
        public String getDescription() {
            return super.getDescription() + " " + super.getPrice() + " " + drink.getDescription();
        }
    }
    //具体的装饰调料,牛奶
    public class Milk extends Decorator{
        public Milk(Drink drink) {
            super(drink);
            setPrice(1.5f);
            setDescription("牛奶");
        }
    }
    
    //测试
    public class Client {
        public static void main(String[] args) {
            Drink order = new LongBlack();
            System.out.println("单品:"+order.getDescription()+" 价格:"+order.cost());
            //加一份巧克力
            order = new Chocolate(order);
            System.out.println("加一份巧克力:"+order.getDescription()+" 价格:"+order.cost());
            order = new Milk(order);
            System.out.println("加一份巧克力和一份牛奶:"+order.getDescription()+" 价格:"+order.cost());
        }
    }

    4,JDK中的应用实例

      Java的IO结构,FilterInputStream就是一个装饰者

    public abstract class InputStream implements Closeable {
      ......  //Component  
    }
    //Decorator抽象的装饰者 class FilterInputStream extends InputStream { //被装饰的对象 protected volatile InputStream in; ...... }
    //具体的装饰者 public class DataInputStream extends FilterInputStream implements DataInput { ...... }
    • InputStream时抽象类,类似前面的Drink(被装饰者)
    • FileInputStream是InputStream子类,类似前面的LongBlack、ShortBlack等单品咖啡
    • FIlterInputStream是InputStream子类,类似前面的Decorator装饰者
    • DataInputStream是FilterInputStream子类,具体的装饰者,类似前面的Milk、Chocolate等 
  • 相关阅读:
    position中的四种属性
    CSS中link和@import的区别
    隐藏对应元素的办法
    word20161217
    word20161216
    word20161215
    word20161214
    word20161213
    word201612012
    word20161211
  • 原文地址:https://www.cnblogs.com/bbgs-xc/p/15318758.html
Copyright © 2011-2022 走看看