ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【设计模式】装饰模式(二):认识装饰模式

【设计模式】装饰模式(二):认识装饰模式 因为学到装饰模式太兴奋所以在【设计模式】装饰模式(一)实战从计费系统的“动态加功能“说起-CSDN博客中先讲了我认为的装饰模式与计费模式结合。这篇我们回归基础篇用简单的例子认识一下装饰模式。一、什么是装饰模式1.1 一句话说清楚装饰模式(Decorator)动态地给一个对象添加一些额外的职责就增加功能来说装饰模式比生成子类更为灵活。[DP]【大话设计模式】大白话版就像给人穿衣服。人是核心穿不穿T恤、穿不穿鞋取决于今天去哪。人本身不需要知道自己会被怎么打扮但穿上之后他确实多了点什么。1.2 它的核心特点特点解释穿衣服动态性​继承是编译期定死的装饰是运行期决定的。今天出门看天气想加外套就加不想加就不加透明性​装饰完还是原来的类型外面看不出区别。穿了衣服还是人不会变成别的。不会变身蜘蛛侠。开闭原则​对扩展开放对修改关闭新加一件衣服 新写一个类老代码一行不改。任意组合​想套几层套几层顺序随便换。今天先长袖套短袖明天短袖套长袖也行。最本质的一点继承是静态的装饰是动态的。继承在编译期就锁死了你是什么装饰模式在运行期才决定你今天穿什么。装饰模式可以做到衣服搭配从数据库读、从配置中心拉。1.3 装饰模式的邻居有哪些GoF 23 个模式里有几个和装饰模式长得很像我很容易搞混模式和装饰的相似点核心区别一句话记忆职责链都链式调用职责链条件传递可能中断装饰每层都执行职责链是踢皮球装饰是叠buff建造者都组装建造者创建新对象装饰增强已有对象建造者是造车装饰是改车策略都能换行为策略是替换算法装饰是叠加增强策略是换人干装饰是加人帮装饰模式占据的位置很清晰在保持核心不变的前提下按场景动态叠加额外职责。二、用装饰模式帮小明穿搭2.1 小明的三套穿搭场景穿什么说明居家短裤 背心基础状态核心功能下楼扔垃圾短裤 背心 外套临时出门加一层体面去公司短裤 背心 衬衣 牛仔裤 外套正式场景叠加多层要求核心洞察短裤背心是基础外套/衬衣/牛仔裤是特定场景下才加的层。2.2 如果不用装饰模式方案Aif-else 堆砌void show(String scene) { System.out.println(小明); System.out.println(穿短裤); System.out.println(穿背心); if (扔垃圾.equals(scene) || 去公司.equals(scene)) { System.out.println(穿外套); } if (去公司.equals(scene)) { System.out.println(穿衬衣); System.out.println(穿牛仔裤); } }能跑。但加一个场景就得改这个方法加一件衣服也得改。核心逻辑和附加逻辑搅在一起违反单一职责违反开闭原则。场景一多这个方法会变成无人敢碰的屎山。方案B继承class XiaoMing extends Person { void show() { System.out.println(小明\n穿短裤\n穿背心); } } class XiaoMingWithJacket extends XiaoMing { void show() { super.show(); System.out.println(穿外套); } } class XiaoMingWithShirtAndJeans extends XiaoMingWithJacket { void show() { super.show(); System.out.println(穿衬衣\n穿牛仔裤); } } // ... 还有无数组合3 种衣服就能产生 7 种组合 7 个子类。10 种衣服呢1024 个。而且编译期写死想换个搭配得写新类重新编译。复用也差——穿外套这个逻辑写在XiaoMingWithJacket里另一个人也穿外套就得再写一遍。2.3 装饰模式四角色角色对应例子职责ComponentPerson抽象类定义统一接口show()ConcreteComponentXiaoMing核心功能本体短裤背心DecoratorFinery抽象类实现 Person 接口持有 Person 引用转发请求ConcreteDecoratorJacket/Shirt/Jeans在转发前后插入自己的行为在大话设计模式中讲到Component 是定义一个对象接口可以给这些对象动态地添加职责。ConcreteComponent 是定义了一个具体的对象也可以给这个对象添加一些职责。Decorator装饰抽象类继承了Component从外类来扩展Component类的功能但对于Component来说是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象起到给Component添加职责的功能[DPE]这里有个容易混淆的点ConcreteComponent 和 ConcreteDecorator 都能添加职责但本质完全不同。ConcreteComponent提供基础职责写死在类里的核心功能。就像小明本身就有短裤背心这是他自带的底裤不是谁给他加的。ConcreteDecorator提供附加职责运行时动态嵌套、随意组合。就像外套、衬衣是外面贴上去的想加就加想摘就摘。两者都有穿衣服的职责但一个是基础一个是附加。同时两者都实现了 Component 接口所以客户端看来它们是一回事——里氏替换这也是装饰模式透明性的精髓装饰完还是那个人。类图关系2.4 动态性的威力我们来看下装饰模式的运行时动态扩展-对象的功能组合不是在编译期通过继承锁死的而是在代码跑起来之后通过对象组合的方式按需决定的。public class DecoratorAssembler { // 而实际项目中这个 Map 从配置文件/数据库/配置中心读取 // 这里用 Map.of 模拟配置内容 private static MapString, ListString config Map.of( 居家, List.of(), 扔垃圾, List.of(Jacket), 去公司, List.of(Jacket, Shirt, Jeans) ); public static Person assemble(String scene, Person core) { ListString decorations config.get(scene); if (decorations null) return core; Person result core; for (String deco : decorations) { result wrap(result, deco); } return result; } private static Person wrap(Person p, String type) { switch (type) { case Jacket: Jacket j new Jacket(); j.decorate(p); return j; case Shirt: Shirt s new Shirt(); s.decorate(p); return s; case Jeans: Jeans d new Jeans(); d.decorate(p); return d; default: return p; } } }新增一个场景改配置代码零改动。继承能做到吗做不到——继承的搭配是写死在类里的编译完就改不了。三、装饰模式的实现微观层3.1 核心机制拆解机制代码体现白话实现同一接口​Finery extends Person装饰完还是 Person外面看不出区别——透明性聚合被装饰类​protected Person component装饰器把活儿甩给里面那层——组合优于继承递归调用​super.show()调用链像拆快递一层层剥开每层干完自己的事再交给下一层这三个机制凑在一起就是装饰模式能跑通的全部秘密。3.2 代码实现逐步展开Step 1Componentabstract class Person { public abstract void show(); }定义统一接口。所有东西——小明本人、外套、衬衣、牛仔裤——都实现这个接口。这是透明性的前提。Step 2ConcreteComponent核心本体class XiaoMing extends Person { Override public void show() { System.out.println(小明); System.out.println( 穿短裤); System.out.println( 穿背心); } }居家状态的小明。短裤背心是他的基础不是装饰出来的。对应你的计费系统里的基础计费逻辑——它是核心不是附加层。Step 3Decorator抽象装饰器abstract class Finery extends Person { protected Person component; public void decorate(Person component) { this.component component; } Override public void show() { if (component ! null) { component.show(); } } }关键在这里Finery继承了Person保持接口一致同时持有一个Person引用组合。show()里不干自己的事直接甩给component。Step 4ConcreteDecorators具体装饰器class Shirt extends Finery { Override public void show() { super.show(); System.out.println( 穿衬衣); } } class Jeans extends Finery { Override public void show() { super.show(); System.out.println( 穿牛仔裤); } } class Jacket extends Finery { Override public void show() { super.show(); System.out.println( 穿外套); } }每个装饰器做两件事super.show()把请求传给里面那层然后自己System.out.println加上自己的行为。super.show()放前面所以打印顺序是从里到外——先穿背心再穿外套最后才套上符合穿衣服的直觉。Step 5客户端调用public class DecoratorDemo { public static void main(String[] args) { XiaoMing xiaoMing new XiaoMing(); // 场景一居家 System.out.println( 场景一居家 ); xiaoMing.show(); // 场景二下楼扔垃圾加件外套 System.out.println(\n 场景二下楼扔垃圾 ); Jacket jacket new Jacket(); jacket.decorate(xiaoMing); jacket.show(); // 场景三去公司 System.out.println(\n 场景三去公司 ); Shirt shirt new Shirt(); Jeans jeans new Jeans(); Jacket jacket2 new Jacket(); shirt.decorate(xiaoMing); jeans.decorate(shirt); jacket2.decorate(jeans); jacket2.show(); // 一行套娃写法 System.out.println(\n 一行套娃 ); new Jacket(new Jeans(new Shirt(xiaoMing))).show(); } }输出 场景一居家 小明穿短裤穿背心 场景二下楼扔垃圾 小明穿短裤穿背心穿外套 场景三去公司 小明穿短裤穿背心穿衬衣穿牛仔裤穿外套 一行套娃 小明穿短裤穿背心穿衬衣穿牛仔裤穿外套调用顺序jacket2.show()触发Jacket.show()→super.show()→Jeans.show()→super.show()→Shirt.show()→super.show()→XiaoMing.show()。从最外层一路传到最内层再逐层返回打印。这就是为什么super.show()放前面时输出是从里到外的——就像穿衣服先穿的在里面后穿的在外面。如果super.show()放后面打印就反过来——像剥洋葱一样从外到内一层层剥开。四、回顾与下篇预告装饰模式的核心手段是递归组合——装饰器本身也是 Component可以再被装饰。这让它既能保持接口透明客户端无感知又能按场景任意叠加组合爆炸但类不爆炸。回顾一下它的核心特点给核心功能叠buff、动态性运行期决定穿什么、透明性装饰完还是人、开闭原则新衣服新类。下一篇我们聊一个更深层的问题new Jacket(new Shirt(xiaoMing)).show()这行代码为什么能跑通super.show()的调用链背后 Java 多态、this指向、里氏替换原则在背后干了什么从代码技巧到设计思想我们下篇见。
返回列表