设计模式
什么是设计模式
- 设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,
而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
学习设计模式的意义
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及
类的关联关系和组合关系的充分理解 - 正确使用设计模式具有以下优点:
- 可以提高程序员的思维能力、编程能力和设计能力
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,
从而缩短软件的开发周期 - 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强
设计模式的基本要素
- 模式名称
- 问题
- 解决方案
- 效果
GoF23
- (Gang of Four)人称[GoF设计模式]
- GoF23
- 一种思维,一种态度,一种进步
- 创建型模式(省去new):
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
- 结构型模式(实现松耦合):
- 适配器模式,桥接模式,装饰模式,组合模式,外观模式,亨元模式,代理模式
- 行为型模式:
- 模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,
解释器模式,状态模式,策略模式,职责链模式,访问者模式
- 模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:控制类的力度大小、将对象解耦、提高其内聚性
- 接口隔离原则:要为各个类建立它们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不跟"陌生人"说话
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
单例模式
- 核心作用:
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
- 常见场景:
- Windows的任务管理器
- Windows的回收站
- 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取
- 网站的计数器一般也会采用单例模式,可以保证同步
- 数据库连接池的设计一般也是单例模式
- 在Servlet编程中,每个Servlet也是单例
- 在Spring中,每个Bean默认就是单例的
饿汉式
//饿汉式单例
public class Hungry {
//可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
//懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*/
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
工厂模式
- 作用:
- 实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式:
- 虽然某种程度上不符合设计原则,但实际使用最多!
- 工厂方法模式
- 不修改已有类的前提下,通过增加新的工厂类实现扩展!
- 抽象工厂模式
- 不可以增加产品,可以增加产品族!
- 简单工厂模式:
- 应用场景:
- JDK中Calendar的getInstance方法
- JDBC中的Connection对象的获取
- Spring中的IOC容器创建管理bean对象
- 反射中Class对象的newInstance方法
抽象工厂模式
- 定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
- 适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
- 优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起创建
- 缺点:
- 规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难
- 增加了系统的抽象性和理解难度
建造者模式
- 建造者模式也属于创建型模式,它提供了一种创建对象的最佳方法
- 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
- 主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象
- 用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象
- 例子:
- 工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
- 汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就
可以使用了(不需要知道汽车是怎么组装的)
- 优点:
- 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节
- 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
- 具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库
的代码,符合"开闭原则"
- 缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,
则不适合使用建造者模式,因此其使用范围受到一定的限制 - 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,
- 应用场景:
- 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品
- 适合于一个具有较多的零件(属性)的产品(对象)的创建过程
- 建造者与抽象工厂模式的比较:
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,
这些产品位于不同的产品等级结构,构成了一个产品族 - 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,
客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程
和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 - 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装
工厂,通过对部件的组装可以返回一辆完整的汽车!
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,
原型模式
- 1.实现一个接口Cloneable
- 2.重写一个方法clone()
适配器模式
- USB网线转换器
- 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的
那些类可以在一起工作 - 对象适配器优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据"里氏代换原则",
适配者的子类也可通过该适配器进行适配
- 类适配器缺点:
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性
- 适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
桥接模式
- 桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,
又称为柄体模式或接口模式 - 优点:
- 桥接模式偶尔类似于多继承方案,但多继承方案违背了类的单一职责原则,复用性比较差,类的个数
也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本 - 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
- 桥接模式偶尔类似于多继承方案,但多继承方案违背了类的单一职责原则,复用性比较差,类的个数
- 缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对
抽象进行设计与编程 - 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对
- 最佳实践:
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态
的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式
独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,
即系统需要对抽象化角色和实现化角色进行动态耦合 - 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理
这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态
- 场景:
- Java语言通过Java虚拟机实现了平台的无关性
- AWT中的peer架构
- JDBC驱动程序也是桥接模式的应用之一