文章目录
- 1.设计模式的六大原则
- 2.创建型设计模式-单例设计模式
- 3.创建型设计模式-工厂模式
- 4.创建型设计模式-原型设计模式
- 5.创建型设计模式-建造者模式
- 6.结构型设计模式-适配器模式
- 7.结构型设计模式-桥接模式
- 8.结构型设计模式-组合模式
- 9.结构型设计模式-装饰器模式
- 10.结构型设计模式-代理模式
- 11.结构型设计模式-外观模式
- 12.结构型设计模式-享元模式
- 13.行为型设计模式-策略模式
- 14.行为型设计模式-模板方法模式
- 15.行为型设计模式-观察者模式
- 16.行为型设计模式-责任链模式
- 17.行为型设计模式-命令模式
- 18.行为型设计模式-迭代器模式
- 19.行为型设计模式-备忘录模式
- 20.行为型设计模式-状态模式
- 21.不常用的设计模式
- 22.设计模式在源码和框架的应用
1.设计模式的六大原则
1.1.软件设计开发原则
(1)单一职责原则
- 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
- 就是实现高内聚、低耦合的指导方针。
- 高内聚:尽可能类的每一个成员方法(最大限度的聚合),模块内部的代码,相互之间的联系越强,内聚就越高,模块的独立性就越好。
- 低耦合:减少类内部,一个成员方法调用另一个成员方法。
(2)开闭原则
- 对扩展开放,对修改关闭,在程序需要惊醒扩展的时候,不去修改原有的代码,实现一个热插拔的效果。
(3)里氏替换原则LSP
- 任何基类可以出现的地方,子类一定可以出现。
- 在程序中尽量使用基类类型定义对象,而在运行时在确定其子类类型,用子类对象来替换父类对象。
(4)依赖倒转原则
- 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体。
- 高层模块不应该依赖于底层模块,二者都应该依赖其抽象。
(5)接口隔离原则
- 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度。
(6)迪米特法则
- 最少知道原则,一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
- 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一但被修改,不会对关联的类造成太大的波及。
软件设计开发原则的作用
- 为了让的代码更好重⽤性,可读性,可靠性,可维护性诞⽣出了很多软件设计的原则,这6⼤设计原则是我们要掌握的
- 将六⼤原则的英⽂⾸字⺟拼在⼀起就是SOLID(稳定的),所以也称之为SOLID原则
1.2.设计模式分类
(1)什么是GOF(Gang of Four)
在 1994 年,由 四位作者合称 GOF(全拼 Gang of Four)四⼈合著出版了⼀本名为 Design Patterns - Elements of Reusable Object-Oriented Software.
他们所提出的设计模式主要是基于以下的⾯向对象设计原则。
1)对接⼝编程⽽不是对实现编程。
2)优先使⽤对象组合⽽不是继承
(2)常见的三大设计模式分类
- 创建型模式
- 提供了一种在创建对象的同时隐藏创建逻辑的方式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
常用:工厂模式、抽象工厂模式、单例模式、建造者模式
不常用:原型模式
- 结构型模式
- 关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
常用:适配器模式、桥接模式、装饰器模式、代理模式
不常用:组合模式、外观模式、享元模式
- 行为模式
- 特别关注对象之间的通信
常用:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、模板模式
不常用:备忘录模式、命令模式
几乎不用:访问者模式、中介者模式、解释器模式
2.创建型设计模式-单例设计模式
2.1.单例模式简介
(1)简介
- 单例设计模式只包含一个对象被称为单例的特殊类,通过单例模式可以保证系统中,应该该模式的类只有一个对象实例。
(2)使用场景
- 业务系统全局只需要一个对象实例,比如mysql连接,redis连接。
- Spring IOC容器中的bean默认就是单例。
- Spring中的@Autowired注入的对象就是单例的。
(3)单例模式分类
- **懒汉模式:**懒加载,延迟创建对象,只有当需要用到对象的时候才会加载创建
- **饿汉模式:**程序启动的时候,就加载创建对象
(4)实现步骤
- 私有化构造方法
- 提供获取单例的方法
2.2.懒汉方式实现
(1)第一种方式
- 高并发下不能保证创建的对象只有一个,多个线程进入到判空,创建对象返回
public static SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
(2)第二种方式
- 高并发下能保证创建的对象是一个,但是synchronized锁定方法,高并发下性能损耗严重
public static synchronized SingletonLazy getInstance(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
(3)第三种方式
- 虽然加了锁,但是同样不能保证单例,锁的位置加的不正确
public static SingletonLazy getInstance(){
if(instance == null){
synchronized (SingletonLazy.class){
instance = new SingletonLazy();
}
}
return instance;
}
(4)第四种方式
DCL双重检查锁定(Double-Checked-Locking),多线程下保持高性能
instrance = new SingletonLazy(),并不是原子性操作,衍生到内存模型
- 1.分配空间给对象
- 2.在空间内创建对象
- 3.将对象赋值给引用的instance
- 注意:假如线程是按照1->3->2的顺序,那这个对象是不完整的
volatile禁止指令重排
private static volatile SingletonLazy instance;
public static SingletonLazy getInstance(){
if(instance == null){
synchronized (SingletonLazy.class){
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
(5)测试
- 一步一步优化,最终选择第四种作为单例懒汉模式的实现
public class SingletonLazy{
private static volatile SingletonLazy instance;
private SingletonLazy(){};
public static SingletonLazy getInstance(){
if(instance == null){
synchronized (SingletonLazy.class){ //1
if(instance == null){ //2
instance = new SingletonLazy(); //3
}
}
}
return instance;
}
public void pub(){
System.out.println("方法调用");
}
}
1.线程A进入 getInstance() 方法。
2.由于 singleton为 null,线程A在 //1 处进入 synchronized 块。
3.线程A被线程B预占。
4.线程B进入 getInstance() 方法。
5.由于 singleton仍旧为 null,线程B试图获取 //1 处的锁。然而,由于线程A已经持有该锁,线程B在 //1 处阻塞。
6.线程B被线程A预占。
7.线程A执行,由于在 //2 处实例仍旧为 null,线程A还创建一个 Singleton 对象并将其引用赋值给 instance。
8.线程A退出 synchronized 块并从 getInstance() 方法返回实例。
9.线程A被线程B预占。
10.线程B获取 //1 处的锁并检查 instance 是否为 null。
11.由于 singleton是非 null 的,并没有创建第二个 Singleton 对象,由线程A所创建的对象被返回。
public class Main {
public static void main(String[] args) {
SingletonLazy.getInstance().pub();
}
}
2.3.饿汉模式实现
- 饿汉方式:提前创建好对象
- 优点:实现简单,没有多线程同步问题
- 缺点:不管有没有使用,instance对象一直占着这段内存
- 如果对象不大,且创建不复杂,直接用饿汉模式即可,其他情况用懒汉模式
public class SingletonHungry{
//提前new好,jvm加载时创建
private static SingletonHungry instance = new SingletonHungry();
//构造私有化
private SingletonHungry(){};
public static SingletonHungry getInstance(){
return instance;
}
public void pub(){
System.out.println("方法调用");
}
}
2.4.JDK源码里的单例模式
- Runtime.java,java运行时的类。
- Desktop,java图形类
3.创建型设计模式-工厂模式
3.1.工厂设计模式简介
(1)简介
- 它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴漏创建逻辑,并且时通过使用一个共同的的接口来指向新创建的对象
(2)工厂模式的三种实现方式
- 简单工厂模式:通过传入相关的类型来返回相应的类,这种方式比较单一,可扩展性较差。
- 工厂方法模式:通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强。
- 抽象工厂模式:基于上述两种模式的扩展,支持细化产品
(3)应用场景
- 解耦:分离职责,把复杂对象的创建和使用的过程分开
- 复用代码,维护成本降低:
- 如果对象创建复杂且多出要用到,如果每处都进行编写,则很多重复代码,如果业务逻辑发生了改变,需用四处修改;
- 使用工厂模式统一创建,则只要修改工厂类即可,降低成本
3.2.简单工厂模式
(1)简介
- 简单工厂模式又称静态工厂模式,可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都是具有共同的父类。由于工厂方法是静态的方法,可以通过类名直接调用,只需传入简单的参数即可。
(2)核心组成
Factory:工厂类,简单工厂模式的核心,它负责实现创建所有的内部逻辑。
IProduct:抽象产品类,简单工厂模式所创建的所有对象的父接口,描述所有实例共有的公共方法。
Product:具体产品类,是简单工厂模式的创建目标。
(3)实现步骤
- 创建抽象产品类,里面有产品的抽象方法,由具体的产品类去实现
- 创建具体产品类,继承了他们的父类,并实现具体方法
- 创建工厂类,提供了一个静态方法createXXX用来生产产品,只需要传入你想产品名称
(4)编码实现
- 统一支付下单接口Pay
public interface Pay {
/**
* 统一下单接口
*/
void unifiedOrder();
}
- 微信支付下单实现类WechatPay
public class WechatPay implements Pay{
@Override
public void unifiedOrder() {
System.out.println("微信支付统一下单接口");
}
}
- 支付宝支付下单实现类AliPay
public class AliPay implements Pay {
@Override
public void unifiedOrder() {
System.out.println("支付宝支付统一下单接口");
}
}
- 统一创建工厂类SimplePayFactory
public class SimplePayFactory {
public static Pay createPay(String payType){
if(payType == null){
return null;
}
if("WECHAT_PAY".equalsIgnoreCase(payType)){
return new WechatPay();
}else if("ALI_PAY".equalsIgnoreCase(payType)){
return new AliPay();
}
//扩展更多
return null;
}
}
- 测试创建
public static void main(String[] args) {
Pay pay = SimplePayFactory.createPay("ALI_PAY");
pay.unifiedOrder();
}
(5)类图关系
- 创建顶层支付接口Pay。
- AliPay、WechatPay分别实现Pay接口。
- 创建工厂类,编写静态方法createPay(),根据传入的参数确定创建那种支付实现类。
(6)优点和缺点
- 优点
- 将对象的创建和对象本身的业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
- 缺点
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑没这一点于开闭原则是相违背的。
3.3.工厂方法模式
(1)简介
工厂方法模式又称工厂模式,是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的前提下引进新的产品,既满足开闭原则。
通过工厂父类定义负责创建产品的公共接口,通过子类来确定所需创建的类型。
相比简单工厂而言,此种方法具有更多的可扩展性和复用性,同时也增强了代码的可读性。
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类。
(2)核心组成
- IProduct:抽象产品类,描述所有实例所共有的公共接口。
- Product:具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个。
- IFactory:抽象工厂类,描述具体工厂的公共接口。
- Factory:具体工厂类,实现创建产品类对象,实现抽象工厂类的接口,如果又多个需要定义多个。
(3)编码实现
- 支付类型工厂接口PayFactory
public interface PayFactory {
Pay createPay();
}
- 支付宝类型工厂实现AliPayFactory
public class AliPayFactory implements PayFactory {
@Override
public Pay createPay() {
return new AliPay();
}
}
- 微信类型工厂实现WechatPayFactory
public class WechatPayFactory implements PayFactory {
@Override
public Pay createPay() {
return new WechatPay();
}
}
- 测试
public static void main(String[] args) {
PayFactory pay = new AliPayFactory();
pay.createPay().unifiedOrder();
}
(4)类关系图
创建支付接口Pay。
创建支付实现类AliPay、WechatPay。
创建支付创建工厂PayFactory。
创建工厂创建实现类AliPayFactory、WechatPayFactory。
客户端生产某个支付类,直接new 具体支付工厂类即可。
(5)优点和缺点
- 优点
- 符合开闭原则,增加一个产品类,只需要实现其父类接口,不会对原有的类进行修改。
- 符合单一职责原则,每个工厂只负责生产对应的产品。
- 使用者只需知道产品的抽象类,无需关心它的实现类,满足迪米特法则。
- 缺点
- 每增加一个产品就要增加一个类
- 每个产品都要有对应的具体工厂和具体产品类
3.4.抽象工厂模式
(1)简介
- 抽象工厂模式是基于简单工厂和工厂方法模式的扩展,是工厂方法模式的升级版,当需要创建的产品有多个产品线时使用抽象工厂模式是比较好的选择,抽象工厂模式在spring种应用最为广泛的一种设计模式。
(2)背景
- 工厂方法模式引入工厂等级结构,解决了简单工厂模式中工厂类职责过重的问题。
- 但工厂方法模式中每个工厂只创建一类具体类的对象,后续发展可能会导致工厂类过多,因此将一些相关的具体类组成一个“具体类族”,由同一个工厂来统一生产,强调的是一系列相关的产品对象。
(3)实现步骤
1.定义两个接口 Pay、Refund。
2.创建具体的Pay产品、创建具体的Refund产品。
3.创建抽象工厂OrderFactory接口,里面两个方法createPay、createRefund。
4.创建支付宝产品族AliOrderFactory,实现OrderFactory抽象工厂。
5.创建微信支付产品族WechatOrderFactory,实现OrderFactory抽象工厂。
6.定义一个超级工厂创造器,通过传递参数获取对应的工厂。
(4)类关系图
抽象工厂涉及到产品族,一个工厂创建这一族的所有产品。
创建支付接口,生产支付具体类,PayFactory,定义抽象方法统一下单方法unifiedOrder()。
创建支付实现类,实现PayFactory接口,实现具体的unifiedOrder()方法,AliPay、WechatPay。
创建退款接口,生产退款具体类,RetundFactory,定义抽象方法统一退款方法retund()。
创建退款实现类,实现RetundFactory接口,实现具体的retund()方法,AliRetund、WechatRetund。
定义OrderFactory,订单工厂中包含订单的支付createPay、订单退款createRetund()。
创建AliOrderFactory、WechatOrderFactory实现OrderFactory中的createPay()、createRetund()方法。
AliOrderFactory、WechatOrderFactory分别返回对应的支付、退款方法。
创建超级工厂类,传入参数返回相应的订单工厂实现类。
(5)编码实现
- 超级工厂OrderFactory
public interface OrderFactory {
PayFactory createPay();
RefundFactory createRefund();
}
- 支付工厂Pay
public interface Pay {
/**
* 统一下单接口
*/
void unifiedOrder();
}
- 退款工厂Retund
public interface Refund {
/**
* 退款
*/
void refund();
}
- 产品族具体实现AliOrderFactory
public class AliOrderFactory implements OrderFactory {
@Override
public PayFactory createPay() {
return new AliPay();
}
@Override
public RefundFactory createRefund() {
return new AliRefund();
}
}
- 产品组具体产品AliPay
public class AliPay implements PayFactory {
@Override
public void unifiedOrder() {
System.out.println("支付宝支付统一下单接口");
}
}
- 产品组具体产品AliRetund
public class AliRefund implements RefundFactory {
@Override
public void refund() {
System.out.println("支付宝退款");
}
}
- 产品族具体实现WechatOrderFactory
public class WechatOrderFactory implements OrderFactory {
@Override
public PayFactory createPay() {
return new WechatPay();
}
@Override
public RefundFactory createRefund() {
return null;
}
}
- 产品组具体产品WechatPay
public class WechatPay implements PayFactory {
@Override
public void unifiedOrder() {
System.out.println("微信支付统一下单接口");
}
}
- 产品组具体产品WechatRetund
public class WechatRefund implements RefundFactory {
@Override
public void refund() {
System.out.println("微信退款");
}
}
- 总创建工厂
public class FactoryProducer {
public static OrderFactory getFactory(String type){
if("WECHAT".equalsIgnoreCase(type)){
return new WechatOrderFactory();
}else if("ALI".equalsIgnoreCase(type)){
return new AliOrderFactory();
}
return null;
}
}
- 测试
OrderFactory ali = FactoryProducer.getFactory("ALI");
ali.createPay().unifiedOrder();
ali.createRefund().refund();
(6)优点和缺点
- 优点
- 当一个产品族中的多个对象被设计成一起工作时,它能保证使用方始终只使用同一个产品族中的对象
- 产品等级结构扩展容易,如果需要增加多一个产品等级,只需要增加新的工厂类和产品类即可, 比如增加银行支付、退款
- 缺点
- 产品族扩展困难,要增加一个系列的某一产品,既要在抽象的工厂和抽象产品里修改代码,不是很符合开闭原则
- 增加了系统的抽象性和理解难度
4.创建型设计模式-原型设计模式
4.1.原型模式简介
(1)简介
- 原型设计模式Prototype
- 是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能
- 工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程
- 应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式
(2)核心组成
- Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
- ConcretePrototype : 具体原型类
- Client: 让一个原型对象克隆自身从而创建一个新的对象
(3)应用场景
- 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
- 如果系统要保存对象的状态,做备份使用
(4)深浅拷贝
浅拷贝实现 Cloneable,深拷贝是通过实现 Serializable 读取二进制流
浅拷贝
如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象。
如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
通过覆盖Object类的clone()方法实现浅克隆
- 深拷贝
无论原型对象的成员变量是基本数据类型还是引用数据类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(serializable)等方式来实现。
- 原型模式是内存二进制流的拷贝,比new对象性能高很多。
4.2.原型模式案例实战
(1)编码实现
编写Person实体,浅克隆实现Cloneable,深克隆实现Serializable
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private List<String> list = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
//浅克隆方法
@Override
public Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
//深克隆方法
public Object deepClone(){
try{
//输出 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//输入 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Person copyObj = (Person)ois.readObject();
return copyObj;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
", list=" + list +
'}';
}
}
Person person1 = new Person();
person1.setName("李祥");
person1.setAge(20);
person1.getList().add("aaa");
//Person person2 = (Person) person1.deepClone();
Person person2 = (Person) person1.deepClone();
person2.getList().add("ccc");
System.out.println(person2);
- 调用浅克隆方法,运行接果
- 调用深克隆方法,运行接果
(2)优点和缺点
- 优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率。
- 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到到历史状态。
- 缺点
- 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,且当对象之间窜在多重的嵌套引用时,需要对每一层对象都必须支持深克隆。
5.创建型设计模式-建造者模式
5.1.建造者模式简介
(1)简介
- 建造者模式(Builder Pattern)
- 使用多个简单的对象一步一步构建称一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 允许用户只通过指定复杂对象的类型和内容就可以构建他们,不需要知道内部的具体构建细节
(2)类关系图
创建顶层构建者接口ComputerBuilder,抽象方法createCpu()、createMainBoard()、createDisk()、createPower()、createMemory()创建独享属性方法,createComputer()构建返回对象。
创建ComputerBuilder的实现类LowComputerBuilder、HeadComputerBuilder,持有Computer实例,实现各个方法返回实例。
创建指挥者ComputerDirector,创建方法createComputer(),将ComputerBuilder作为参数,返回不同对象。
(3)核心组成
- **Builder:**抽象建造者,定义多个通用方法和构建方法。
- **ConcreteBuilder:**具体建造者,可以有多个。
- **Director:**指挥者,控制整个组合过程,将需求交给建造者,由建造者去创建对象。
- **Product:**产品角色
5.2.建造者模式案例实战
(1)编码实现
- 构建者顶级接口ComputerBuilder
/**
* 声明建造者的顶层接口
*/
public interface ComputerBuilder {
void buildCpu();
void buildMainBoard();
void buildDisk();
void buildPower();
void buildMemory();
//返回创建Computer的方法
Computer createComputer();
}
- 低配Computer实现类LowComputerBuilder
**
* 具体的建造者,实现顶层的接口
*/
public class LowComputerBuilder implements ComputerBuilder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("低配CPU");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("低配MainBoard");
}
@Override
public void buildDisk() {
computer.setDisk("低配Disk");
}
@Override
public void buildPower() {
computer.setPower("低配Power");
}
@Override
public void buildMemory() {
computer.setMemory("低配Memory");
}
@Override
public Computer createComputer() {
return computer;
}
}
- 高配Computer实现类HeadComputerBuilder
/**
* 具体的建造者,实现顶层的接口
*/
public class HeadComputerBuilder implements ComputerBuilder{
private Computer computer = new Computer();
@Override
public void buildCpu() {
computer.setCpu("高配CPU");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("高配MainBoard");
}
@Override
public void buildDisk() {
computer.setDisk("高配Disk");
}
@Override
public void buildPower() {
computer.setPower("高配Power");
}
@Override
public void buildMemory() {
computer.setMemory("高配Memory");
}
@Override
public Computer createComputer() {
return computer;
}
}
- 产品对象Computer
public class Computer {
private String cpu;
private String memory;
private String mainBoard;
private String disk;
private String power;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
public String getDisk() {
return disk;
}
public void setDisk(String disk) {
this.disk = disk;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", mainBoard='" + mainBoard + '\'' +
", disk='" + disk + '\'' +
", power='" + power + '\'' +
'}';
}
}
- 指挥者Director
/**
* Director组装者,将产品和创建过程进行解耦,使用相同的创建过程创建不同的产品,是全程知道组装过程,具体细节还是builder去操作
*/
public class Director {
public static Computer createComputer(ComputerBuilder builder){
builder.buildCpu();
builder.buildDisk();
builder.buildMainBoard();
builder.buildMemory();
builder.buildPower();
return builder.createComputer();
}
}
- 测试
//指挥者创建电脑传入低配的电脑建造者
Computer lowComputer = Director.createComputer(new LowComputerBuilder());
//指挥者创建电脑传入高配的电脑建造者
Computer headComputer = Director.createComputer(new HeadComputerBuilder());
System.out.println(lowComputer);
System.out.println(headComputer);
(2)优点和缺点
- 优点
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,更加精细地控制产品的创建过程
- 增加新的具体建造者无须修改原有类库的代码,符合开闭原则
- 建造者模式结合链式编程来使用,代码上更加美观
- 缺点
- 建造者模式所创建的产品一般具有较多的共同点,如果产品差异大则不建议使用
(3)建造者模式与抽象工厂模式的比较
- 建造者模式返回一个组装好的完整产品,抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构没构成一个产品族
6.结构型设计模式-适配器模式
6.1.适配器模式简介
(1)简介
- 适配器模式(Adapter Pattern),作为两个不兼容接口的桥梁,属于结构型设计模式,适配器模式可以使原本由于接口不兼容而不能一起工作的类,一起工作。
(2)常见的几种适配器模式
- 类的适配器模式
- 想将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式
- 想将一个对象转化成满足另一个新接口的对象时,可以创建一个适配器类,持有原类一个实例,在适配器类的方法中,调用实例的方法就行。
- 接口适配器模式
- 不想实现一个接口中的所有方法时,可以创建一个Adapter,实现所有方法,在写别的类的时候,继承Adapter类。
(3)案例
JDBC给出一个客户端通用的抽象接口,每一个具体数据库厂商 如 SQL Server、Oracle、MySQL等,就会开发JDBC驱动,就是一个介于JDBC接口和数据库引擎接口之间的适配器软件
6.2.适配器模式案例
- 总结
- 在使用一些旧系统或者时类库时,经常会出现接口不兼容的问题,适配器模式在解决这类问题上占据优势。
6.3.接口适配器案例实战
(1)接口适配器
有些接口中有很多抽象的方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有事时比较浪费,应为并不是所有的方法都是我们需要的,有事只需要是线部分接口就行了。
(2)编码实现
- 顶级接口PayGateway
public interface PayGateway {
void unifiedOrder();
void refund();
void query();
void sendRedPack();
}
- 适配器类PayGatewayAdapter
//可以默认空实现,让继承的子类去实现
public class PayGatewayAdapter implements PayGateway {
@Override
public void unifiedOrder() {
}
@Override
public void refund() {
}
@Override
public void query() {
}
@Override
public void sendRedPack() {
}
}
- 实现类ProductVideoOrder
/**
* 订单类只需实现支付和退款,无需实现查询等功能
*/
public class ProductVideoOrder extends PayGatewayAdapter{
@Override
public void unifiedOrder() {
System.out.println("下单");
}
@Override
public void refund() {
System.out.println("退款");
}
}
- 测试
ProductVideoOrder productVideoOrder = new ProductVideoOrder();
productVideoOrder.refund();
productVideoOrder.unifiedOrder();
6.4.类适配器案例实战
- 要对一个老的类中新增一个方法,但是不想修改老的类,将一个老的类中的一个方法集成到一个新的类中。
目标原始类OldModule持有methodA方法。
创建目标类接口,持有原始类methodA的抽象,以及新的方法methodB()抽象。
创建适配器类,继承原始类,实现目标类接口,实现新的抽象方法methodB()方法。
客户端调用适配器类就可以集成调用methodA方法、和methodB方法。
(1)案例实战
- 目标原始类OldModule
public class OldModule {
public void methodA(){
System.out.println("Old methodA()");
}
}
- 新目标类接口TargetModule
public interface TargetModule {
/**
* 和需要适配的老的类的方法名相同
*/
void methodA();
/**
* 新的方法
*/
void methodB();
}
- 适配器类Adapter,继承OldModule,实现TargetModule接口
public class Adapter extends OldModule implements TargetModule {
@Override
public void methodB() {
System.out.println("New methodB()");
}
}
- 测试
ProductVideoOrder productVideoOrder = new ProductVideoOrder();
productVideoOrder.refund();
productVideoOrder.unifiedOrder();
(2)优点和缺点
- 优点
- 可以让两个没有关联的类一起运行,使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 增加灵活度,提高复用性,适配器类可以在多个系统使用,符合开闭原则。
- 缺点
- 整体类的调用链路增加,本来A可以直接调用C,使用适配器后,要先经过适配器类在调用C
7.结构型设计模式-桥接模式
7.1.桥接模式简介
(1)简介
- 与适配器模式类似,根据不同的场景进行搭配,桥接设计模式也是结构型设计模式。
- 将抽象部分与实现部分分离,是他们都可以独立的变化,通过组合来桥接其他的行为、维度。
(2)应用场景
- 系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性。
- 不想使用继承导致系统类的个数急剧增加的系统。
- 有时候一个类,可能拥有多个变化维度,使用桥接模式就可以解决这个问题,且解耦。
(3)继承方式与桥接模式对比
创建手机的抽象类Phone,持有Color的实例,定义抽象方法useColor()。
创建实现类SXPhone,实现run方法,显式调用父类的构造。
创建接口Color,定义抽象方法,userColor()。
RedColor、YellowColor分别实现Color接口,实现useColor()方法。
客户端在使用的时候直接将颜色的实例通过构造传给手机对象即可。
7.2.桥接模式案例实战
(1)编码实战
- 编写颜色接口Color
public interface Color {
void useColor();
}
- 编写颜色实现类RedColor、BlueColor、YellowColor
public class BlueColor implements Color {
@Override
public void useColor() {
System.out.println("蓝色");
}
}
public class RedColor implements Color {
@Override
public void useColor() {
System.out.println("红色");
}
}
public class YellowColor implements Color {
@Override
public void useColor() {
System.out.println("黄色");
}
}
- 编写Phone抽象类
public abstract class Phone {
//将color作为一个属性桥接进来
protected Color color;
public void setColor(Color color) {
this.color = color;
}
/**
* 运行方法
*/
abstract public void run();
}
- phone的具体实现类PGPhone、HWPhone、SXPhone
public class SXPhone extends Phone {
public SXPhone(Color color) {
super.setColor(color);
}
@Override
public void run() {
color.useColor();
System.out.println("三星手机");
}
}
public class HWPhone extends Phone {
public HWPhone(Color color){
super.setColor(color);
}
@Override
public void run() {
color.useColor();
System.out.println("华为手机");
}
}
public class PGPhone extends Phone {
public PGPhone(Color color){
super.setColor(color);
}
@Override
public void run() {
color.useColor();
color.useColor();System.out.println("苹果手机");
}
}
- 测试
Phone redHWPhone = new HWPhone(new RedColor());
redHWPhone.run();
Phone blueHWPhone = new HWPhone(new BlueColor());
blueHWPhone.run();
(2)优点和缺点
优点
- 抽象和分离实现。
- 扩展能力强,符合开闭原则。
缺点
- 增加系统的设计难度。
桥接模式用于设计的前期,精细化产品设计,适配器模式适用于实际完成之后,发现一些类不能一起工作。
8.结构型设计模式-组合模式
8.1.组合模式简介
(1)简介
- 组合设计模式又叫部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构,可以更好的实现管理操作。
- 组合模式是得用户可以使用一致的方法操作单个对象和组合对象
- 部分-整体对象的基本操作多数是一样的,但是应用还是会有不一样的地方。
- 核心:组合模式可以使用一棵树来表示
(2)应用场景
- 文件夹和文件,都有增加、删除等api,有层级管理关系。
- 公司后台部门的阶级组成等等,只要是想要表达对象的部分整体的层次机构都可以使用。
(3)角色
- 组合部件(Component):他是一个抽象接口,表示树根,(C://)
- 合成部件(Composite):和组合部件类似,也有自己的子节点,(文件夹)
- 叶子(Leaf):在组合中表示子节点对象,注意是没有子节点,(文件)
8.2.组合模式案例实战
(1)编码实现
- 创建节点抽象类Root
public abstract class Root {
private String name;
public Root(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void addFile(Root root);
public abstract void removeFile(Root root);
public abstract void display(int depth);
}
- 创建文件夹实体类Folder
public class Folder extends Root {
List<Root> folders = new ArrayList<>();
public Folder(String name) {
super(name);
}
@Override
public void addFile(Root root) {
folders.add(root);
}
@Override
public void removeFile(Root root) {
folders.remove(root);
}
@Override
public void display(int depth) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < depth; i++) {
stringBuffer.append("-");
}
System.out.println(stringBuffer.toString()+this.getName());
for (Root folder : folders) {
//每个下级两个横线
folder.display(depth+2);
}
}
}
- 创建文件实体类File
public class File extends Root {
public File(String name) {
super(name);
}
@Override
public void addFile(Root root) {
}
@Override
public void removeFile(Root root) {
}
@Override
public void display(int depth) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < depth; i++) {
stringBuffer.append("-");
}
System.out.println(stringBuffer.toString()+this.getName());
}
}
- 测试
Root root = new Folder("C://");
Root folder1 = new Folder("桌面");
Root folder2 = new Folder("公共");
Root folder3 = new Folder("图像");
Root folder4 = new Folder("lixiang");
Root file1 = new File("lixiang.txt");
root.addFile(folder1);
root.addFile(folder2);
root.addFile(folder3);
folder1.addFile(folder4);
folder4.addFile(file1);
root.display(0);
(2)优点和缺点
优点
- 客户端只需要面对一直的对象而不用考虑整体部分或者节点叶子的问题。
- 方便创建出复杂的层次结构。
缺点
- 客户端需要花费更多的时间清理之间的层次关系。
9.结构型设计模式-装饰器模式
9.1.装饰器模式简介
(1)简介
- 装饰器设计模式(Decorator Pattern)
- 属于结构型模式,它是作为现有的类的包装,允许向一个现有的对象添加新的功能,同时又不改变其结构
- 给对象增加功能,一般两种方式 继承或关联组合,将一个的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,比继承模式更加灵活
- jdk源码中用的最多装饰器模式的就是IO流
(2)装饰器模式的类图
创建抽象接口Component,被装饰类和装饰类共同实现Component。
创建装饰类的具体装饰类,具体装饰类中持有被装饰者的实例。
客户端创建具体装饰类,传入装饰者实例。
(3)角色(被装饰的类和装饰类都拥有相同的超类)
- **抽象组件(Component):**定义装饰方法的规范
- **被装饰者(ConcreteComponent):**Component的具体实现也就是我们要装饰的类
- **装饰者组件(Decorator):**定义具体装饰者的行为规范,和Component角色有相同的接口,持有组件Component对象的实例引用
- **具体实现物(ConcreteDecorator):**负责给构建对象装饰附加的功能
9.2.装饰器模式案例实战
(1)编码实战
- 顶层接口Car
public interface Car {
String getDec();
int getPrice();
}
- 被装饰实现类BMCar、ADCar
public class ADCar implements Car {
private String dec = "奥迪汽车";
@Override
public String getDec() {
return dec;
}
@Override
public int getPrice() {
return 200;
}
}
public class BMCar implements Car {
private String dec = "宝马汽车";
@Override
public String getDec() {
return dec;
}
@Override
public int getPrice() {
return 100;
}
}
- 装饰器类CarDecorator
public class CarDecorator implements Car {
private String dec = "汽车装饰器";
@Override
public String getDec() {
return dec;
}
@Override
public int getPrice() {
return 0;
}
}
- 具体实现装饰类TailCarDecorator
public class TailCarDecorator extends CarDecorator {
private Car car;
public TailCarDecorator(Car car){
this.car = car;
}
private String dec = "增加一个尾翼";
@Override
public String getDec() {
return car.getDec()+dec;
}
@Override
public int getPrice() {
return car.getPrice()+10;
}
}
- 测试
Car car = new TailCarDecorator(new BMCar());
System.out.println(car.getDec());
(2)优点和缺点
- 优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但装饰模式可以提供比继承更多的灵活性。
- 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无需改变,符合开闭原则。
- 缺点
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
- 增加系统的复杂度。
9.3.JDK源码中的装饰器模式
- InputStream IO流
- 抽象组件(Component):InputStream定义装饰方法的规范。
- 被装饰者(ConcreteComponent):FileInputStream、ByteArrayInputStream,被装饰的实现类。
- 装饰者组件(Decorator):FilterInputStream定义具体装饰者的行为规范,和Component角色有相同的接口,持有Component的实现的对象实例。
- 具体装饰物:BufferedInputStream、DataInputStream负责给Component的实现对象增加附加功能。
- 类图
10.结构型设计模式-代理模式
10.1.代理模式简介
(1)代理设计模式(Proxy Pattern)
- 为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式。
- 客户端并不直接调用真实对象,而是通过掉用代理,来间接调用真实的对象。
(2)角色
- Subject:抽象接口,真实对象和代理对象都要实现的一个抽象接口。
- Proxy:包含了对真实对象的引用,从而可以随意的操作真实对象的方法。
- RealProject:真实对象。
(3)类关系图
创建公共接口,代理类对象和真实类对象都实现接口。
客户端直接调用代理类对象。
10.2.代理模式案例实战
(1)编码实现
- 目标接口DigitalSell
public interface DigitalSell {
void sell();
}
- 真实类DigitalSellReal
public class DigitalSellReal implements DigitalSell {
@Override
public void sell() {
System.out.println("方法执行");
}
}
- 代理类DigitalSellProxy
public class DigitalSellProxy implements DigitalSell {
private DigitalSellReal digitalSellReal = new DigitalSellReal();
@Override
public void sell() {
System.out.println("前置增强");
digitalSellReal.sell();
System.out.println("后置增强");
}
}
- 测试类
DigitalSell digitalSellReal = new DigitalSellReal();
System.out.println("真实对象执行:");
digitalSellReal.sell();
System.out.println("------------------------------");
DigitalSell digitalSellProxy = new DigitalSellProxy();
System.out.println("代理对象执行:");
digitalSellProxy.sell();
(2)JDK动态代理
- 准备目标接口HelloService
/**
* 真实抽象类
*/
public interface HelloService {
String hello(String name);
}
- 目标接口的实现HelloServiceImpl
/**
* 真实实现类
*/
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello:"+name;
}
}
- 编写代理类JDKProxy,实现InvocationHandler接口,重写invoke方法
public class JDKProxy implements InvocationHandler {
//定义真实业务类对象
private Object object;
//通过构造方法设置真实业务类对象
public JDKProxy(Object object) {
this.object = object;
}
/**
* @param proxy 代理对象
* @param method 代理方法对象
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("PROXY:"+proxy.getClass().getName());
Object result = method.invoke(object, args);
System.out.println(method.getClass().getName());
return result;
}
}
- 测试
HelloService service = new HelloServiceImpl();
//生成代理类的class对象
Class<?> proxyClass = Proxy.getProxyClass(service.getClass().getClassLoader(), service.getClass().getInterfaces());
//创建自定义的InvocationHandler,传入真实类对象
InvocationHandler invocationHandler = new JDKProxy(service);
//获取代理类的构造器对象
Constructor constructor = proxyClass.getConstructor(new Class[] {InvocationHandler.class});
//反射创建代理对象
HelloService proxyService = (HelloService)constructor.newInstance(invocationHandler);
System.out.println(proxyService.hello("李祥"));
(3)GGLIB动态代理
- 不用接口,直接代理实现类HelloServiceImpl
/**
* 真实实现类
*/
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
return "Hello:"+name;
}
}
- 编写动态代理类,实现MethodInterceptor接口,重写intercept
public class CGLIBProxy implements MethodInterceptor {
//创建真实类对象的引用
private Object object;
//设置真是类对象
public CGLIBProxy(Object object) {
this.object = object;
}
//为目标类创建代理对象
public Object getProxyInstance(){
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(object.getClass());
//设置回调函数
en.setCallback(this);
//创建子类的代理对象
return en.create();
}
/**
*
* @param o 代理对象
* @param method 代理对象方法
* @param objects 参数
* @param methodProxy 方法的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object aSuper = methodProxy.invokeSuper(o, objects);
return aSuper;
}
}
- 测试
//创建目标对象
HelloServiceImpl helloService = new HelloServiceImpl();
HelloServiceImpl helloServiceProxy =(HelloServiceImpl) new CGLIBProxy(helloService).getProxyInstance();
System.out.println(helloServiceProxy);
System.out.println(helloServiceProxy.hello("李祥"));
(4)JDK动态代理和CGLIB动态代理的区别
JDK动态代理:要求目标对象实现一个,但是又的时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLIB动态代理。
CGLIB动态代理,他是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
JDK动态代理是自带的,CGLIB需要引入第三方包。
CGLIB动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理。
(5)优点和缺点
- 优点
- 可以在访问一个类时做一些控制,或增加功能。
- 操作代理类无需修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
- 缺点
- 增加系统的复杂性和调用的链路
11.结构型设计模式-外观模式
11.1.外观模式简介
(1)简介
外观设计模式(Facade Pattern):门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问的系统接口,定义一个高层的接口使得这个接口更加容易使用。
(2)应用场景
- 开发里面MVC三层架构,在数据访问层、业务逻辑层和表示层之间用interface接口进行相互交互,客户端访问只需访问接口,无需知道其内部实现方式。
- 对于复杂难以维护的老系统进行扩展,可以用外观设计模式。
- 需要对一个复杂的模块或子系统提供一个外界访问的接口,外界对子系统的访问只要黑盒操作。
(3)角色
- 外观角色(Facade):客户端可以调用这个角色的方法,这个外观方法知道多个子系统的功能和实际调用。
- 子系统角色(SubSystem):每个子系统都可以被客户端直接调用,子系统并不对外暴漏。
(4)类图关系
创建子系统A、子系统B、子系统C。
创建外观类Facade,分别持有A、B、C的实例。
11.2.外观模式案例实战
(1)代码实现
- 顶层接口IMessageManager
public interface IMessageManager {
void send();
}
- 子系统实现SmsMessageManager、MailMessageManager、DingDingMessageManager
public class DingDingMessageManager implements IMessageManager{
@Override
public void send() {
System.out.println("发送钉钉信息");
}
}
public class MailMessageManager implements IMessageManager{
@Override
public void send() {
System.out.println("发送邮件信息");
}
}
public class SmsMessageManager implements IMessageManager{
@Override
public void send() {
System.out.println("发送短信信息");
}
}
- 外观类MessageFacade
public class MessageFacade implements IMessageManager{
private IMessageManager dingDingMessageManager = new DingDingMessageManager();
private IMessageManager smsMessageManager = new SmsMessageManager();
private IMessageManager mailMessageManager = new MailMessageManager();
@Override
public void send() {
dingDingMessageManager.send();
smsMessageManager.send();
mailMessageManager.send();
}
}
- 测试
IMessageManager messageFacade = new MessageFacade();
messageFacade.send();
(2)优点和缺点
- 优点
- 减少了系统的相互依赖,提高了灵活性。
- 符合依赖倒转原则:针对接口编程,依赖于抽象而不依赖于具体。
- 符合迪米特法则:最少知道原则,一个实体应当尽量少的与其他实体间发生相互作用。
- 缺点
- 增加了系统的类和链路
- 不是很符合开闭原则,如果增加了新的骆及,需要修改facade外观类
12.结构型设计模式-享元模式
12.1.享元模式简介
(1)简介
- 享元设计模式(Flyweight Pattern):属于结构型设计模式,主要用于减少创建对象的数量,以减少内存占用和提高性能,他提供了减少对象数量从而改善应用所需的对象结构方式。享元模式尝试重试现有的同类对象,如果未找到匹配对象,则创建新对象。
(2)应用场景
- Java中的String,如果字符串常量池里有则返回,如果没有则创建一个字符串保存在字符串常量池中。
- 数据库连接池、线程池等。
- 系统中有大量类似的对象,或者需要用到缓冲池的时候可以用到享元设计模式,池化技术。
- 如果发现某个对象生成了大量细粒度的实例,并且这些实例除了几个参数之外基本都是相同的,如果把这些共享参数移到类外面,在方法调用时将他们传递进来,也可以通过共享对象,减少实例的个数。
(3)内部状态
- 不会随环境的改变而有所不同,是可以共享的。
(4)外部状态
- 不可以共享的,他随环境的改变而改变,因此外部状态是由客户端来支持(因为环境的变化一般是由客户端引起的)。
(5)角色
- **抽象享元角色:**为具体享元角色规定了必须实现的方法,而外部状态就是一参数的形式通过此方法传入。
- **具体享元角色:**实现抽象角色规定的方法。如果存在内部状态,就负责为内部状态提供存储空间。
- **享元工厂角色:**负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
- 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外部状态。
(6)类关系图
创建抽象享元角色FlyWeight。
创建具体享元角色ConcreteWebSite。
创建享元工厂,统一生产。
12.2.享元模式案例实战
(1)编码实战
- 顶层接口CloudWebSite,抽象享元角色
public interface CloudWebSite {
void run(Company company);
}
- 接口实现类ConcreteWebSite,具体享元角色
public class ConcreteWebSite implements CloudWebSite {
private String category;
public ConcreteWebSite(String category){
this.category = category;
}
@Override
public void run(Company company) {
System.out.println("网站分类:"+category+",公司:"+company.getName());
}
}
- 享元模式中具体实体,Company
public class Company {
private String name;
public Company(String name) {
this.name = name;
}
public Company() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
'}';
}
}
- 享元工厂类WebSiteFactory
public class WebSiteFactory {
//这快用map结构,模拟享元模式下如果有就直接返回,没有在重新创建
private Map<String,ConcreteWebSite> map = new HashMap<>();
public ConcreteWebSite getWebSiteByKey(String key){
if(map.containsKey(key)){
return map.get(key);
}else{
ConcreteWebSite webSite = new ConcreteWebSite(key);
map.put(key,webSite);
return webSite;
}
}
/**
* 获取站点数量
* @return
*/
public int getWebSiteCategorySize(){
return map.size();
}
}
- 测试
WebSiteFactory factory = new WebSiteFactory();
ConcreteWebSite webSite = factory.getWebSiteByKey("公司官网");
webSite.run(new Company("百度"));
ConcreteWebSite webSite2 = factory.getWebSiteByKey("公司官网");
webSite2.run(new Company("字节跳动"));
ConcreteWebSite webSite3 = factory.getWebSiteByKey("信息流");
webSite3.run(new Company("字节跳动"));
System.out.println(factory.getWebSiteCategorySize());
(2)优点和缺点
- 优点
- 大大减少了对象的创建,降低了程序内存的占用,提高效率。
- 缺点
- 提高了系统的复杂度,需要分离出内部状态和外部状态。
- 注意划分内部状态和外部状态,否则可能会引起线程安全的问题,必须有一个工厂类加以控制。
- 享元设计模式和原型、单例模式的区别
- 原型设计模式是指创建对象的种类,然后通过拷贝这些原型来创建新的对象。
- 单例设计模式保证一个类仅有一个实例。
13.行为型设计模式-策略模式
13.1.策略模式简介
(1)简介
- 定义一些列的算法,把他们一个个封装起来,并且是他们可以相互替换。比如天猫双十一,活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可以进行相互替换。
(2)应用场景
- 如果在一个系统中有许多类,它们之间的区别仅在于他们的行为,那么可以使用策略模式。
- 不希望暴漏复杂的、与算法相关的业务逻辑,可以使用策略模式,比如支付场景。
(3)角色
- **Context上下文:**屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
- **Strategy策略角色:**抽象策略角色,是对策略,算法家族的抽象。
- **ConcreteStrategy具体策略角色:**用于实现抽象策略中的操作,即实现具体的算法。
(4)类关系图
创建抽象策略接口Stategy。
创建具体策略实现类ConcreteStrategy。
创建上下文Context类,持有策略实例,传入具体策略实例执行不同的策略。
13.2.策略模式案例实战
(1)编码实战
- 策略抽象类Strategy
public abstract class Strategy {
/**
* 策略抽象类,用于不同的策略具体实现计算价格
* @param productOrder
* @return
*/
public abstract double computePrice(ProductOrder productOrder);
}
- 策略具体实现NormalActivity、DiscountActivity、VoucherActivity
/**
* 无策略
*/
public class NormalActivity extends Strategy {
@Override
public double computePrice(ProductOrder productOrder) {
return productOrder.getPrice();
}
}
/**
* 折扣策略
*/
public class DiscountActivity extends Strategy {
private double rate;
public DiscountActivity(double rate) {
this.rate = rate;
}
@Override
public double computePrice(ProductOrder productOrder) {
return productOrder.getPrice() * rate;
}
}
/**
* 折扣策略
*/
public class VoucherActivity extends Strategy {
private double voucher;
public VoucherActivity(double voucher) {
this.voucher = voucher;
}
@Override
public double computePrice(ProductOrder productOrder) {
if (this.voucher<productOrder.getPrice()){
return productOrder.getPrice() - this.voucher;
}else{
return 0;
}
}
}
- 策略上下文PromotionContext
/**
* 策略调用上下文
*/
public class PromotionContext {
private Strategy strategy;
public PromotionContext(Strategy strategy) {
this.strategy = strategy;
}
public double executeStrategy(ProductOrder order){
return strategy.computePrice(order);
}
}
- 测试
ProductOrder order = new ProductOrder(888,1,1);
PromotionContext context;
//无优惠策略
context = new PromotionContext(new NormalActivity());
System.out.println(context.executeStrategy(order));
//折扣优惠策略
context = new PromotionContext(new DiscountActivity(0.8));
System.out.println(context.executeStrategy(order));
//满减优惠策略
context = new PromotionContext(new VoucherActivity(200));
System.out.println(context.executeStrategy(order));
(2)优点和缺点
- 优点
- 满足开闭原则,当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的具体策略的实例
- 避免使用多重条件判断,如果不用策略模式可能会使用多重条件语句不利于维护,和工厂模式的搭配使用可以很好地消除代码if-else的多层嵌套(工厂模式主要是根据参数,获取不同的策略)
- 缺点
- 策略类数量会增多,每个策略都是一个类,复用的可能性很小
- 对外暴露了类所有的行为和算法,行为过多导致策略类膨胀
14.行为型设计模式-模板方法模式
14.1.模板方法模式简介
(1)简介
- 模板方法模式Template Method:定义一个操作中的算法骨架,将算法的一些步骤延延迟到子类中,使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定的步骤,属于行为模式。
(2)应用场景
javaweb里面的servlet,HttpService类提供了一个service()方法。
有多个子类共有逻辑相同的方法,可以考虑做为模板方法。
设计一个系统时知道了算法所需的关键步骤,且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,可以延迟到子类进行完成。
(3)角色
- 抽象模板(Abstract Template):定义一个模板方法,这个模板方法一般是一个具体的方法,给出一个顶级的算法骨架,而逻辑骨架的组成步骤在相应的抽象操作中,推迟到子类实现。
- 模板方法:定义了算法的骨架,按照某种顺序调用其包含的基本方法。
- 基本方法:是整个算法中的一个步骤,包括抽象方法和具体方法。
- 抽象方法:在抽象类中声明,有具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
- 具体模板(Concrete Template):实现父类所定义的一个或者多个抽象方法,他们是一个顶级算法逻辑的组成步骤。
(4)类关系图
创建抽象类,定义不同的模板执行的抽象方法,定义一个final类型的模板执行方法construction(),规定其他抽象方法的执行顺序。
创建具体的模板类实现抽象类的方法,coding()、test()、online()。
客户端调用父类的construction()方法,具体coding()、test()、online()调用子类的实现。
14.2.模板方法模式案例实战
(1)编码实战
- 顶层抽象模板类,定义方法的执行顺序AbstractProjectManager
public abstract class AbstractProjectManager {
public final void construction(){
design();
coding();
test();
online();
}
/**
* 具体方法,需求设计
*/
public void design(){
System.out.println("需求设计30天");
}
/**
* 抽象方法,编码,每个系统的编码不一样
*/
public abstract void coding();
/**
* 抽象方法,测试,每个系统的测试方式不一样
*/
public abstract void test();
/**
* 抽象方法,上线,每个系统的上线方式不一样
*/
public abstract void online();
}
- 模板的具体实现类,PayServiceProjectManager 、UserServiceProjectManager
public class UserServiceProjectManager extends AbstractProjectManager {
@Override
public void coding() {
System.out.println("编码50天");
}
@Override
public void test() {
System.out.println("自动化测试、功能测试");
}
@Override
public void online() {
System.out.println("全量上线");
}
}
public class PayServiceProjectManager extends AbstractProjectManager {
@Override
public void coding() {
System.out.println("编码90天");
}
@Override
public void test() {
System.out.println("自动化测试、功能测试、安全测试");
}
@Override
public void online() {
System.out.println("灰度上线、AB双切");
}
}
- 测试
AbstractProjectManager manager;
manager = new PayServiceProjectManager();
manager.construction();
System.out.println("-------------------------");
manager = new UserServiceProjectManager();
manager.construction();
(2)优点和缺点
- 优点
- 扩展性好,对不变的代码进行封装,对可变的代码进行扩展,符合开闭原则。
- 提高代码复用性,将相同部分的代码放在抽象的父类中,将不同的代码放入不同的子类中。
- 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反转控制。
- 缺点
- 每一个不同的实现都需要一个子类来实现,导致系统的类的个数增多。
(3)模板方法和建造者的区别
- 两者很大交集,建造者模式比模板方法模式多了一个指挥类,该类体现的是模板方法中抽象类的固定算法的共嗯那个,是一个创建对象的固定算法。
15.行为型设计模式-观察者模式
15.1.观察者模式简介
(1)简介
- 观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并且自动更新,也叫做发布订阅模式Publish/Subscribe,属于行为型模式。
(2)应用场景
- 消息通知:邮箱通知、微信朋友圈,监听观察事件。
- 当一个对象的改变需要同时改变其他对象,且他不知道具体有多少个对象有待改变的时候,考虑使用观察者模式。
(3)角色
- Subject主题:持有多个观察者对象的引用,抽象主题提供了一个接口可以增加和删除观察者对象,有一个观察者数组,并实现增删以及通知操作。
- **Observer抽象观察者:**为具体观察者定义一个接口,在得到主题的通知时更新自己。
- **ConcreteSubject具体主题:**将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知。
- **ConcreteObserver具体观察者:**实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态保持一致。
(4)类关系图
定义抽象主题类Subject,其中有addObserver、deleteObserver、notifyAllObservers()方法,doSomethings()为抽象方法留给具体主题实现。
具体主题类ConcreteSubject实现doSomethings(),调用父类的notifyAllObservers(),通知观察者们,表示不同主题要做的具体事情。
定义抽象观察者接口Observer定义抽象方法update()。
创建具体的观察者,实现update()方法,具体观察者收到主题doSomething方法执行后具体的执行动作。
将观察者设置到主题中调用addObserver()方法。
15.2.观察者模式案例实战
(1)编码实战
- 抽象类主题Subject
public abstract class Subject {
private List<Observer> list = new ArrayList<>();
/**
* 增加观察者
*/
public void addObserver(Observer observer){
list.add(observer);
}
/**
* 删除观察者
*/
public void deleteObserver(Observer observer){
list.remove(observer);
}
/**
* 通知观察者
*/
public void notifyAllObserver(){
for (Observer observer : list) {
observer.update();
}
}
/**
* 定义抽象方法,用于子类实现
*/
public abstract void doSomething();
}
- 具体主题实现类BossConcreteSubject
public class BossConcreteSubject extends Subject {
@Override
public void doSomething(){
System.out.println("老板正在悄悄的走来");
//调用父类通知观察者
super.notifyAllObserver();
}
}
- 观察者接口Observer
/**
* 观察者接口
*/
public interface Observer {
/**
* 抽象接口,当主题发生变化时,观察者监听随之发生响应
*/
void update();
}
- 具体观察者实现类LXConcreteObserver、ZSConcreteObserver
public class ZSConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("张三收起了正在播放抖音的手机,开始假装敲码");
}
}
public class LXConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("李祥收起了正在播放抖音的手机,开始假装敲码");
}
}
- 测试
//创建老板对象,主题对象
BossConcreteSubject subject = new BossConcreteSubject();
//创建张三对象,观察者对象
Observer zsConcreteObserver = new ZSConcreteObserver();
//创建李祥对象,观察者对象
Observer lxConcreteObserver = new LXConcreteObserver();
//将观察者加入主题
subject.addObserver(zsConcreteObserver);
subject.addObserver(lxConcreteObserver);
subject.doSomething();
(2)优点和缺点
- 优点
- 降低了目标与观察者之间的耦合关系,目标与观察者之间建立了一套触发机制。
- 观察者和被观察者是抽象耦合的。
- 缺点
- 观察者和观察目标之间有循环依赖的话,会触发他们之间进行循环调用,可能导致系统崩溃。
- 一个被观察者对象有很多的直接和简介的观察者的话,将所有的观察者都会收到通知的时候会花费很多时间。
16.行为型设计模式-责任链模式
16.1.责任链模式简介
(1)简介
- 责任链设计模式(Chain of Responsibility Pattern):客户端发出一个请求,链上的对象都有机会来处理这个请求,而客户端不需要知道谁是具体的处理对象。
- 让多个对象由机会处理请求,避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条调用链,并沿着这个调用链传递该请求,直到有一个对象处理它才终止。符合条件处理请求,不符合传递到下一个节点。
(2)应用场景
- Apache Tomcat对Encoding编码处理,SpringBoot中的拦截器。
- 多个对象处理同一请求,但是具体由那个对象处理是运行时刻决定的,这种对象就可以用责任链模式。
- 日志输出级别debug->info->warning->error。
(3)角色
- **Handler抽象处理者:**定义了一个处理请求的接口。
- **ConcreteHandler具体处理者:**处理所负责的请求,可访问它的后续节点,如果可处理该请求就处理,否则就转发给后续节点。
(4)类关系图
- 创建抽象类RiskControlManager持有自身的引用,用于设置更高一级的规则,抽象方法handler()用于不同的规则实现。
- 创建不同的规则实现类,实现handler(),如果符合规则就处理,部分和就调用父类的handler,会具体下发到某一个规则类中。
- 客户端调用
16.2.责任链模式案例实战
(1)业务流程图
(2)编码实战
- 请求实体类Request
public class Request {
private String requestType;
private int money;
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public Request(String requestType, int money) {
this.requestType = requestType;
this.money = money;
}
public Request() {
}
}
- 请求类型枚举类控制指定请求时才会进入风控处理规则RequestType
public enum RequestType {
ACCOUNT(10010,"转账"),
PAY(10011,"支付");
private int code;
private String name;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
RequestType(int code, String name) {
this.code = code;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
RequestType() {
}
}
- 抽象风控级别类RiskControlManager
public abstract class RiskControlManager {
/**
* 设置风控级别的名称
*/
protected String name;
/**
* 设置更高一级的风控级别
*/
protected RiskControlManager controlManager;
public RiskControlManager(String name) {
this.name = name;
}
public void setRiskControlManager(RiskControlManager controlManager){
this.controlManager = controlManager;
}
/**
* 抽象方法,由具体的风控级别实现
*/
public abstract void handler(Request request);
}
- 具体风控实现类FirstRiskControlManager、SecondRiskControlManager、ThirdRiskControlManager
public class FirstRiskControlManager extends RiskControlManager {
public FirstRiskControlManager(String name) {
super(name);
}
@Override
public void handler(Request request) {
if(RequestType.valueOf(request.getRequestType())!=null && request.getMoney()<1000){
System.out.println("请输入支付密码");
System.out.println(name+":"+request.getRequestType()+",金额:"+request.getMoney());
}else{
//交给下一个节点处理
if(controlManager!=null){
controlManager.handler(request);
}
}
}
}
public class SecondRiskControlManager extends RiskControlManager {
public SecondRiskControlManager(String name) {
super(name);
}
@Override
public void handler(Request request) {
if(RequestType.valueOf(request.getRequestType())!=null && request.getMoney()>=1000 && request.getMoney()<=10000){
System.out.println("请输入支付密码,确认短信验证码");
System.out.println(name+":"+request.getRequestType()+",金额:"+request.getMoney());
}else{
//交给下一个节点处理
if(controlManager!=null){
setRiskControlManager(controlManager);
}
}
}
}
public class ThirdRiskControlManager extends RiskControlManager {
public ThirdRiskControlManager(String name) {
super(name);
}
@Override
public void handler(Request request) {
if(RequestType.valueOf(request.getRequestType())!=null && request.getMoney()>10000){
System.out.println("请输入支付密码,确认刷脸支付");
System.out.println(name+":"+request.getRequestType()+",金额:"+request.getMoney());
}else{
//交给下一个节点处理
if(controlManager!=null){
controlManager.handler(request);
}
}
}
}
- 测试
//先创建三个风控对象
RiskControlManager firstRiskControlManager = new FirstRiskControlManager("初级风控");
RiskControlManager secondRiskControlManager = new SecondRiskControlManager("中级风控");
RiskControlManager thirdRiskControlManager = new ThirdRiskControlManager("高级风控");
//风控规则建立联系,设置各自下一级风控级别
firstRiskControlManager.setRiskControlManager(secondRiskControlManager);
secondRiskControlManager.setRiskControlManager(thirdRiskControlManager);
Request request = new Request(RequestType.ACCOUNT.name(),1000);
firstRiskControlManager.handler(request);
(3)优点和缺点
- 优点
- 客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者 降低了耦合度。
- 通过改变链内的调动它们的次序,允许动态地新增或者删除处理类,比较很方便维护。
- 增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则。
- 每个类只需要处理自己该处理的工作,明确各类的责任范围,满足单一职责原则。
- 缺点
- 处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
- 不能保证请求一定被接收。
- 如果链路比较长,系统性能将受到一定影响,而且在进行代码调试时不太方便。
17.行为型设计模式-命令模式
17.1.命令模式简介
(1)简介
- 命令设计模式(Command Pattern):请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的对象,并把该命令传给相应的对象执行命令,属于行为型模式。
- 命令模式是一种特殊的策略模式,体现的是多个策略执行的问题,而不是选择的问题。
(2)应用场景
- 日常每个界面、按钮、键盘时间操作都是命令模式。
(3)角色
- **抽象命令(Command):**需要执行的所有命令都在这里生明。
- **具体命令(ConcreteCommand):**定义一个接收者和行为之间的弱耦合,实现execute()方法,负责调用接收者的相应操作,execute()方法通常叫做执行方法。
- **接收者(Receiver):**负责具体实施和执行一个请求,命令传递到这里的是应该被执行的,实时和执行请求的方法叫做行动方法。
- **请求者(Invoker):**负责调用命令对象执行请求,相关的方法叫做行动方法。
- **客户端(Client):**创建一个具体命令(ConcreteCommand)对象确定其接收者。
(4)类关系图
创建命令的接收者,定义执行的方法on()、off()、change()。
创建命令接口Commond,定义抽象方法exectue()。
创建命令具体实现类,OnCommond、OffCommod、ChangeCommond实现exectue()方法持有命令接收者实例不同的命令实例执行不同的命令接收者中的方法。
创建命令请求者持有命令的实例,通过传入不同的命令示例接收者执行不同的命令操作。
17.2.命令模式案例实战
(1)编码流程
- 创建命令接收者TVReceiver
/**
* 命令接收者
*/
public class TVReceiver {
public void on(){
System.out.println("电视开启");
}
public void off(){
System.out.println("电视关闭");
}
public void change(){
System.out.println("电视换台");
}
}
- 命令的抽象接口,用于实现不同的具体命令Command
public interface Command {
//命令执行方法
void execute();
}
- 具体的命令实现,ChangeCommand、OnCommand、OffCommand
/**
* 具体命令实现类
*/
public class OnCommand implements Command {
/**
* 设置命令的接收者
*/
private TVReceiver receiver;
public void setReceiver(TVReceiver receiver) {
this.receiver = receiver;
}
public OnCommand(TVReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("执行开机命令:");
receiver.on();
}
}
/**
* 具体命令实现类
*/
public class OffCommand implements Command {
/**
* 设置命令的接收者
*/
private TVReceiver receiver;
public void setReceiver(TVReceiver receiver) {
this.receiver = receiver;
}
public OffCommand(TVReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("执行关机命令:");
receiver.off();
}
}
/**
* 具体命令实现类
*/
public class ChangeCommand implements Command {
/**
* 设置命令的接收者
*/
private TVReceiver receiver;
public void setReceiver(TVReceiver receiver) {
this.receiver = receiver;
}
public ChangeCommand(TVReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("执行换台命令:");
receiver.change();
}
}
- 命令的发起者(请求者)AppInvoker
/**
* 发起命令者
*/
public class AppInvoker {
/**
* 设置需要执行的命令
*/
private Command onCommand;
private Command offCommand;
private Command changeCommand;
public void setOnCommand(Command onCommand) {
this.onCommand = onCommand;
}
public void setOffCommand(Command offCommand) {
this.offCommand = offCommand;
}
public void setChangeCommand(Command changeCommand) {
this.changeCommand = changeCommand;
}
public void on(){
onCommand.execute();
}
public void off(){
offCommand.execute();
}
public void change(){
changeCommand.execute();
}
}
- 测试
//创建命令的接收者
TVReceiver receiver = new TVReceiver();
//创建命令对象,绑定接收者,确定是哪一个接收者执行命令
Command onCommand = new OnCommand(receiver);
Command offCommand = new OffCommand(receiver);
Command changeCommand = new ChangeCommand(receiver);
//创建命令发号者,绑定命令对象
AppInvoker appInvoker = new AppInvoker();
appInvoker.setChangeCommand(changeCommand);
appInvoker.setOffCommand(offCommand);
appInvoker.setOnCommand(onCommand);
appInvoker.on();
System.out.println();
appInvoker.change();
System.out.println();
appInvoker.off();
(2)优点和缺点
- 优点
- 调用者角色与接收者角色之间没有任何依赖关系,不需要了解到底是那个接收者执行,降低了系统的耦合度。
- 扩展性强,新的命令可以很容易的添加到老的系统中去。
- 缺点
- 过多的命令模式会导致某些系统有过多的具体命令类。
18.行为型设计模式-迭代器模式
18.1.迭代器模式简介
(1)简介
- 迭代器设计模式(iterator Pattern):提供一种方法顺序访问一个聚合对象中各个元素,而又无需暴漏该对象的内部实现,属于行为型设计模式。JDK源码中集合框架用的最多,可以将集合看成一个容器,迭代器的作用就是把集合中的对象一个一个遍历出来。
(2)应用场景
- 迭代器模式是与集合共存的,只要实现一个集合,就需要同时提供这个集合的迭代器,就像java中的Collection,List,Map等都有自己的迭代器。
(3)角色
- **抽象角色(Aggregate):**提供创建具体迭代器角色的接口,一般是接口,包好一个iterator()方法,例如java中的Collection接口,List接口,Set等。
- **具体容器角色(ConcreteAggregate):**实现抽象容器的具体实现类,比如List接口的有序表现ArrayList。
- **抽象迭代器角色(iterator):**负责定义访问和遍历元素的接口,包括几个核心方法,取得下一个元素的方法next(),判断是否遍历结束的方法isDone()。
- **具体迭代器角色(ConcreteIterator):**实现迭代器接口中定义的方法,并要记录遍历中的当前位置,完成集合的迭代。
(4)类关系图
创建抽象容器类,定义抽象方法add()、remove()、iterator()方法。
创建容器实现类实现add()、remove()、iterator()方法。
创建抽象迭代器类,Iterator,定义抽象方法hasNext()、next()。
创建具体迭代器类ConllectionIterator实现asNext()、next()方法。
18.2.迭代器模式案例实战
(1)编码实战
- 定义迭代器接口Iterator
public interface Iterator {
/**
* 判断是否有下一个元素
* @return
*/
boolean hasNext();
/**
* 获取下一个元素
* @return
*/
Object next();
}
- 迭代器的具体实现类ConctreteIterator
public class ConcreteIterator implements Iterator {
//定义一个List集合
private List list;
//定义下标
private int index = 0;
public ConcreteIterator(List list) {
this.list = list;
}
//判断是否有下一个元素
@Override
public boolean hasNext() {
return index != list.size();
}
//返回下一个元素
@Override
public Object next() {
if(this.hasNext()){
Object object = this.list.get(index);
index++;
return object;
}
return null;
}
}
- 定义容器接口ICollection
public interface ICollection {
void add(Object object);
void remove(Object object);
Iterator iterator();
}
- 容器实现类MyCollection
public class MyCollection implements ICollection {
private List list = new ArrayList();
@Override
public void add(Object object) {
list.add(object);
}
@Override
public void remove(Object object) {
list.remove(object);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(list);
}
}
- 测试
ICollection collection = new MyCollection();
collection.add("李祥1号");
collection.add("李祥2号");
collection.add("李祥3号");
collection.add("李祥4号");
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
(2)优点和缺点
优点
- 可以做到不爆露集合的内部结构,又可让外部代码透明的访问集合内部的数据。
- 支持以不同的方式遍历一个聚合对象。
缺点
- 对于比较简单的遍历,使用迭代器遍历较为繁琐。
- 迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常。
19.行为型设计模式-备忘录模式
19.1.备忘录模式简介
(1)简介
- 备忘录设计模式(Memento Pattern):在不破坏封闭的前提下,获取一个对象的内部状态,保存对象的某个状态,以便在适当的时候恢复对象,又叫做快照模式,属于行为模式。
- 备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问。
(2)应用场景
- git代码版本回滚
- 数据库事务回滚
- 虚拟机快照功能
(3)角色
- Originator:发起者,记录当前的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态的所有数据,可以根据需要决定Memento存储自己的哪些内部状态。
- Memento:备忘录,负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态,属性和Originator保持一致。
- Caretaker:管理者,对备忘录进行管理,保存和提供备忘录。
(4)类关系图
类的发起者SystemOriginator,其中包含saveSnapshot()保存当前状态、resumeSnapshot()返回之前保存的状态。
备忘录类持有和发起者类相同的属性,做存储。
备忘录管理者类持有SystemStateMemento实例,管理SystemStateMemento备忘。
19.2.备忘录模式案例实战
(1)编码实现
- 对象发起者SystemOrginator
/**
* 对象发起者
*/
public class SystemOriginator {
private int storage = 100;
public void display(){
System.out.println("当前内存剩余:"+storage);
}
public void run(){
this.storage = this.storage-10;
}
/**
* 保存快照
*/
public SystemStateMemento saveSnapshot(){
return new SystemStateMemento(storage);
}
/**
* 恢复快照
*/
public void resumeSnapshot(SystemStateMemento memento){
this.storage = memento.getStorage();
}
}
- 备忘录类SystemStateMemento
/**
* 备忘录类
*/
public class SystemStateMemento {
private int storage;
public SystemStateMemento(int storage) {
this.storage = storage;
}
public int getStorage() {
return storage;
}
public void setStorage(int storage) {
this.storage = storage;
}
}
- 备忘录类SystemStateCretaker
/**
* 备忘录管理者
*/
public class SystemStateCaretaker {
private SystemStateMemento systemStateMemento;
public SystemStateMemento getSystemStateMemento() {
return systemStateMemento;
}
public void setSystemStateMemento(SystemStateMemento systemStateMemento) {
this.systemStateMemento = systemStateMemento;
}
}
- 测试
//创建目标类,快照的发起者
SystemOriginator systemOriginator = new SystemOriginator();
System.out.println("开机");
systemOriginator.display();
System.out.println("添加快照");
//调用保存快照的方法
SystemStateMemento systemStateMemento = systemOriginator.saveSnapshot();
//将保存的快照设置到快照管理者中
SystemStateCaretaker systemStateCaretaker = new SystemStateCaretaker();
systemStateCaretaker.setSystemStateMemento(systemStateMemento);
systemOriginator.run();
systemOriginator.run();
systemOriginator.run();
System.out.println("使用一段时间");
systemOriginator.display();
System.out.println("恢复快照");
systemOriginator.resumeSnapshot(systemStateCaretaker.getSystemStateMemento());
systemOriginator.display();
(2)优点和缺点
- 优点
- 给用户提供了一种可以恢复状态的机制。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
- 缺点
- 消耗更多的资源,而且每一次保存都会消耗一定的内存。
20.行为型设计模式-状态模式
20.1.状态模式简介
(1)简介
- 状态设计模式(State Pattern):对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为,属于行为模式。
- 允许一个对象在其内部状态改变时改变它的行为。
- 状态模式是策略模式的孪生兄弟,他们的UML图是一样的,但实际上解决的是不同情况的两种场景问题。
(2)应用场景
- 一个对像的行为取决于它的状态,并且他必须在运行时刻根据状态改变它的行为。
- 代码中包含大量与对象有关的条件语句,比如一个操作中含有庞大的多分支的if else语句,且这些分支依赖于该对象的状态。
- 电商订单状态:未支付、已支付、派送中等,各个状态处理不同的事情。
(3)角色
- Context上下文:定义了客户程序需要的接口并维护一个具体状态角色的实例,将于状态相关的操作委托给当前的ConcreteState对象来处理。
- State抽象状态类:定义一个接口以封装与Context的一个特定状态相关的行为。
- ConcreteState具体状态类:实现抽象状态定义的接口。
(4)类关系图
状态接口OrderState,定义抽象方法handler()。
具体的状态实现类NewOrderState、PayOrderState、SendOrderState实现OrderState接口实现具体的handler方法。
OrderContext上下文类,持有状态引用,调用set方法中调用不同状态中的handler方法。
20.2.状态模式案例实战
(1)编码实现
- 定义状态接口State
/**
* 状态接口
*/
public interface OrderState {
/**
* 子类实现,不用的状态处理逻辑方法
*/
void handler();
}
- 定义状态的具体实现NewOrderState、PayOrderState、SendOrderState
public class NewOrderState implements OrderState {
@Override
public void handler() {
System.out.println("新订单入库");
System.out.println("调用客服服务");
}
}
public class PayOrderState implements OrderState {
@Override
public void handler() {
System.out.println("订单支付");
System.out.println("调用客服服务");
}
}
public class SendOrderState implements OrderState {
@Override
public void handler() {
System.out.println("订单发货");
System.out.println("调用客服服务");
System.out.println("调用短信服务");
}
}
- 定义状态的上下文OrderContext
/**
* 状态上下文,设置不同的状态执行不同的逻辑
*/
public class OrderContext {
private State state;
public void setState(State state) {
this.state = state;
System.out.println("当前状态为:"+state.getClass().toString().substring(state.getClass().toString().lastIndexOf(".")+1));
state.handler();
System.out.println();
}
}
- 测试
OrderContext context = new OrderContext();
context.setState(new NewOrderState());
context.setState(new PayOrderState());
context.setState(new SendOrderState());
(2)优点和缺点
- 优点
- 只需要改变对象状态即可改变对象的行为。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
- 缺点
- 状态模式的使用会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码混乱。
- 对开闭原则不是很友好。
- 状态模式和策略模式的区别
- 状态模式关注的是对于不同的状态做不同的事情,不同状态不能切换。
- 策略模式是针对同一种事情的不同解决办法,不同的策略可以进行切换。
21.不常用的设计模式
21.1.访问者模式
(1)简介
- 访问者模式(Visitor Pattern):使用一个访问者类,改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者的改变而改变,属于行为型设计模式。
(2)应用场景
将数据结构与数据操作分离。
稳定数据结构和一边的操作耦合问题。
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
(3)优点和缺点
- 优点
- 符合单一职责,可扩展性,灵活性。
- 缺点
- 具体元素对访问者公布细节,违反了迪米特法则。
- 违反依赖倒转原则,没有依赖于抽象。
- 具体元素变更比较困难。
21.2.中介者模式
(1)简介
- 中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
(2)应用场景
系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
(3)优点和缺点
优点
- 降低了类的复杂度,将一对多转化成了一对一。
- 各个类之间的解耦。
- 符合迪米特原则。
缺点
- 中介者会庞大,变得复杂难以维护。
21.3.解释器模式
(1)简介
- 解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
(2)应用场景
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
一些重复出现的问题可以用一种简单的语言来进行表达。
一个简单语法需要解释的场景。
(3)优点和缺点
优点
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式。
- 易于实现简单文法。
缺点
- 可利用场景比较少。
- 对于复杂的文法比较难维护。
- 解释器模式会引起类膨胀。
- 解释器模式采用递归调用方法。
22.设计模式在源码和框架的应用
22.1.JDK源码中的设计模式
迭代器设计模式:Iterator类
装饰器设计模式:BufferedInputStream类
单例设计模式:Runtime类
建造者设计模式:StringBuilder类
适配器设计模式:JDBC数据库驱动
享元设计模式:String类
策略设计模式:Comparator接口常用的compare()方法