设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
21266 人已学习
课程目录
已更新 65 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (12讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?
23 | 实战一(上):针对业务系统的开发,如何做需求分析和设计?
24 | 实战一(下):如何实现一个遵从设计原则的积分兑换系统?
25 | 实战二(上):针对非业务的通用框架开发,如何做需求分析和设计?
26 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?
设计原则与思想:规范与重构 (11讲)
27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?
28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?
29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?
30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?
31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)
32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)
33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)
34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题
35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”
36 | 实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?
37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码
设计原则与思想:总结课 (3讲)
38 | 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点
39 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(上)
40 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(下)
设计模式与范式:创建型 (7讲)
41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?
42 | 单例模式(中):我为什么不推荐使用单例模式?又有何替代方案?
43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模式?
44 | 工厂模式(上):我为什么说没事不要随便用工厂模式创建对象?
45 | 工厂模式(下):如何设计实现一个Dependency Injection框架?
46 | 建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式
47 | 原型模式:如何最快速地clone一个HashMap散列表?
设计模式与范式:结构型 (8讲)
48 | 代理模式:代理在RPC、缓存、监控等场景中的应用
49 | 桥接模式:如何实现支持不同类型和渠道的消息推送系统?
50 | 装饰器模式:通过剖析Java IO类库源码学习装饰器模式
51 | 适配器模式:代理、适配器、桥接、装饰,这四个模式有何区别?
52 | 门面模式:如何设计合理的接口粒度以兼顾接口的易用性和通用性?
53 | 组合模式:如何设计实现支持递归遍历的文件系统目录树结构?
54 | 享元模式(上):如何利用享元模式优化文本编辑器的内存占用?
55 | 享元模式(下):剖析享元模式在Java Integer、String中的应用
设计模式与范式:行为型 (6讲)
56 | 观察者模式(上):详解各种应用场景下观察者模式的不同实现方式
57 | 观察者模式(下):如何实现一个异步非阻塞的EventBus框架?
58 | 模板模式(上):剖析模板模式在JDK、Servlet、JUnit等中的应用
59 | 模板模式(下):模板模式与Callback回调函数有何区别和联系?
60 | 策略模式(上):如何避免冗长的if-else/switch分支判断代码?
61 | 策略模式(下):如何实现一个支持给不同大小文件排序的小程序?
不定期加餐 (3讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
春节特别加餐 | 王争:如何学习《设计模式之美》专栏?
免费
设计模式之美
登录|注册

50 | 装饰器模式:通过剖析Java IO类库源码学习装饰器模式

王争 2020-02-26
上一节课我们学习了桥接模式,桥接模式有两种理解方式。第一种理解方式是“将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。另一种理解方式更加简单,类似“组合优于继承”设计原则,这种理解方式更加通用,应用场景比较多。不管是哪种理解方式,它们的代码结构都是相同的,都是一种类之间的组合关系。
今天,我们通过剖析 Java IO 类的设计思想,再学习一种新的结构型模式,装饰器模式。它的代码结构跟桥接模式非常相似,不过,要解决的问题却大不相同。
话不多说,让我们正式开始今天的学习吧!

Java IO 类的“奇怪”用法

Java IO 类库非常庞大和复杂,有几十个类,负责 IO 数据的读取和写入。如果对 Java IO 类做一下分类,我们可以从下面两个维度将它划分为四类。具体如下所示:
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类。具体如下所示:
在我初学 Java 的时候,曾经对 Java IO 的一些用法产生过很大疑惑,比如下面这样一段代码。我们打开文件 test.txt,从中读取数据。其中,InputStream 是一个抽象类,FileInputStream 是专门用来读取文件流的子类。BufferedInputStream 是一个支持带缓存功能的数据读取类,可以提高数据读取的效率。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(47)

  • 下雨天
    你是一个优秀的歌手,只会唱歌这一件事,不擅长找演唱机会,谈价钱,搭台,这些事情你可以找一个经纪人帮你搞定,经纪人帮你做好这些事情你就可以安稳的唱歌了,让经纪人做你不关心的事情这叫代理模式。
    你老爱记错歌词,歌迷和媒体经常吐槽你没有认真对待演唱会,于是你想了一个办法,买个高端耳机,边唱边提醒你歌词,让你摆脱了忘歌词的诟病,高端耳机让你唱歌能力增强,提高了基础能力这叫装饰者模式。
    2020-02-26
    8
    73
  • 小晏子
    对于添加缓存这个应用场景使用哪种模式,要看设计者的意图,如果设计者不需要用户关注是否使用缓存功能,要隐藏实现细节,也就是说用户只能看到和使用代理类,那么就使用proxy模式;反之,如果设计者需要用户自己决定是否使用缓存的功能,需要用户自己新建原始对象并动态添加缓存功能,那么就使用decorator模式。
    2020-02-26
    1
    50
  • 守拙
    补充关于Proxy Pattern 和Decorator Pattern的一点区别:

    Decorator关注为对象动态的添加功能, Proxy关注对象的信息隐藏及访问控制.
    Decorator体现多态性, Proxy体现封装性.

    reference:
    https://stackoverflow.com/questions/18618779/differences-between-proxy-and-decorator-pattern
    2020-02-26
    3
    19
  • Jxin
    今天的课后题:
    1.有意思,关于代理模式和装饰者模式,各自应用场景和区别刚好也想过。

    1.代理模式和装饰者模式都是 代码增强这一件事的落地方案。前者个人认为偏重业务无关,高度抽象,和稳定性较高的场景(性能其实可以抛开不谈)。后者偏重业务相关,定制化诉求高,改动较频繁的场景。

    2.缓存这件事一般都是高度抽象,全业务通用,基本不会改动的东西,所以一般也是采用代理模式,让业务开发从缓存代码的重复劳动中解放出来。但如果当前业务的缓存实现需要特殊化定制,需要揉入业务属性,那么就该采用装饰者模式。因为其定制性强,其他业务也用不着,而且业务是频繁变动的,所以改动的可能也大,相对于动代,装饰者在调整(修改和重组)代码这件事上显得更灵活。
    2020-02-26
    4
    16
  • rammelzzz
    对于无需Override的方法也要重写的理解:
    虽然本身BufferedInputStream也是一个InputStream,但是实际上它本身不作为任何io通道的输入流,而传递进来的委托对象InputStream才能真正从某个“文件”(广义的文件,磁盘、网络等)读取数据的输入流。因此必须默认进行委托。
    2020-02-26
    13
  • Yo nací para quererte.
    对于为什么中间要多继承一个FilterInputStream类,我的理解是这样的:
    假如说BufferedInputStream类直接继承自InputStream类且没有进行重写,只进行了装饰
    创建一个InputStream is = new BufferedInputStream(new FileInputStream(FilePath));
    此时调用is的没有重写方法(如read方法)时调用的是InputStream类中的read方法,而不是FileInputStream中的read方法,这样的结果不是我们想要的。所以要将方法再包装一次,从而有FilterInputStream类,也是避免代码的重复,多个装饰器只用写一遍包装代码即可。
    2020-02-26
    6
  • iLeGeND

    // 代理模式的代码结构(下面的接口也可以替换成抽象类)
    public interface IA {
      void f();
    }
    public class A impelements IA {
      public void f() { //... }
    }
    public class AProxy impements IA {
      private IA a;
      public AProxy(IA a) {
        this.a = a;
      }
      
      public void f() {
        // 新添加的代理逻辑
        a.f();
        // 新添加的代理逻辑
      }
    }

    // 装饰器模式的代码结构(下面的接口也可以替换成抽象类)
    public interface IA {
      void f();
    }
    public class A impelements IA {
      public void f() { //... }
    }
    public class ADecorator impements IA {
      private IA a;
      public ADecorator(IA a) {
        this.a = a;
      }
      
      public void f() {
        // 功能增强代码
        a.f();
        // 功能增强代码
      }
    }

    老师 上面代码结构完全一样啊 不能因为 f() 中写的 逻辑不同 就说是两种模式吧
    2020-02-26
    1
    4
  • 唐朝农民
    订单的优惠有很多种,比如满减,领券这样的是不是可以使用decorator 模式来实现
    2020-02-26
    4
  • 李小四
    设计模式_50:
    # 作业

    正如文中所说,装饰器是对原有功能的扩展,代理是增加并不相关的功能。
    所以问题就变成使用者认为“缓存”是否扩展了原功能
    - 比如说需要把想把所有的网络信息都加上缓存,提高一些查询效率,这时候应该使用代理模式;
    - 如果我在设计网络通信框架,需要把提供“缓存”作为一种扩展能力,这时应该用装饰器模式;

    现实中,大部分的网络缓存都以代理模式被实现。

    另外,缓存(Cache)与缓冲(Buffer)是不同的概念,这里也可以区分一下。

    # 感受
    到了具体模式的课程,有一个明显的特点:一句话感觉看懂了,反复读才能发现有更多的信息在里面,坦白讲,很多模式编程中没有用过,与单纯地读原理和特征相比,我想真正用的时候才能理解更深入的东西。
    2020-03-07
    3
  • 岁月神偷
    我觉得应该用代理模式,当然这个是要看场景的。代理模式是在原有功能之外增加了其他的能力,而装饰器模式则在原功能的基础上增加额外的能力。一个是增加,一个是增强,就好比一个是在手机上增加了一个摄像头用于拍照,而另一个则是在拍照这个功能的基础上把像素从800W提升到1600W。我觉得通过这样的方式区分的话,大家互相沟通起来理解会统一一些。
    2020-02-27
    3
  • 嘿泥鳅
    添加缓存这个功能与父类或者接口定义的功能无关应该使用代理模式。
    2020-03-01
    2
  • Frank
    打卡 设计模式-装饰器模式
    装饰器模式是一种类似于代理模式的结构型模式。主要意图是增强原始类的功能,可以实现多个功能的增强(即不同的功能单独一个类维护,使用该模式将其功能组合起来)。该模式主要是为了解决为了实现某些功能导致子类膨胀的问题。个人觉得主要体现了单一职责、组合优先于继承原则。主要应用场景有Java IO 流设计。但是有个疑惑,在Reader和Writer体系结构的设计中,并没有像InputStream和OutputStream那样设计一个过滤流类,而BufferedReader等直接继承了Reader。按照作者本文的分析,字符输入流直接跳过了使用中间类来继承的步骤,这样的设计又该如何理解?
    对于课堂讨论,我觉得应该使用装饰器模式,因为“添加缓存”这个功能跟原始功能是由直接关系的。而代理模式所面向主要是将框架代码与业务代码解耦合。
    2020-02-27
    2
  • 守拙
    如果仅添加缓存一个功能, 使用Proxy好一些, 如果还有其他需求, 使用Decorator好一些.
    如果让我选择的话, 我宁愿选择Decorator, 方便扩展, 符合开闭原则.
    2020-02-26
    2
  • 忆水寒
    如果只是需要对所有 对象的缓存功能进行增强(相当于缓存是新的功能了),则可以使用代理模式。
    如果只是对某一类对象进行增强,而这类对象有共同的接口或父类,则可以使用装饰模式。
    2020-02-26
    2
  • 木头
    看业务场景,如果只是针对某个类的某一个对象,添加缓存,那么就使用装饰模式。如果是针对这个类的所有对象,添加缓存。那么就使用代理模式
    2020-02-26
    2
  • 松花皮蛋me
    通过将原始类以组合的方式注入到装饰器类中,以增强原始类的功能,而不是使用继承,避免维护复杂的继承关系。另外,装饰器类通常和原始类实现相同的接口,如果方法不需要增强,重新调用原始类的方法即可。
    2020-02-26
    2
  • Vivian
    代理模式:用户不用关心和参与具体的实现细节,只想拿到增强后的最终结果,相对于用户是静态的增强。
    装饰器模式:装饰器类和原始类都需要继承相同的类或者实现相同的接口,用户可以根据自己的配置决定使用怎样的增强功能,相对于用户是动态的增强。
    🌰:对于“添加缓存”这个场景,如果需要添加的缓存是固定的,变化不多,可以用代理模式;如果缓存有多种类型且配置多变,应该采用装饰器模式。比如 mybatis 的二级缓存的配置,可以配置缓存的有效时长,缓存的淘汰策略:LRU/FIFO,是否只读等。
    2020-03-17
    1
  • Richie
    代理模式和装饰器模式最主要的不同是意图不同。其次使用方式也不同。
    课堂讨论题中,我们给接口添加缓存功能的时候,使用的是代理模式;给InputStream添加缓存读取功能的时候,则使用的是装饰器模式。
    前者实际上是由框架处理的,也就是把缓存的事情代理给框架去处理了;而后者则还是由应用自己处理,应用调用方得到的是一个增强过的类。
    2020-03-16
    1
  • Geek_54edc1
    用代理模式,因为:1、可以动态代理 2、装饰器支持嵌套,但是缓存场景也用不上嵌套,如果支持嵌套,有点过度设计了
    2020-03-12
    1
  • 石仔
    缓存场景
    代理模式:可以很好的做到解耦用户代码,做到无侵入.
    装饰器模式:需要用户自己组合代码,非常的不方便.和业务代码混杂在一起.
    2020-03-12
    1
收起评论
47
返回
顶部