结构型模式之二其他模式
文章目录
- 1. 桥接模式
- 1.1 角色职责
- 1.2 使用场景
- 2 装饰器模式
- 2.1 例子
- 2.2 角色职责
- 2.3 框架中的使用
- 3.适配器模式
- 3.1 角色职责
- 3.2 例子
- 3.3 框架中的应用
- 4 门面模式
- 4.1 角色职责
- 4.2 例子
- 4.3 框架中的应用
- 5 组合模式
- 5.1 角色职责
- 5.2 例子
- 5.3 总结
- 6 享元模式
- 6.1 角色职责
- 6.2 例子
- 6.3 区别
- 6.4 框架中的应用
- 7 结构型设计模式总结
1. 桥接模式
一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。
“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
当一个类内部具备两种或多种变化维度时,使用桥接模式可以[解耦]这些变化的维度,使高层代码架构稳定。
桥接模式和策略模式区别
桥接模式强调的是抽象与实现的分离,策略模式强调的是算法的封装和相互替换,前者是结构模式,后者是行为模式。
1.1 角色职责
观察上面的 UML 类图,我们可以发现,桥接模式分为以下四个角色:
角色 | 职责 |
---|---|
Abstraction | 定义抽象接口,拥有一个Implementor类型的对象引用 |
RefinedAbstraction | 扩展Abstraction中的接口定义 |
Implementor | 实现部分,可以为接口或者是抽象类,其方法不一定要与抽象部分中的一致,一般情况下是由实现部分提供基本的操作,而抽象部分定义的则是基于实现部分基本操作的业务方法 |
具体实现化角色 | 实现Implementor接口,给出具体实现 |
让手机既可以按照品牌分类,又可以按照软件类型分类。
1.2 使用场景
不同的品牌针对不同的软件有这不同的兼容支持,起初针对A品牌添加游戏功能。
public class ABrandGame {public void run(){System.out.println("A品牌游戏运行");}
}
新增需求针对B品牌添加游戏功能,基于面向对象的理解,需要抽象一个接口,AB品牌均该接口,UML类图如下:
public interface BrandPhone {/*** 运行*/void run();
}
添加一个通话功能,A、B品牌均需要,则意味着接口为品牌、其下有抽象子类A、B品牌,其下有对应的游戏、通讯功能。UML类图如下:
如此,添加更多的功能和更多的品牌,则类的扩展成倍的增长,因为此处使用继承这种强耦合的关系,在业务过于复杂的时候弊端明显,其实仔细分析该需求有两个维度的变化一种是品牌,一种是软件功能。使用桥接模式可以很好解决继承过深和强耦合关系。
桥接模式的使用
2 装饰器模式
装饰器模式对某一个事物进行装饰的一种模式。从代码层面而言,是对类的一个扩展或者是修饰,从传统方法而言,我们可以使用继承来对某一个类进行扩展,但是往往会导致会出现非常多的子类,如果我们要想避免这种情况,那么我们就可以使用设计模式中的——装饰器模式。
2.1 例子
我们以女孩最爱喝的奶茶为例子,定义一个奶茶接口MilkyTea
public interface MilkyTea {/*** 制作奶茶*/public void make();
}
实现椰香奶茶、珍珠奶茶、芋圆奶茶
public class CoconutMilkyTea implements MilkyTea{//制作椰香奶茶@Overridepublic void make(){System.out.println("制作普通奶茶");System.out.println("添加椰蓉");}
}public class PearlMilkyTea implements MilkyTea {//制作珍珠奶茶@Overridepublic void make() {System.out.println("制作普通奶茶");System.out.println("添加珍珠");}
}public class TaroBallsMilkyTea implements MilkyTea {//制作芋圆奶茶@Overridepublic void make() {System.out.println("制作普通奶茶");System.out.println("添加芋圆");}
}
需求变动,除了普通类型的奶茶,为了健康增加半糖选项,为了应付高温增加冰奶茶,按照如上逻辑,上述的三种奶茶都需要派生出对应的半糖产品和冷饮产品,如此满足如上需求产品的类就会变成3*3的九个类
如果再增加更多的奶茶或者产品特性,天啊,类的数量会成倍增加。这时候装饰器模式就要起作用了。
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。其作用如下:
给原始类添加增强功能
可以对原始类嵌套使用多个装饰器
此处对业务进行重构,将上述的材料抽取处理珍珠、芋圆、椰蓉等’装饰’食材,提供最初的奶茶接口的实现,需要添加材料可以通过装配模式丰富功能。
实现接口
public class NormalMilkyTea implements MilkyTea {@Overridepublic void make() {System.out.println("制作普通奶茶");}
}
装配类
//珍珠装饰类
public class PearlDecorator implements MilkyTea {//持有一个需要增强的原始类private MilkyTea milkyTea;public PearlDecorator(MilkyTea milkyTea){this.milkyTea = milkyTea;}@Overridepublic void make() {//原始类功能milkyTea.make();//装饰增强System.out.println("添加珍珠");}
}//其他装饰类如上所示
单元测试
@Test
public void test(){//制作珍珠芋圆奶茶//普通奶茶MilkyTea milkyTea = new NormalMilkyTea();//半糖装饰SugarHalfDecorator sugarHalfMilyTea = new SugarHalfDecorator(milkyTea);//珍珠装饰PearlDecorator pearMilyTea = new PearlDecorator(sugarHalfMilyTea);//芋圆装饰TaroBallsDecorator taroBallsMilyTea = new TaroBallsDecorator(pearMilyTea);//椰蓉装饰CoconutDecorator coconutMilyTea = new CoconutDecorator(pearMilyTea);//冷饮装饰ColdDecorator coldMilyTea = new ColdDecorator(coconutMilyTea);coldMilyTea.make();
}
使用装配模式后,可以灵活的配置制作各种奶茶品种了,而且对于新增奶茶材料只需要实现一个装饰类即可。
2.2 角色职责
装饰器模式主要包含以下角色。
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能(上述例子中因为只有一个接口所以没有抽象装饰角色)。
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
2.3 框架中的使用
java的IO流使用了装饰模式
装饰器和代理模式异同
- 同:都是进行功能的增强。
- 异:装饰器是对原始类相关功能进行增强而代理模式是对非相关功能的增强。
3.适配器模式
一般来说,适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。
3.1 角色职责
相关角色
适配器模式(Adapter)包含以下主要角色。
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
应用场景:
- 外部系统接口的二次封装,抽象出符合我们业务需要的接口方法
- 同一功能点的多种实现框架的整合适配,比如多种日志系统的适配示例sl4j。
- 兼容老版本接口
- 适配不同格式的数据
适配器的两种实现
- 类适配器:主要使用方式是继承方式,Adapter(适配器)继承Adaptee(适配者类)
- 对适配器:对象适配器使用组合关系来实现,Adapter(适配器)持有Adaptee(适配者类)成员变量。
3.2 例子
电动剃须刀、手机、电脑、平板这些家用电器需要不同电压的来进行充电,但是国家电网统一提供220V的电压,所以此处需要使用转接头进行处理。
目标接口
// 充电器接口
public interface Charger {/*** 充电功能*/void charge();
}
适配者
//pc适配者
public class PcCharge {public void chargePc(){System.out.println("180V电压给电脑充电");}
}
//手机适配者
public class PhoneCharge {public void chargePhone(){System.out.println("5V电压给手机充电");}
}
适配器
//pc电源适配器(对象适配)
public class PcChargeAdaptor implements Charger {//对象适配,以组合的形式持有适配者,Charger接口都需要被实现private PcCharge pcCharge;@Overridepublic void charge() {System.out.println("220V电压转换为180V");pcCharge.chargePc();}
}//pc电源适配器(类适配)继承形式拥有适配者方式,同名函数可以不用去实现
public class PhoneChargeAdaptor extends PhoneCharge implements Charger {@Overridepublic void charge() {System.out.println("220V电压转换为5V");chargePhone();}
}
UML类图
3.3 框架中的应用
slf4j 为logback、log4j、apache log等提供适配
为了兼容1.0时期的Enumeration,Collections类中使用适配器模式,兼容新老版本。这里用的是对象适配模式,用iterator代替Enumeration
4 门面模式
4.1 角色职责
门面模式,也叫外观模式,英文全称是 Facade Design Pattern。门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。说白了门面为了简化接口细粒度情况下的接口调用的复杂度。
作用
- 解决易用性问题,隐藏底层细节,降低复杂性,提供简单易用的高层接口组装。
角色 | 职责 |
---|---|
外观角色(Facade) | 也称门面角色,系统对外的统一接口 |
子系统角色(SubSystem) | 可以同时有一个或多个SubSystem |
4.2 例子
本例子以饭店提供服务为例,客户点餐不需要主动去和厨房的厨师说明,只需和饭店的服务员(门面角色)说明自己的诉求然后等待即可,不需要关注厨师、食材坐等自己的餐品端到自己面前即可
chef接口及实现
public interface Chef {void cook();
}
//冷菜师
public class ColdDishChef implements Chef {@Overridepublic void cook() {System.out.print(" 制作凉拌黄瓜 ");}
}
//面点师
public class PastryChef implements Chef {@Overridepublic void cook() {System.out.print(" 制作华夫饼 ");}
}
//热炒师
public class StirFryChef implements Chef {@Overridepublic void cook() {System.out.print(" 制作烤全羊 ");}
}
waiter 门面
public class Waiter {private ColdDishChef coldDishChef = new ColdDishChef();private StirFryChef stirFryChef = new StirFryChef();private PastryChef pastryChef = new PastryChef();//套餐A 凉拌黄瓜 烤全羊public void foodMenuA(){System.out.print("套餐A:");coldDishChef.cook();stirFryChef.cook();System.out.println();}//套餐B 华夫饼 烤全羊public void foodMenuB(){System.out.print("套餐B:");pastryChef.cook();stirFryChef.cook();System.out.println();}//套餐B 华夫饼 烤全羊 凉拌黄瓜public void foodMenuC(){System.out.print("套餐C:");coldDishChef.cook();pastryChef.cook();stirFryChef.cook();System.out.println();}
}
4.3 框架中的应用
5 组合模式
5.1 角色职责
将一组对象组织(Compose)成树形结构,以表示一种“部分-整
体”的层次结构。组合让客户端可以统一单个对象和组合对象的处理逻辑。
比如文件系统中的文件(单一对象)和目录(组合对象),组织架构的子部门(组合对象)和员工(单一对象)。
角色 | 职责 |
---|---|
抽象组件(Component)角色 | 为组合对象和叶子对象声明公共的接口,让客户端可以通过这个接口来访问和管理整个对象树,并可以为这些定义的接口提供缺省的实现。 |
组合对象(Composite)角色 | 通常会存储子组件(组合对象、叶子对象),定义包含子组件的那些组件的行为,并实现在抽象组件中定义的与子组件有关的操作,例如子组件的添加(addChild)和删除(removeChild)等。 |
叶子对象(Leaf)角色 | 定义和实现叶子对象的行为,并且它不再包含其他的子节点对象 |
客户端(Client)角色 | 通过Component接口来统一操作组合对象和叶子对象,以创建出整个对象树结构。 |
5.2 例子
实现整个公司的人员架构图(部门、员工的隶属关系),并且提供接口计算出部门的薪资成本。
部门和员工抽象组件
public abstract class Component {protected long id;protected double salary;public Component(long id){this.id = id;}public Component(long id,Double salary){this.id = id;this.salary = salary;}/*** 计算薪水*/abstract Double calcSalary();
}
部门
public class Depart extends Component {private List<Component> staffList = new ArrayList<>();public Depart(long id){super(id);}public Depart(long id,List<Component> staffList){super(id);this.staffList.addAll(staffList);}public void addStaff(Component staff){this.staffList.add(staff);}public void addStaffList(List<Component> staffList){this.staffList.addAll(staffList);}@Overridepublic Double calcSalary() {Double totalSalary = 0D;this.salary = totalSalary;if(CollectionUtils.isEmpty(staffList)){return totalSalary;}for(Component staff:staffList){totalSalary +=staff.calcSalary();}this.salary = totalSalary;return totalSalary;}
}
员工
//员工
public class Staff extends Component {public Staff(long id,Double salary) {super(id,salary);}@Overridepublic Double calcSalary() {return this.salary;}
}
5.3 总结
组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。
对于相同的对象可以在内存中保留一份实例多出使用,对于相似对象也可以将这些对象中相同的部分(字段)提取出来,设计成享元,让这些大量相似对象引用这些享元。
6 享元模式
6.1 角色职责
享元模式对于可能出现的大量重复对象,将其不可变的属性抽取出现形成一个不可变的对象即为享元对象。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
角色 | 职责 |
---|---|
抽象享元⾓⾊(Flyweight) | 是所有的具体享元类的基类,为具体享元规范需要实现的公共接⼝,⾮享元的外部状态以参数的形式通过⽅法传⼊ |
具体享元(Concrete Flyweight)⾓⾊ | 实现抽象享元⾓⾊中所规定的接⼝。 |
⾮享元(Unsharable Flyweight)⾓⾊ | 是不可以共享的外部状态,一般情况与具体享元以组合的形式关联(成员变量或者方法形参) |
享元⼯⼚(Flyweight Factory)⾓⾊ | 负责创建和管理享元⾓⾊。当客户对象请求⼀个享元对象时,享元⼯⼚检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建⼀个新的享元对象。 |
6.2 例子
我们以一个大型象棋为例,游戏有成千上万个房间,每一个房间都有一个棋局,每个棋局有若干棋子,如果创建这些对象会导致程序出现庞大的相似对象,但是分析发现每个棋子的颜色(红方、黑方)、文字(车,炮,相)是一致的,那么我们可以将其抽取出来创建一个享元对象。
享元对象
public class ChessPieceUnit {//唯一标识private Integer id;//文字 车马炮private String text;//红方、黑方private String color;public ChessPieceUnit(Integer id, String text, String color) {this.id = id;this.text = text;this.color = color;}
享元对象工厂
public class ChessPieceUnitFactory {private static Map<Integer, ChessPieceUnit> chessPieces = new HashMap<>();static{chessPieces.put(1,new ChessPieceUnit(1,"车","红"));chessPieces.put(1,new ChessPieceUnit(1,"马","黑"));//...省略其他棋子}public static ChessPieceUnit getChessUnit(Integer chessPieceId){ChessPieceUnit chessPieceUnit = chessPieces.get(chessPieceId);return chessPieceUnit;}
}
非享元对象
public class ChessPiece {//享元对象private ChessPieceUnit chessPieceUnit;//棋子所在x轴坐标private Double positionX;//棋子所在y轴坐标private Double positionY;public ChessPiece(ChessPieceUnit chessPieceUnit, Double positionX, Double positionY) {this.chessPieceUnit = chessPieceUnit;this.positionX = positionX;this.positionY = positionY;}
6.3 区别
享元与单例对象的区别
单例对象是一个类只能创建一个对象,享元对象是可以创建多个,享元对象可以看做单例对象的变种:多例对象(有限)对象的复用。
享元与对象池的区别
都是对对象的复用,但是对象池更侧重于使用者独占该对象,使用完成后放回池中再供其他对象使用。而享元模式侧重于多个使用者共同使用
6.4 框架中的应用
jdk中的Integer,Long中使用享元模式缓存一部分数据。
jdk中字符串常量池
7 结构型设计模式总结
设计模式 | 作用 |
---|---|
代理模式 | 代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。 |
桥接模式 | 桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。 |
适配器模式 | 适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口 |
装饰器模式 | 装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。 |
门面模式 | 简化接口细粒度情况下的接口调用的复杂度 |
组合模式 | 更像是对业务场景的一种数据结构和算法的抽象 |
享元模式 | “享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存 |
结构型模式之二其他模式相关推荐
- 图解Java设计模式学习笔记——结构型模式(适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式、代理模式)
一.适配器模式(类适配器.对象适配器.接口适配器) 1.现实生活中的例子 泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了国内的电器了. 2.基本介绍 适配器模式(Ad ...
- 设计模式之七个结构型模式的相关知识,简单易懂。
七. 适配器模式-Adapter Pattern 1) 不兼容结构的协调--适配器模式(一) 我的笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压 ...
- 设计模式(18):结构型-享元模式(Flyweight)
设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于 ...
- JavaScript设计模式(三):结构型设计模式-外观模式、适配器模式、代理模式、装饰者模式、桥接模式、组合模式、享元模式
JavaScript设计模式 - 结构型设计模式 套餐服务-外观模式 外观模式(Facade) 水管弯弯-适配器模式 适配器模式(Adapter) 适配异类框架 参数适配 牛郎织女-代理模式 代理模式 ...
- 结构型模式(五)门面模式(Facade Pattern 外观模式)
一.模式定义 门面模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统 ...
- 设计模式——结构型之用桥梁模式(Bridge Pattern)将“抽象”与“实现”解耦(五)
引言 相信对于现实生活中这样的情况都不陌生,比如说开关与它具体控制的电器,开关的类型多种多样,而电器也是千变万化,两者之间相对独立变化却又耦合在一起,再比如说奶茶店的奶茶,有不同规格大小.不同口味.不 ...
- 设计模式(十五)享元模式(结构型)
概述 当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题.例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,那 ...
- Java 经典设计模式-- 03.结构型模式
前言 书接上文,上一篇中创建型设计模式中的常用设计模式做了简单的介绍,本篇将继续对结构型设计模式中的常用模式进行介绍与分析. 目录: 适配器模式 桥接模式 组合模式 修饰模式 代理模式 简单提及: M ...
- 结构型模式中最能体现扩展性模式的是?
设计模式分为三大类: 创建型设计模式:单例模式,工厂方法模式,简单工厂模式,建造者模式. 原型模式 结构型设计模式:适配器模式,***模式,AOP.装饰器模式. 行为型设计模式:观察者模.板方法模式 ...
最新文章
- 爬虫学习笔记(十二)—— scrapy-redis(一):基本使用、介绍
- 【创业】创业公司股权架构设计注意事项
- Eclipse中怎样使用ERMaster进行单表设计并导出为DDL
- 《中秋书月》月圆之夜,我和德鲁克
- PHP面试题:PHP加速模式/扩展? PHP调试模式/工具?
- Error: Could not find or load main class org.apache.tez.dag.app.DAGAppMaster
- 【安全】JAAS/GSS-API/SASL/Kerberos简介
- UltraEdit汇编语言高亮
- Oracle数据空间的管理
- vb.net同步服务器文件,vb.net - VB.NET - 如何以编程方式将身份验证传递给服务器 - 如何访问需要身份验证的服务器上的文件 - 堆栈内存溢出...
- C语言与线性代数,编程与线性代数
- 【编译原理】理解BNF
- 计算机挂个硬盘显示格式化怎么解决,硬盘提示格式化怎么办?硬盘数据怎么恢复?...
- 任务管理器被管理员停用怎么办
- 机器学习识别颜色_使用机器学习为颜色命名
- 网络购车平台哪家强?
- python通过requests库发送请求
- java silk v3 转码,小程序、录音、TP5、转码、silk
- numpy.ix_()函数
- NXP(Freescale) QorIQ T2080 SD接口使用
热门文章
- 如何查看电脑最大支持多少GB内存
- Linux安装omnet++
- 自然人税收管理系统扣缴客户端Sqlite数据库有密码的,如何破解读取呢
- 洛达1536u升级固件_最新Airpods蓝牙耳机华强北二代终极版洛达1562M
- 无盘服务器启动不了,无盘工作站不能启动的解决方案
- 机器学习——朴素贝叶斯(Naive Bayes)详解及其python仿真
- 外文翻译:Study on Key Technology of Power Users Credit Rating Evaluation Ba(基于大数据的电力用户信用评级评估关键技术研究)
- 第一个Android 程序的源代码: TxtReader文本阅读器
- Spring源码学习:BeanPostProcessor注册和执行时机
- 某游戏社区App | So层逆向分析