前言
本文旨在快速梳理常用的设计模式,了解每个模式主要针对的是哪些情况以及其基础特征,每个模式前都有列举出一个或多个可以深入阅读的参考网页,以供读者详细了解其实现。
分为三篇文章:
- 上篇:设计模式基础理念和创建型设计模式
- 中篇:行为型设计模式
- 下篇:结构型设计模式
快速回忆
结构型
- 适配器(Adapter)
- 装饰器(Decorator)
- 代理模式(Proxy)
- 外观模式/门面模式(Facade)
- 桥接模式(Bridge Pattern)
- 组合模式(Composite)
- 享元模式(Flyweight)
理念
首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不同的业务场景,用不同的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,但是这都是建立在解耦的基础之上。
高内聚低耦合
高内聚:系统中A、B两个模块进行交互,如果修改了A模块,不影响模块B的工作,那么认为A有足够的内聚。
低耦合:就是A模块与B模块存在依赖关系,那么当B发生改变时,A模块仍然可以正常工作,那么就认为A与B是低耦合的。
结构型
适配器(Adapter)
定义
客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。
角色
- 目标(Target)角色:这就是所期待得到的接口。注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
- 源(Adapee)角色:现在需要适配的接口。
- 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
类适配器
创建新类,继承源类,并实现新接口
class adapter extends oldClass implements newFunc{} 复制代码
对象适配器
创建新类持源类的实例,并实现新接口
class adapter implements newFunc { private oldClass oldInstance ;} 复制代码
- 类适配器使用对象继承的方式,是静态的定义方式
- 而对象适配器使用对象组合的方式,是动态组合的方式。
接口适配器
创建新的抽象类实现旧接口方法
abstract class adapter implements oldClassFunc { void newFunc();} 复制代码
总结
建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。
优点
- 更好的复用性
- 更好的扩展性
缺点
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
装饰模式(Decorator)
给一类对象增加新的功能,装饰方法与具体的内部逻辑无关。
实现
设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。
代码
interface Source{ void method();} public class Decorator implements Source{ private Source source ; public void decotate1(){ System.out.println("decorate"); } @Override public void method() { decotate1(); source.method(); } } 复制代码
装饰模式与代理模式的区别
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。
- 用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。
- 当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。
代理模式(Proxy)
详细代码实例:www.cnblogs.com/daniels/p/8…
简介
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。
为什么要用代理模式
- 中介隔离作用
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。 - 开闭原则,增加功能
真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
有哪几种代理模式
静态代理
是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
代码:静态代理创建代理类:
package main.java.proxy.impl; import main.java.proxy.BuyHouse; public class BuyHouseProxy implements BuyHouse { private BuyHouse buyHouse; public BuyHouseProxy(final BuyHouse buyHouse) { this.buyHouse = buyHouse; } @Override public void buyHosue() { System.out.println("买房前准备"); buyHouse.buyHosue(); System.out.println("买房后装修"); } } 复制代码
静态代理总结
- 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理:JDK反射机制(接口代理)
- 是在程序运行时通过反射机制动态创建的。
- 为需要拦截的接口生成代理对象以实现接口方法拦截功能。
代码:编写动态处理器
package main.java.proxy.impl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicProxyHandler implements InvocationHandler { private Object object; public DynamicProxyHandler(final Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("买房前准备"); Object result = method.invoke(object, args); System.out.println("买房后装修"); return result; } } 复制代码
代码:编写测试类
package main.java.proxy.test; import main.java.proxy.BuyHouse; import main.java.proxy.impl.BuyHouseImpl; import main.java.proxy.impl.DynamicProxyHandler; import java.lang.reflect.Proxy; public class DynamicProxyTest { public static void main(String[] args) { BuyHouse buyHouse = new BuyHouseImpl(); BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), new Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse)); proxyBuyHouse.buyHosue(); } } 复制代码
动态代理总结
- 优势:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
- 劣势:只能对接口进行代理
动态代理:CGLIB代理
- 其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
- 但因为采用的是继承,所以不能对final修饰的类进行代理。
- JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
代码见网页
CGLIB代理总结:(与JDK代理区别:)
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。
- 所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。
- 同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
外观模式/门面模式(Facade)
简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用。这个模式中,涉及到3个角色。
角色
1)门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。
2)子系统角色: 实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。
3)客户角色: 通过调用Facede来完成要实现的功能。
代码
package com.huawei.facadeDesign.facade; import org.apache.log4j.Logger; import com.huawei.facadeDesign.children.CPU; import com.huawei.facadeDesign.children.Disk; import com.huawei.facadeDesign.children.Memory; /** * 门面类(核心) * @author Administrator * */ public class Computer { public static final Logger LOGGER = Logger.getLogger(Computer.class); private CPU cpu; private Memory memory; private Disk disk; public Computer() { cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void start() { LOGGER.info("Computer start begin"); cpu.start(); disk.start(); memory.start(); LOGGER.info("Computer start end"); } public void shutDown() { LOGGER.info("Computer shutDown begin"); cpu.shutDown(); disk.shutDown(); memory.shutDown(); LOGGER.info("Computer shutDown end..."); } } 复制代码
优点
- 松散耦合:使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
- 简单易用:客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
- 更好的划分访问层次:有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。
桥接模式(Bridge Pattern)
含义
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”?
如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就要使用Bridge模式。
由上图变为下图
代码
详细代码见参考网页
static void Main(string[] args){ //男人开着公共汽车在高速公路上行驶; Console.WriteLine("========================="); AbstractRoad Road3 = new SpeedWay(); Road3.Car = new Bus(); people p = new Man(); p.Road = Road3; p.Run(); Console.Read(); } 复制代码
组合模式(Composite)
组合模式是为了表示那些层次结构,同时部分和整体也可能是一样的结构,常见的如文件夹或者树.
定义
组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。
在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
角色
1.Component :组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
2.Leaf:叶子对象。叶子结点没有子结点。
3.Composite:容器对象,定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
适用场景
1、需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。
2、让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。
享元模式(Flyweight)
定义
所谓享元模式就是运行共享技术有效地支持大量细粒度对象的复用,所以享元模式要求能够共享的对象必须是细粒度对象。
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
代码
享元工厂类FlyweightFactory:
利用了HashMap保存已经创建的颜色
public class FlyweightFactory{ static Map<String, Shape> shapes = new HashMap<String, Shape>(); public static Shape getShape(String key){ Shape shape = shapes.get(key); //如果shape==null,表示不存在,则新建,并且保持到共享池中 if(shape == null){ shape = new Circle(key); shapes.put(key, shape); } return shape; } public static int getSum(){ return shapes.size(); } } 复制代码
客户端程序:Client.java
public class Client { public static void main(String[] args) { Shape shape1 = FlyweightFactory.getShape("红色"); shape1.draw(); Shape shape2 = FlyweightFactory.getShape("灰色"); shape2.draw(); Shape shape3 = FlyweightFactory.getShape("绿色"); shape3.draw(); Shape shape4 = FlyweightFactory.getShape("红色"); shape4.draw(); Shape shape5 = FlyweightFactory.getShape("灰色"); shape5.draw(); Shape shape6 = FlyweightFactory.getShape("灰色"); shape6.draw(); System.out.println("一共绘制了"+FlyweightFactory.getSum()+"中颜色的圆形"); } } 复制代码
参考
简书大牛:www.jianshu.com/nb/10186551
Github:github.com/CyC2018/Int…