结构型设计模式
文章目录
建造者模式:
基本介绍:
- 建造者模式又叫做生成器模式,是一种对象构建模式。它可以将复杂对象的创建过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构建出不同表现(属性)的对象
- 建造者模式是一步一步创建一个复杂的对象,它允许用户只指定复杂对象的类型和内容就创建它们,用户不需要知道内部的具体构建细节
创建者模式的四个角色:
-
Product(产品对象):一个具体的产品对象
-
Builder(抽象建造者):创建一个Product对象的各个部件指定接口/抽象类
-
ConcreteBuilder(具体创建者):实现接口,构建和装配各个部件
-
Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是隔离客户和对象的生产过程。二是,负责控制产品对象的生产过程。
-
UML:
-
练习:
注意事项和细节:
- 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 每一个具体建造者相对独立,而与其他的具体创建者无关,因此可以很方便的替换具体建造者或者增加新的具体建造者,用户使用不同具体建造者即可获得不同的产品对象
- 可以更加精细的控制产品的创建过程。将负责的产品创建步骤分解在不同的方法中,使得创新过程更加清晰,也更方便使用程序来控制创建过程
- 增加新的具体建造者无须修改原有的类库的代码,指挥者类针对抽象创建者类编程,系统扩展更加方便,符合开闭原则。
- 建造者模式所创建的产品一般具有较多的共同点,其组成范围部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。因此使用范围遭到一定的限制。
- 如果产品内部变化复杂,可能导致需要定义很多具体从建造者类来实现这种变化,导致系统变得很庞大,因此这种情况下,要考虑是否选择建造者模式
- 抽象工厂模式和建造者模式
- 抽象工厂模式实现产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它主要的目的是通过组装零配件而产生一个新产品
适配器模式
基本介绍:
- 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能在一起工作的两个类可以协同工作。其别名为包装类
- 适配器模式属于结构性模式
- 主要分为:类适配器模式,对象适配器模式,接口适配器模式
工作原理:
- 适配器模式:将一个类的接口转化成另一种接口,让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
类适配器:
用例:手机充电适配器将220v电压转化成5v电压
注意事项和细节:
- Java是单继承机制,所以类适配器需要继承src类是一大缺点,再者就是要丢转化的目标是接口,同样具有一定的限制性
- src类的方法在adapter中暴露出来,增加了使用的成本
- 由于其继承了src类,所以它可以根据需求重写src类的方法,使得adapter的灵活性增加了
对象适配器:
基本介绍:
- 基本思路和类适配器相同,只是将adapter类作修饰,不是继承src类,而是持有src类的实例,以解决兼容性问题。即持有src类,实现dst类接口,完成src–>dst的适配
- 根据
合成复用原则
,在系统中尽量使用关联关系来替代继承关系 - 对象适配器模式是适配器模式常用的一种
注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现的方式不同
- 根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口
- 使用成本更低,更灵活
接口适配器模式(缺省适配器):
- 一些书籍上也称之为:缺省适配器模式
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择的覆盖父类的某些方法来实现需求。
- 适用于一个接口不想使用其全部接口的情况
拓展:
抽象类和接口的区别:
- 相似:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
- 区别:
- 接口里只能包含抽象方法,静态方法和默认方法(加default),不能为普通方法提供方法实现
- 抽象类则完全可以包含普通方法,接口中的普通方法默认为抽象方法。
- 接口中的成员变量只能是 public static final 类型的,并且必须赋值,否则通不过编译。
- 抽象类中的成员变量可以是各种类型的
- 接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
- 接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
桥接模式:
上图问题:
- 扩展性问题:类爆炸
- 违法单一职责原则
基本介绍
- 桥接模式是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变(MVC三层架构)
- 是一种结构型设计模式,
- 桥接模式基于类的最小设计原则,通过封装,聚合,即继承等行为让不同的类承担不同的职责。它的主要特点就是把抽象与行为实现分离开来,从而可以保持各个部分的独立性以及应对它们的功能拓展
注意事项和细节:
- 实现了抽象和实现部分分离,从而极大的提高了系统的灵活性,让抽象部分和实际部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统
- 对于系统的高层部分,只需知道抽象部分和实现部分的接口即可,其他的部分由具体业务来完成
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关系关联建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性
装饰者模式
定义:装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰着模式也体现了开闭原则
代码:
Drink
:抽象类
public abstract class Drink {
private String des;
private int price=0;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public abstract int cost();
}
coffee
package decorator;
public class Coffee extends Drink{
@Override
public int cost() {
return super.getPrice();
}
}
longBlack
package decorator;
public class LongBlack extends Coffee{
public LongBlack(){
setDes("longBlack");
setPrice(2);
}
}
decorator
package decorator;
public class Decorator extends Drink{
private Drink drink; //被装饰者
public Decorator(Drink drink){
this.drink=drink;
}
@Override
public int cost() {
//进行费用的叠加计算,递归的计算价格
return super.getPrice()+drink.cost();
}
@Override
public String getDes() {
return super.getDes()+":"+super.getPrice()+" && "+drink.getDes()+":"+drink.getPrice();
}
}
chocolate
package decorator;
public class Chocolate extends Decorator{
public Chocolate(Drink drink) {
super(drink);
setDes("chocolate");
setPrice(3);
}
}
cofferBar
package decorator;
public class DrinkBar {
public static void main(String[] args) {
Drink order = new LongBlack();
System.out.println("cost: "+order.cost());
System.out.println("des: "+order.getDes());
order= new Chocolate(order);
System.out.println("cost: "+order.cost());
System.out.println("des: "+order.getDes());
}
}
组合模式
基本介绍:
- 组合模式,又叫做部分整体模式它创建了对象组的树形结构,将对象组合成树状结构以表示“整体–部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次
- 这种类型的设计模式属于结构型模式
- 组合模式使得用户对单个对象的组合对象的访问具有一致性,即,组合能让客户以一致性的方式处理个别对象以及组合对象
解决问题:
- 组合模式可以解决这样的问题:当我们要处理的对象可以生成一颗树形结构,而我们要做的是对树上的节点和叶子进行操作时,它可以提供一致的方式,而不用考虑它是节点还是叶子。
注意事项和细节:
- 简化客户端操作,客户端只要面对一致的对象而不用考虑整体部分或者叶子节点的问题
- 具有较强的扩展性。当我们要更改组合对象时,只需要调整内部层次关系,客户端不需要做出任何改动
- 方便创建出复杂的层次关系。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
- 要求较高的抽象性,如果节点和叶子由很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
外观模式
基本介绍:
- 外观模式也叫做“界面模式”,外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
享元模式
基本介绍:
- 享元模式:也叫做“蝇量模式”:运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重复创建,如果没有我们需要的,就创建一个。
- 享元模式能够解决重复对象的内存浪费问题,当系统中由大量相似对象,需要缓冲池时。不需总是创建对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术,string常量池,数据库连接池,缓冲池都是享元模式的应用
- Flyweight:抽象的享元角色,同时定义出对象的外部状态和内部状态的接口或者实现
- ConcreteFlyweight:具体的享元角色,具体的产品类,实现抽象角色定义的相关业务
- UnshareConcreteFlyweight:不可共享的角色,一般不会出现在享元工厂中
- FlyweightFactory:享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的方法。
内部状态和外部状态:
- 享元模式提出了两个要求,细粒度和共享对象
- 内部状态指对象共享出来的信息,存储在享元对象中内部且不会随环境改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的,不可共享的状态
代理模式
基本介绍:
- 代理模式:为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问对象,这样做的好处是:可以在目标对象实现的基础上,增加额外的功能操作,即拓展目标对象的功能。
- 被代理的对象可以是远程对象,创建开销大的对象或者需要安全控制的对象
- 代理模式的不同形式:静态代理,动态代理(JDK代理,接口代理),Cglib代理(可以在内存动态的创建对象,而不需要实现接口,属于动态代理的范畴)
静态代理:
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承共同的父类
优缺点:
- 优点:在不修改目标功能的前提下,能通过代理对象对目标功能的扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类一旦接口增加方法,目标对象与代理对象都要维护
动态代理:
基本介绍:
- 代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成是利用JDK的API,动态的在内存中构建代理对象
PorxyFactory:
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
public Object getProxyInstance(){
/*java.lang.reflect.Proxy
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
*ClassLoader:指定当前目标对象使用的类加载器,获取加载器的方法固定
* interfaces:目标对象实现的接口类型,使用泛型方法确认类型
* InvocationHandler:事件处理,执行目标对象时,会触发事件处理方法
*
* */
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过反射机制调用目标对象的方法
Object invoke = method.invoke(target, args);
return invoke;
}
});
}
}
Cglib代理:
基本介绍:
- 静态代理模式和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口。这个时候可使用目标对象子类来实现代理,这就是cglib代理
- cglib代理也叫做子类代理,它是内存中构建一个子类对象从而实现对目标对象的功能扩展
- cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与是实现Java接口,它广泛的被许多AOP的框架使用,例如spring aop,实现方法的拦截
- cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
public Object getProxyInstance(){
//创建一个工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用目标对象的方法
Object invoke = method.invoke(target, args);
return invoke;
}
}