admin管理员组文章数量:1539736
单例模式
简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。
频繁创建对象、管理对象是一件耗费资源的事,我们只需要创建一个对象来用就足够了!
能使用一个对象来做就不用实例化多个对象!这就能减少我们空间和内存的开销~
编写单例模式的代码其实很简单,就分了三步:
- 将构造函数私有化
- 在类的内部创建实例
- 提供获取唯一实例的方法
public class Java3y {
// 1.将构造函数私有化,不可以通过new的方式来创建对象
private Java3y(){}
// 2.在类的内部创建自行实例
private static Java3y java3y = new Java3y();
// 3.提供获取唯一实例的方法
public static Student getJava3y() {
return java3y;
}
}
getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。单例模式实现起来也很容易,直接看demo吧
懒汉式
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
按照我的习惯,我恨不得写满注释,怕你们看不懂,但是这个代码实在太简单了,所以我没写任何注释,如果这几行代码你都看不明白的话,那你可以洗洗睡了,等你睡醒了再来看我的博客说不定能看懂。
上面的是最基本的写法,也叫懒汉写法(线程不安全)下面我再公布几种单例模式的写法:
懒汉式写法(线程安全版)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式写法
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
在单例模式的饿汉式实现中,使用static
关键字修饰实例的原因主要有以下几点:
-
全局唯一: 通过
static
修饰的实例变量,确保在整个应用程序中只创建一次实例,即单例。在上述代码中,INSTANCE就是一个静态的单例实例,不论何时何地,只要类加载完成,INSTANCE就已存在且是唯一的。
-
线程安全: 类加载过程是线程安全的,因此通过类加载初始化的静态变量
INSTANCE
也是线程安全的,不会出现多线程环境下创建多个实例的问题。 -
节省资源: 由于静态变量在类加载时只初始化一次,避免了每次创建对象时都要初始化实例变量,减少了内存开销。
-
无需实例化直接访问: 通过
static
修饰的实例可以直接通过类名来访问,无需创建类的实例,符合单例模式的初衷——不需要实例化就能全局访问到唯一实例。
总结起来,static
关键字在这里的使用是为了确保单例模式中实例的全局唯一性和线程安全性,并且便于直接通过类名访问。
静态内部类(推荐)
- 当任何一个线程第一次调用
getInstance()
时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。(被调用时才进行初始化!)- 初始化静态数据时,Java提供了的线程安全性保证。(所以不需要任何的同步)
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
- 简单,直接写就行了
- 防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候(安全)!
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。
public enum SingletonEnumExample {
2 INSTANCE; // 创建一个枚举实例,它本身就是单例
3
4 // 单例类的私有成员变量和方法
5 private String data;
6
7 // 构造方法私有化,防止外部直接实例化
8 private SingletonEnumExample() {
9 this.data = "Initial Data";
10 // 初始化其他状态...
11 }
12
13 // 提供给外部访问单例实例的方法
14 public String getData() {
15 return this.data;
16 }
17
18 // 其他公共方法...
19}
具体使用:
String singletonData = SingletonEnumExample.INSTANCE.getData();
SingletonEnumExample
是一个枚举类型,其中的INSTANCE
就是单例实例。- 枚举类型的构造方法默认是私有的,因此外部无法直接创建新的实例。
- 由于Java语言规范保证枚举实例在任何情况下只会被初始化一次,并且是线程安全的,因此它自然满足单例模式的要求。
- 若要在单例中添加额外的状态或方法,直接在枚举中定义即可。
双重校验锁(DCL)
public class Singleton {
// volatile 防止指令重排序 对象知识实例可能还没初始化完成,暂时不能被使用
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
// 第一次判断:先判断单例对象有没有被创建,不用每次来一个新的线程来了都进入Synchronized
//如果不为空就说明单例已经创建过了,直接return就可以了
//将锁的范围缩小,提高性能
if (singleton == null) {
synchronized (Singleton.class) {
//第二次判断:对象有无被创建,没有就创建一个
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
总结:
我个人比较喜欢静态内部类写法和饿汉式写法,其实这两种写法能够应付绝大多数情况了。其他写法也可以选择,主要还是看业务需求吧。
更多参考:
https://segmentfault/a/1190000014888431
单例模式:https://blog.csdn/QGhurt/article/details/107711356
观察者模式
Spring的观察者模式
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,依赖这个对象的所有对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
// 当调用 DemoPublisher 的 publish() 方法的时候,比如 demoPublisher.publish("你好") ,控制台就会打印出:接收到的信息是:你好 。
观察者模式UML图
看不懂图的人端着小板凳到这里来,给你举个栗子:假设有三个人,小美(女,22),小王和小李。小美很漂亮,小王和小李是两个程序猿,时刻关注着小美的一举一动。有一天,小美说了一句:“谁来陪我打游戏啊。”这句话被小王和小李听到了,结果乐坏了,蹭蹭蹭,没一会儿,小王就冲到小美家门口了,在这里,小美是被观察者,小王和小李是观察者,被观察者发出一条信息,然后观察者们进行相应的处理,看代码:
public interface Person {
//小王和小李通过这个接口可以接收到小美发过来的消息
void getMessage(String s);
}
这个接口相当于小王和小李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看
public class LaoWang implements Person {
private String name = "小王";
public LaoWang() {
}
@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s);
}
}
public class LaoLi implements Person {
private String name = "小李";
public LaoLi() {
}
@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s);
}
}
代码很简单,我们再看看小美的代码:
public class XiaoMei {
List<Person> list = new ArrayList<Person>();
public XiaoMei(){
}
public void addPerson(Person person){
list.add(person);
}
//遍历list,把自己的通知发送给所有暗恋自己的人
public void notifyPerson() {
for(Person person:list){
person.getMessage("你们过来吧,谁先过来谁就能陪我一起玩儿游戏!");
}
}
}
我们写一个测试类来看一下结果对不对
public class Test {
public static void main(String[] args) {
XiaoMei xiao_mei = new XiaoMei();
LaoWang lao_wang = new LaoWang();
LaoLi lao_li = new LaoLi();
//小王和小李在小美那里都注册了一下
xiao_mei.addPerson(lao_wang);
xiao_mei.addPerson(lao_li);
//小美向小王和小李发送通知
xiao_mei.notifyPerson();
}
}
完美~
装饰者模式——IO流
转载:Java IO 设计模式总结 | JavaGuide
装饰器(Decorator)模式 可以在不改变原有对象的情况下拓展其功能。
装饰器模式通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景(IO 这一场景各种类的继承关系就比较复杂)更加实用。比如 InputStream
家族,InputStream
类下有 FileInputStream
(读取文件)、BufferedInputStream
(增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream
代码的情况下扩展了它的功能。
对于字节流来说, FilterInputStream
(对应输入流)和FilterOutputStream
(对应输出流)是装饰器模式的核心,分别用于增强 InputStream
和OutputStream
子类对象的功能。
我们常见的BufferedInputStream
(字节缓冲输入流)、DataInputStream
等等都是FilterInputStream
的子类,BufferedOutputStream
(字节缓冲输出流)、DataOutputStream
等等都是FilterOutputStream
的子类。BufferedInputStream
(字节缓冲输入流)来增强 FileInputStream。
对于字符流来说,BufferedReader
可以用来增加 Reader
(字符输入流)子类的功能,BufferedWriter
可以用来增加 Writer
(字符输出流)子类的功能。
这也是装饰器模式很重要的一个特征,那就是可以对原始类嵌套使用多个装饰器。
为了实现这一效果,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。上面介绍到的这些 IO 相关的装饰类和原始类共同的父类是 InputStream
和OutputStream
。
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), "UTF-8"));
装饰者模式——(三明治加工)
对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包夹一下,很丰盛的一顿午饭,营养又健康。(ps:不知道上海哪里有卖好吃的三明治的,求推荐~)那我们应该怎么来写代码呢?首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:
这个就是个装饰器类,想要增加些新功能就继承这个类来扩展即可。
public class Food {
private String food_name;
public Food() {
}
public Food(String food_name) {
this.food_name = food_name;
}
public String make() {
return food_name;
};
}
代码很简单,我就不解释了,然后我们写几个子类继承它:
//面包类
public class Bread extends Food {
private Food basic_food;
public Bread(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+面包";
}
}
//奶油类
public class Cream extends Food {
private Food basic_food;
public Cream(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+奶油";
}
}
//蔬菜类
public class Vegetable extends Food {
private Food basic_food;
public Vegetable(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+蔬菜";
}
}
这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了
public class Test {
public static void main(String[] args) {
Food food = new Bread(new Vegetable(new Cream(new Food("香肠"))));
System.out.println(food.make());
}
}
看到没有,一层一层封装,我们从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈~ 这个设计模式简直跟现实生活中一摸一样,看懂了吗?我们看看运行结果吧
运行结果
一个三明治就做好了~
装饰者模式——(打电话前播放彩铃)
转载:https://segmentfault/a/1190000014771830
一、对象增强的常用方式
很多时候我们可能对Java提供给我们的对象不满意,不能满足我们的功能。此时我们就想对Java原对象进行增强,能够实现我们想要的功能就好~
一般来说,实现对象增强有三种方式:
-
继承
- 继承父类,子类扩展
-
装饰器模式
- 使用“包装”的方式来增强对象
-
代理模式
- 给女朋友讲解么是代理模式
1.1继承
最简单的方式就是继承父类,子类扩展来达到目的。虽然简单,但是这种方式的缺陷非常大:
- 一、如果父类是带有数据、信息、属性的话,那么子类无法增强。
- 二、子类实现了之后需求无法变更,增强的内容是固定的。(代码写死了)
1.1.1第一点
第一点就拿以前在学JDBC的时候来说:
- 当时想要自己写一个简易的JDBC连接池,连接池由
List<Connection>
来管理。显然我们的对象是Connection,当写到close()
方法的时候卡住了。 - 因为我们想要的功能是:调用
close()
是让我们的Connection返回到“连接池”(集合)中,而不是关闭掉。 - 此时我们不能使用继承父类的方式来实现增强。因为Connection对象是由数据库厂商来实现的,在得到Connection对象的时候绑定了各种信息(数据库的username、password、具体的数据库是啥等等)。我们子类继承Connection是无法得到对应的数据的!就更别说调用
close()
方法了。
1.1.2第二点
第二点我也举个例子:
现在我设计一个电话类:
public class Phone {
// 可以打电话
public void call() {
System.out.println("打电话给周围的人关注公众号Java3y");
}
}
此时,我想打电话之前能听彩铃,于是我继承Phone类,实现我想要的功能。
public class MusicPhone extends Phone {
// 听彩铃
public void listenMusic() {
System.out.println("我怀念的是无话不说,我怀念的是一起做梦~~~~~~");
}
@Override
public void call() {
// 在打电话之前听彩铃
listenMusic();
super.call();
}
}
我们的功能就做好了:
- 此时,我又突然想实现多一个需求了,我想要听完电话之后告诉我一下当前的时间是多少。没事,我们又继承来增强一下:
// 这里继承的是MusicPhone类
public class GiveCurrentTimePhone extends MusicPhone {
// 给出当前的时间
public void currentTime() {
System.out.println("当前的时间是:" + System.currentTimeMillis());
}
@Override
public void call() {
super.call();
// 打完电话提示现在的时间是多少啦
currentTime();
}
}
所以我们还是可以完成任务滴:
可是我需求现在又想变了:
- 我不想听彩铃了,只想听完电话通知一下时间就好了........(可是我们的通知时间电话类是继承在听彩铃的电话类基础之上的),,,
- 我又有可能:我想在听电话之前报告一下时间,听完电话听音乐!...
- 如果需求变动很大的情况下,而我们又用继承的方式来实现这样会导致一种现象:类爆炸(类数量激增)!并且继承的层次可能会比较多~
所以,我们可以看到子类继承父类这种方式来扩展是十分局限的,不灵活的~
因此我们就有了装饰模式!
1.2装饰模式
首先我们来看看装饰模式是怎么用的吧。
1.2.1前提代码
电话接口:
// 一个良好的设计是抽取成接口或者抽象类的
public interface Phone {
// 可以打电话
void call();
}
具体的实现:
public class IphoneX implements Phone {
@Override
public void call() {
System.out.println("打电话给周围的人关注公众号Java3y");
}
}
1.2.2包装模式实现
上面我们已经拥有了一个接口还有一个默认实现。包装模式是这样干的:
首先我们弄一个装饰器,它实现了接口,以组合的方式接收我们的默认实现类。
想要新增加些功能,继承这个装饰器类就可以了。
// 装饰器,实现接口
public abstract class PhoneDecorate implements Phone {
// 以组合的方式来获取默认实现类
private Phone phone;
public PhoneDecorate(Phone phone) {
this.phone = phone;
}
@Override
public void call() {
phone.call();
}
}
有了装饰器以后,我们的扩展都可以以装饰器为基础进行扩展,继承装饰器来扩展就好了!
我们想要在打电话之前听音乐:
// 继承装饰器类来扩展
public class MusicPhone extends PhoneDecorate {
public MusicPhone(Phone phone) {
super(phone);
}
// 定义想要扩展的功能
public void listenMusic() {
System.out.println("继续跑 带着赤子的骄傲,生命的闪耀不坚持到底怎能看到,与其苟延残喘不如纵情燃烧");
}
// 重写打电话的方法
@Override
public void call() {
// 在打电话之前听音乐
listenMusic();
super.call();
}
}
现在我也想在打完电话后通知当前的时间,于是我们也继承装饰类来扩展:
// 这里继承的是装饰器类
public class GiveCurrentTimePhone extends PhoneDecorate {
public GiveCurrentTimePhone(Phone phone) {
super(phone);
}
// 自定义想要实现的功能:给出当前的时间
public void currentTime() {
System.out.println("当前的时间是:" + System.currentTimeMillis());
}
// 重写要增强的方法
@Override
public void call() {
super.call();
// 打完电话后通知一下当前时间
currentTime();
}
}
可以完成任务:
就目前这样看起来,比我直接继承父类要麻烦,而功能效果是一样的....我们继续往下看~~
此时,我不想在打电话之前听到彩铃了,很简单:我们不装饰它就好了!
此时,我想在打电话前报告一下时间,在打完电话之后听彩铃。
- 注意:虽然说要改动类中的代码,但是这种改动是合理的。因为我定义出的
GiveCurrentTimePhone类
和MusicPhone类
本身从语义上就没有规定扩展功能的执行顺序 - 而继承不一样:先继承Phone->实现MusicPhone->再继承MusicPhone实现GiveCurrentTimePhone。这是固定的,从继承的逻辑上已经写死了具体的代码,是难以改变的。
所以我们还是可以很简单地完成功能:
二、装饰模式讲解
可能有的同学在看完上面的代码之后,还是迷迷糊糊地不知道装饰模式是怎么实现“装饰”的。下面我就再来解析一下:
- 第一步:我们有一个Phone接口,该接口定义了Phone的功能
- 第二步:我们有一个最简单的实现类iPhoneX
- 第三步:写一个装饰器抽象类PhoneDecorate,以组合(构造函数传递)的方式接收我们最简单的实现类iPhoneX。其实装饰器抽象类的作用就是代理(核心的功能还是由最简单的实现类iPhoneX来做,只不过在扩展的时候可以添加一些没有的功能而已)。
- 第四步:想要扩展什么功能,就继承PhoneDecorate装饰器抽象类,将想要增强的对象(最简单的实现类iPhoneX或者已经被增强过的对象)传进去,完成我们的扩展!
再来看看下面的图,就懂了!
往往我们的代码可以省略起来,成了这个样子(是不是和IO的非常像!)
// 先增强听音乐的功能,再增强通知时间的功能
Phone phone = new GiveCurrentTimePhone(new MusicPhone(new IphoneX()));
结果是一样的:
2.1装饰模式的优缺点
优点:
- 装饰类和被装饰类是可以独立的,低耦合的。互相都不用知道对方的存在
- 装饰模式是继承的一种替代方案,无论包装多少层,返回的对象都是is-a的关系(上面的例子:包装完还是Phone类型)。
- 实现动态扩展,只要继承了装饰器就可以动态扩展想要的功能了。
缺点:
- 多层装饰是比较复杂的,提高了系统的复杂度。不利于我们调试~
三、总结
最后来补充一下包装模式和代理模式的类图:
对象增强的三种方式:
- 继承
- 包装模式
- 代理模式
那么只要遇到Java提供给我们的API不够用,我们增强一下就行了。在写代码时,某个类被写死了,功能不够用,增强一下就可以了!
工厂模式
简单工厂模式:一个工厂生产多种具体的产品
工厂方法模式:抽象工厂下有一个工厂方法,抽象工厂的子类去实例化产品,不同的具体工厂生产不同的单一的具体产品
抽象工厂模式:抽象工厂下有多个抽象的工厂,可以生产多个产品
简单工厂模式:一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口
// 抽象产品类
abstract class Car {
public void run();
public void stop();
}
// 具体实现类
class Benz implements Car {
public void run() {
System.out.println("Benz开始启动了。。。。。");
}
public void stop() {
System.out.println("Benz停车了。。。。。");
}
}
class Ford implements Car {
public void run() {
System.out.println("Ford开始启动了。。。");
}
public void stop() {
System.out.println("Ford停车了。。。。");
}
}
// 工厂类
class Factory {
public static Car getCarInstance(String type) {
Car c = null;
if ("Benz".equals(type)) {
c = new Benz();
}
if ("Ford".equals(type)) {
c = new Ford();
}
return c;
}
}
public class Test {
public static void main(String[] args) {
Car c = Factory.getCarInstance("Benz");
if (c != null) {
c.run();
c.stop();
} else {
System.out.println("造不了这种汽车。。。");
}
}
}
工厂方法模式:有四个角色,抽象工厂,具体工厂,抽象产品,具体产品。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品
// 抽象产品角色
public interface Moveable {
void run();
}
// 具体产品角色
public class Plane implements Moveable {
@Override
public void run() {
System.out.println("plane....");
}
}
public class Broom implements Moveable {
@Override
public void run() {
System.out.println("broom.....");
}
}
// 抽象工厂
public abstract class VehicleFactory {
//返回一个抽象产品
abstract Moveable create();
}
// 具体工厂
public class PlaneFactory extends VehicleFactory {
public Moveable create() {
return new Plane();
}
}
public class BroomFactory extends VehicleFactory {
public Moveable create() {
return new Broom();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
VehicleFactory factory = new BroomFactory();
Moveable m = factory.create();
m.run();
}
}
抽象工厂模式:与工厂方法模式不同的是,工厂方法模式中的工厂只生产单一的产品,而抽象工厂模式中的工厂生产多个产品
/抽象工厂类
public abstract class AbstractFactory {
public abstract Vehicle createVehicle();
public abstract Weapon createWeapon();
public abstract Food createFood();
}
//具体工厂类,其中Food,Vehicle,Weapon是抽象类,
public class DefaultFactory extends AbstractFactory{
@Override
public Food createFood() {
return new Apple();
}
@Override
public Vehicle createVehicle() {
return new Car();
}
@Override
public Weapon createWeapon() {
return new AK47();
}
}
//测试类
public class Test {
public static void main(String[] args) {
AbstractFactory f = new DefaultFactory();
Vehicle v = f.createVehicle();
v.run();
Weapon w = f.createWeapon();
w.shoot();
Food a = f.createFood();
a.printName();
}
}
更多参考:https://segmentfault/a/1190000014949595
代理模式(proxy)
静态代理
有两种,静态代理和动态代理。先说静态代理,很多理论性的东西我不讲,我就算讲了,你们也看不懂。什么真实角色,抽象角色,代理角色,委托角色。。。乱七八糟的,我是看不懂。之前学代理模式的时候,去网上翻一下,资料一大堆,打开链接一看,基本上都是给你分析有什么什么角色,理论一大堆,看起来很费劲,不信的话你们可以去看看,我是看不懂他们在说什么。咱不来虚的,直接用生活中的例子说话。(注意:我这里并不是否定理论知识,我只是觉得有时候理论知识晦涩难懂,喜欢挑刺的人一边去,你是来学习知识的,不是来挑刺的)
到了一定的年龄,我们就要结婚,结婚是一件很麻烦的事情,(包括那些被父母催婚的)。有钱的家庭可能会找司仪来主持婚礼,显得热闹,洋气~好了,现在婚庆公司的生意来了,我们只需要给钱,婚庆公司就会帮我们安排一整套结婚的流程。整个流程大概是这样的:家里人催婚->男女双方家庭商定结婚的黄道即日->找一家靠谱的婚庆公司->在约定的时间举行结婚仪式->结婚完毕
婚庆公司打算怎么安排婚礼的节目,在婚礼完毕以后婚庆公司会做什么,我们一概不知。。。别担心,不是黑中介,我们只要把钱给人家,人家会把事情给我们做好。所以,这里的婚庆公司相当于代理角色,现在明白什么是代理角色了吧。
代码实现请看:
//代理接口
public interface ProxyInterface {
//需要代理的是结婚这件事,如果还有其他事情需要代理,比如吃饭睡觉上厕所,也可以写
void marry();
//代理吃饭(自己的饭,让别人吃去吧)
//void eat();
//代理拉屎,自己的屎,让别人拉去吧
//void shit();
}
文明社会,代理吃饭,代理拉屎什么的我就不写了,有伤社会风化~~~能明白就好
好了,我们看看婚庆公司的代码:
public class WeddingCompany implements ProxyInterface {
private ProxyInterface proxyInterface;
public WeddingCompany(ProxyInterface proxyInterface) {
this.proxyInterface = proxyInterface;
}
@Override
public void marry() {
System.out.println("我们是婚庆公司的");
System.out.println("我们在做结婚前的准备工作");
System.out.println("节目彩排...");
System.out.println("礼物购买...");
System.out.println("工作人员分工...");
System.out.println("可以开始结婚了");
proxyInterface.marry();
System.out.println("结婚完毕,我们需要做后续处理,你们可以回家了,其余的事情我们公司来做");
}
}
看到没有,婚庆公司需要做的事情很多,我们再看看结婚家庭的代码:
public class NormalHome implements ProxyInterface{
@Override
public void marry() {
System.out.println("我们结婚啦~");
}
}
这个已经很明显了,结婚家庭只需要结婚,而婚庆公司要包揽一切,前前后后的事情都是婚庆公司来做,听说现在婚庆公司很赚钱的,这就是原因,干的活多,能不赚钱吗?
来看看测试类代码:
public class Test {
public static void main(String[] args) {
ProxyInterface proxyInterface = new WeddingCompany(new NormalHome());
proxyInterface.marry();
}
}
运行结果如下:
动态代理
动态代理就是拦截直接访问对象,可以给对象进行增强的一项技能。
要实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话我们可以考虑cglib代理。
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能!
public interface Programmer {
// 程序员每天都写代码
void coding();
}
public class Java3y implements Programmer {
@Override
public void coding() {
System.out.println("Java3y最新文章:......给女朋友讲解什么是代理模式.......");
}
}
public class Main {
public static void main(String[] args1) {
// Java3y请水军
Java3y java3y = new Java3y();
Programmer programmerWaterArmy = (Programmer) Proxy.newProxyInstance(java3y.getClass().getClassLoader(), java3y.getClass().getInterfaces(), (proxy, method, args) -> {
// 如果是调用coding方法,那么水军就要点赞了
if (method.getName().equals("coding")) {
method.invoke(java3y, args);
System.out.println("我是水军,我来点赞了!");
} else {
// 如果不是调用coding方法,那么调用原对象的方法
return method.invoke(java3y, args);
}
return null;
});
// 每当Java3y写完文章,水军都会点赞
programmerWaterArmy.coding();
}
}
Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,该方法需要三个参数:
- 参数一:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
- 参数二:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
- 参数三:生成的代理对象的方法里干什么事【实现handler接口,我们想怎么实现就怎么实现】
在编写动态代理之前,要明确几个概念:
- 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】
- 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
- 使用JDK动态代理必须要有接口【参数二需要接口】
上面也说了:代理对象会实现接口的所有方法,这些实现的方法交由我们的handler来处理!
- 所有通过动态代理实现的方法全部通过
invoke()
调用
所以动态代理调用过程是这样子的:
3.2静态代理和动态代理的区别
很明显的是:
- 静态代理需要自己写代理类-->代理类需要实现与目标对象相同的接口
- 而动态代理不需要自己编写代理类--->(是动态生成的)
使用静态代理时:
- 如果目标对象的接口有很多方法的话,那我们还是得一一实现,这样就会比较麻烦
使用动态代理时:
- 代理对象的生成,是利用JDKAPI,动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型),并且会默认实现接口的全部方法。
应用:中文过滤器
public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
final HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setContentType("text/html;charset=UTF-8");
request.setCharacterEncoding("UTF-8");
//放出去的是代理对象
chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是不是getParameter方法
if (!method.getName().equals("getParameter")) {
//不是就使用request调用
return method.invoke(request, args);
}
//判断是否是get类型的
if (!request.getMethod().equalsIgnoreCase("get")) {
return method.invoke(request, args);
}
//执行到这里,只能是get类型的getParameter方法了。
String value = (String) method.invoke(request, args);
if (value == null) {
return null;
}
return new String(value.getBytes("ISO8859-1"), "UTF-8");
}
}), response);
}
策略模式
《设计模式之禅》:
定义一组算法,将每个算法都封装起来,并且使他们之间可以互换
策略模式的类图是这样的:
策略的接口和具体的实现应该很好理解:
- 策略的接口相当于我们上面所讲的ResultSetHandler接口(定义了策略的行为)
-
具体的实现相当于我们上面所讲的BeanHandler实现(接口的具体实现)
- 具体的实现一般还会有几个,比如可能还有ListBeanHandler、MapBeanHandler等等
令人想不明白的可能是:策略模式还有一个Context上下文对象。这对象是用来干什么的呢?
《设计模式之禅》:
Context叫做上下文角色,起承上启下封装作用, 屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
在知乎上也有类似的问题(为什么不直接调用,而要通过Person?):
说白了,通过Person来调用更符合面向对象(屏蔽了直接对具体实现的访问)。
首先要明白一个道理,就是——到底是 “人” 旅游,还是火车、汽车、自行车、飞机这些交通工具旅游?
如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单
具体的链接:
- 策略设计模式使用? - 知乎
所以我们再说回上文的通用类图,我们就可以这样看了:
1.2策略模式例子
现在3y拥有一个公众号,名称叫做Java3y。3y想要这让更多的人认识到Java3y这个公众号。所以每天都在想怎么涨粉(hahah
于是3y就开始想办法了(操碎了心),同时3y在这一段时间下来发现涨粉的方式有很多。为了方便,定义一个通用的接口方便来管理和使用呗。
接口:
/**
* 增加粉丝策略的接口(Strategy)
*/
interface IncreaseFansStrategy {
void action();
}
涨粉的具体措施,比如说,请水军:
/**
* 请水军(ConcreteStrategy)
*/
public class WaterArmy implements IncreaseFansStrategy {
@Override
public void action() {
System.out.println("3y牛逼,我要给你点赞、转发、加鸡腿!");
}
}
涨粉的具体措施,比如说,认真写原创:
/**
* 认真写原创(ConcreteStrategy)
*/
public class OriginalArticle implements IncreaseFansStrategy{
@Override
public void action() {
System.out.println("3y认真写原创,最新一篇文章:《策略模式,就这?》");
}
}
3y还想到了很多涨粉的方法,比如说送书活动啊、商业互吹啊等等等...(这里就不细说了)
说到底,无论是哪种涨粉方法,都是通过3y去执行的。
/**
* 3y(Context)
*/
public class Java3y {
private IncreaseFansStrategy strategy ;
public Java3y(IncreaseFansStrategy strategy) {
this.strategy = strategy;
}
// 3y要发文章了(买水军了、送书了、写知乎引流了...)。
// 具体执行哪个,看3y选哪个
public void exec() {
strategy.action();
}
}
所以啊,每当到了发推文的时候,3y就可以挑用哪种方式涨粉了:
public class Main {
public static void main(String[] args) {
// 今天2018年12月24日
Java3y java3y = new Java3y(new WaterArmy());
java3y.exec();
// 明天2018年12月25日
Java3y java4y = new Java3y(new OriginalArticle());
java4y.exec();
// ......
}
}
执行结果:
1.3策略模式优缺点
优点:
-
算法可以自由切换
- 改一下策略很方便
-
扩展性良好
- 增加一个策略,就多增加一个类就好了。
缺点:
-
策略类的数量增多
- 每一个策略都是一个类,复用的可能性很小、类数量增多
-
所有的策略类都需要对外暴露
- 上层模块必须知道有哪些策略,然后才能决定使用哪一个策略
1.4JDK的策略模式应用
不知道大家还能不能想起ThreadPoolExecutor(线程池):线程池你真不来了解一下吗?
学习ThreadPoolExecutor(线程池)就肯定要知道它的构造方法每个参数的意义:
/**
* Handler called when saturated or shutdown in execute.
*/
private volatile RejectedExecutionHandler handler;
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//....
this.handler = handler;
}
/**
* Invokes the rejected execution handler for the given command.
* Package-protected for use by ScheduledThreadPoolExecutor.
*/
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
其中我们可以找到RejectedExecutionHandler,这个参数代表的是拒绝策略(有四种具体的实现:直接抛出异常、使用调用者的线程来处理、直接丢掉这个任务、丢掉最老的任务)
其实这就是策略模式的体现了。
最后
看完会不会觉得策略模式特别简单呀?就一个算法接口、多个算法实现、一个Context来包装一下,就完事了。
转载:https://segmentfault/a/1190000017568892
无论是面试还是个人的提升,设计模式是必学的。今天来讲解门面(外观)模式~
上一次分享了一篇好文:《为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的 API》
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API, 使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
不知道大家有没有了解过门面模式,我去翻了一下《设计模式之禅》,发现非常简单,所以在这给大家分享一下。
门面(外观)模式
1.1门面模式现实例子
一个电源总开关可以控制所有电器的打开或关闭状态。
无论是空调、冰箱、电视、电脑、风扇等等,只要是电器都受这个电闸控制。只要这个电闸将关闭,所有的电器都会受到牵连(一同关闭)。
电源总开关(电闸)即为该系统的外观模式设计。
1.2回到代码世界
比如,我们家里现在有空调、冰箱、电脑这么几个电器
// 冰箱
public class Fridge {
// 关闭冰箱
public void turnOff() {
}
// 开冰箱灯..减低冰箱温度..调高冰箱温度...
}
// 电视
public class Television {
// 关闭电视
public void turnOffTV() {
System.out.println("关闭电视");
}
// 切换电视节目..减低电视声音..调高电视声音...
public void doSomething() {
System.out.println("切换电视节目..减低电视声音..调高电视声音...");
}
}
// 电脑
public class Computer {
// 关闭电脑
public void turnOffComputer() {
System.out.println("关闭电脑");
}
// 使用电脑干别的事~
public void doSomething() {
System.out.println("使用电脑干别的事~");
}
}
如果没有电闸的的情况下,我想将上面的电器关闭掉,我需要这样干:
// 我要关闭电视、电脑、空调
public static void main(String[] args) {
new Computer().turnOffComputer();
new Fridge().turnOffFridge();
new Television().turnOffTV();
// 当然了,一个正常的家庭不单单只有这么点电器的。
// 如果某一天我想关闭家里所有的电器,就需要重复new 个对象,调用其turn offer方法
}
一个一个关是不是很麻烦,所以我们就有了电闸:
// 电闸
public class ElectricBrake {
private Computer computer = new Computer();
private Fridge fridge = new Fridge();
private Television television = new Television();
// 关闭所有电器
public void turnOffAll() {
computer.turnOffComputer();
fridge.turnOffFridge();
television.turnOffTV();
}
}
当我们想关闭所有电器的时候,我们可以使用电闸来关闭。
// 我要关闭所有电器
public static void main(String[] args) {
ElectricBrake brake = new ElectricBrake();
brake.turnOffAll();
}
有经验的同学可能就会想,这不就再封装了一层吗??这就是门面模式啦??嗯,没错,这就是门面模式
1.3门面模式介绍
《设计模式之禅》:
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
门面模式的通用类图十分简单:
按照我们的例子,子系统就相当于电脑、冰箱、电视。统一的对象就相当于我们的电闸。我们通过电闸来对所有电器进行关闭(使得不用逐个逐个找电脑、冰箱、电视来关闭)
使用了门面模式,使客户端调用变得更加简单!
1.4门面模式的优缺点
优点:
- 减少系统的相互依赖。使用门面模式,所有的依赖都是对门面对象的依赖,与子系统无关
- 提高了灵活性。不管子系统内部如何变化,只要不影响门面对象,任你自由活动。
缺点:
- 不符合开闭原则,对修改关闭,对扩展开放。比如我们上面的例子,如果有新电器要想要加入一次关闭的队伍中,只能在门面对象上修改
turnOffAll()
方法的代码。
最后
是不是觉得门面设计模式就那么一回事了?说白了就是对子系统封装了一层,给予了一个高层次的接口(门面对象),进而方便客户端调用。
模板方法模式
1.1模板方法模式现实例子
大家都知道,我每次写原创技术文章,开头总会有“只有光头才能变强”。我当然不可能每次写文章的时候都去复制这句话(因为这样太麻烦了)。
我有自己的写作模板,给大家看一下:
前言和最后都是固定下来的,至于第一点和第二点就得看是写什么文章,写不同的文章对应的内容也是不一样的。
每次我写文章的时候,只要在这个模板上添加我自己想写的东西就好了,就不用每次都复制一遍相同的内容,这样就大大减少我的工作量啦。
1.2回到代码世界
代码来源于生活,同样地我可以将我写文章的过程用代码来描述,大家来看一下。
3y每篇文章都会有“前言”和“最后”的内容,3y把这两个模块写出来了。
// 3y的文章模板
public class Java3yWriteArticle {
// 前言
public void introduction() {
System.out.println("只有光头才能变强");
}
// 最后
public void theLast() {
System.out.println("关注我的公众号:Java3y");
}
}
3y写文章的时候,3y可能就会这样使用:
// 3y写文章
public static void main(String[] args) {
Java3yWriteArticle writeArticle = new Java3yWriteArticle();
// 前言
writeArticle.introduction();
// 实际内容
System.out.println("大家好,我是3y,今天来给大家分享我写的模板方法模式");
// 最后
writeArticle.theLast();
}
这样是可以完成3y写文章的功能,但是这样做好吗?这时候3y女朋友也想写文章,她的文章同样也想有“前言”和“最后”两个模块,所以3y女朋友的文章模板是这样的:
// 3y女朋友的文章模板
public class Java3yGFWriteArticle {
// 前言
public void introduction() {
System.out.println("balabalabalalabalablablalab");
}
// 最后
public void theLast() {
System.out.println("balabalabalalabalablablalab");
}
}
那3y女朋友写文章的时候,可能也会这样使用:
// 3y女朋友写文章
public static void main(String[] args) {
Java3yGFWriteArticle java3yGFWriteArticle = new Java3yGFWriteArticle();
// 前言
java3yGFWriteArticle.introduction();
// 实际内容
System.out.println("3y是傻子,不用管他");
// 最后
java3yGFWriteArticle.theLast();
}
可以发现3y和3y女朋友要写文章的时是要重复调用introduction();
和theLast();
。并且,3y的文章模板和3y女朋友的文章模板中的“前言”和“最后”只是实现内容的不同,却定义了两次,明显就是重复的代码。面对重复的代码我们会做什么?很简单,抽取出来!
于是我们就可以抽取出一个通用的WriteArticle(为了方便调用,我们还将写文章的步骤封装成一个方法):
// 通用模板
public abstract class WriteArticle {
// 每个人的“前言”都不一样,所以抽象(abstract)
protected abstract void introduction();
// 每个人的“最后”都不一样,所以抽象(abstract)
protected abstract void theLast();
// 实际要写的内容,每个人的“实际内容”都不一样,所以抽象(abstract)
protected abstract void actualContent();
// 写一篇完整的文章(为了方便调用,我们将这几个步骤分装成一个方法)
public final void writeAnCompleteArticle() {
// 前言
introduction();
// 实际内容
actualContent();
// 最后
theLast();
}
}
所以,3y的模板就可以继承通用模板,在通用模板上实现自己想要的就好了:
// 3y的文章模板
public class Java3yWriteArticle extends WriteArticle {
// 前言
@Override
public void introduction() {
System.out.println("只有光头才能变强");
}
// 最后
@Override
public void theLast() {
System.out.println("关注我的公众号:Java3y");
}
@Override
protected void actualContent() {
System.out.println("大家好,我是3y,今天来给大家分享我写的模板方法模式");
}
}
同样地,3y女朋友的文章模板也是类似的:
// 3y女朋友的文章模板
public class Java3yGFWriteArticle extends WriteArticle {
// 前言
@Override
public void introduction() {
System.out.println("balabalabalalabalablablalab");
}
// 最后
@Override
public void theLast() {
System.out.println("balabalabalalabalablablalab");
}
@Override
protected void actualContent() {
System.out.println("3y是傻子,不用管他");
}
}
想要真正写文章的时候就十分方便了:
// 3y写文章
public static void main(String[] args) {
WriteArticle java3ywriteArticle = new Java3yWriteArticle();
java3ywriteArticle.writeAnCompleteArticle();
}
// 3y女朋友写文章
public static void main(String[] args) {
WriteArticle java3yGFWriteArticle = new Java3yGFWriteArticle();
java3yGFWriteArticle.writeAnCompleteArticle();
}
要点:
- 把公共的代码抽取出来,如果该功能是不确定的,那我们将其修饰成抽象方法。
- 将几个固定步骤的功能封装到一个方法(模板方法)中,这个方法中调用几个基本的方法(固定的逻辑顺序)
- 对外暴露这个方法,就可以非常方便调用了。
嗯,上面的就是模板方法模式,就这么简单!
1.3模板方法模式介绍
《设计模式之禅》:
定义一个操作中的算法框架,而将一些步骤延迟到子类中。使子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
根据我们上面的例子,来讲讲这段话的含义:
-
定义一个操作中的算法框架,而将一些步骤延迟到子类中。
- WriteArticle中有一个writeAnCompleteArticle()方法,该方法定义了发文章的所有步骤,但是这些步骤大多是抽象的,得由子类来实现。
-
使子类可以不改变一个算法的结构即可重定义该算法的某些步骤
- 外界是通过调用writeAnCompleteArticle()方法来写文章的,子类如果改变具体的实现就会间接改变了算法的细节。
比如3y在文章模板中的introduction()
改了,“只有充钱才能变强”
@Override
public void introduction() {
System.out.println("只有充钱才能变强");
}
我们没有碰过writeAnCompleteArticle()的代码,但再次调用这个方法的时候,具体的实现就会发生改变(因为writeAnCompleteArticle受子类的具体实现影响)
下面我们看一下模板方法模式的通用类图:
在模板方法模式中,也有几个术语,根据我们的例子中的注释,我给大家介绍一下:
// 抽象模板类
public abstract class WriteArticle {
// 基本方法
protected abstract void introduction();
// 基本方法
protected abstract void theLast();
// 基本方法
protected abstract void actualContent();
// 模板方法
public final void writeAnCompleteArticle() {
introduction();
actualContent();
theLast();
}
}
// 具体模板类
public class Java3yWriteArticle extends WriteArticle {
// 实现基本方法
@Override
public void introduction() {
System.out.println("只有充钱才能变强");
}
// 实现基本方法
@Override
public void theLast() {
System.out.println("关注我的公众号:Java3y");
}
// 实现基本方法
@Override
protected void actualContent() {
System.out.println("大家好,我是3y,今天来给大家分享我写的模板方法模式");
}
}
- 基本方法:在子类实现,并且在模板方法中被调用,具体模板类实现抽象模板类的基本方法(模板方法不需要实现)
- 模板方法:定义了一个框架,实现对基本方法的调用,完成固定的逻辑。
1.4模板方法的优缺点
优点:
- 封装不变的部分,扩展可变的部分。把认为是不变的部分的算法封装到父类,可变部分的交由子类来实现!
- 提取公共部分的代码,行为由父类控制,子类实现!
缺点:
- 抽象类定义了部分抽象方法,这些抽象的方法由子类来实现,子类执行的结果影响了父类的结果(子类对父类产生了影响),会带来阅读代码的难度!
模板方法模式——AQS
最经典的就是JUC包下的AQS(AbstractQueuedSynchronizer)了。AQS是什么?
AQS其实就是一个可以给我们 实现锁的框架。内部实现的关键是:先进先出的队列、state状态
我们可以看一下AQS定义的acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire()
相当于模板方法,tryAcquire(arg)
相当于基本方法。
AQS 的设计是基于模板方法模式的,它有一些方法必须要子类去实现的,它们主要有:
这里不使用抽象方法的目的是:避免强迫子类中把所有的抽象方法都实现一遍,减少无用功,这样子类只需要实现自己关心的抽象方法即可,比如 信号 Semaphoreopen 只需要实现 tryAcquire 方法而不用实现其余不需要用到的模版方法
isHeldExclusively()
:该线程是否正在独占资源。只有用到 condition 才需要去实现它。tryAcquire(int)
:独占方式。尝试获取资源,成功则返回 true,失败则返回 false。tryRelease(int)
:独占方式。尝试释放资源,成功则返回 true,失败则返回 false。tryAcquireShared(int)
:共享方式。尝试获取资源。负数表示失败;0 表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared(int)
:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回 true,否则返回 false。
最后
模板方法模式也很简单呀,一个抽象类有基本方法(等着被子类实现的方法),有模板方法(对外暴露、调用基本方法、定义了算法的框架),那就完事了。
推荐阅读和参考资料:
使用函数式接口优化模版设计模式,避免实现过多的子类:
用Java8改造后的模板方法模式真的是yyds!
- 《设计模式之禅》
- Carson带你学设计模式:模板方法模式(Template Method)_模板 设计模式-CSDN博客
- 设计模式 ( 十九 ) 模板方法模式Template method(类行为型)_templat类的 process()作用-CSDN博客
原型模式
适配器模式
Spring中的适配器模式_spring 适配器模式-CSDN博客
适配器(Adapter Pattern)模式 主要用于接口互不兼容的类的协调工作,你可以将其联想到我们日常经常使用的电源适配器。
适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。通过适配器,我们可以将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据。
InputStreamReader
和 OutputStreamWriter
就是两个适配器(Adapter), 同时,它们两个也是字节流和字符流之间的桥梁。InputStreamReader
使用 StreamDecoder
(流解码器)对字节进行解码,实现字节流到字符流的转换, OutputStreamWriter
使用StreamEncoder
(流编码器)对字符进行编码,实现字符流到字节流的转换。
InputStream
和 OutputStream
的子类是被适配者, InputStreamReader
和 OutputStreamWriter
是适配器。
// InputStreamReader 是适配器,FileInputStream 是被适配的类
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName), "UTF-8");
// BufferedReader 增强 InputStreamReader 的功能(装饰器模式)
BufferedReader bufferedReader = new BufferedReader(isr);
适配器模式和装饰器模式有什么区别呢?
装饰器模式 更侧重于动态地增强原始类的功能,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。并且,装饰器模式支持对原始类嵌套使用多个装饰器。
适配器模式 更侧重于让接口不兼容而不能交互的类可以一起工作,当我们调用适配器对应的方法时,适配器内部会调用适配者类或者和适配类相关的类的方法,这个过程透明的。就比如说 StreamDecoder
(流解码器)和StreamEncoder
(流编码器)就是分别基于 InputStream
和 OutputStream
来获取 FileChannel
对象并调用对应的 read
方法和 write
方法进行字节数据的读取和写入。
责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为设计模式,用于解决请求的发送者和接收者之间的耦合问题。在这种模式中,一系列对象(即“链”)被组织起来,每个对象都具有处理请求的能力,并且知道如何将请求转发给链中的下一个对象。当一个请求进入责任链时,它会沿着链传递,直到遇到能够适当处理该请求的对象,此时请求会被处理;如果当前对象无法处理,它会自动将请求传递给下一个对象,如此继续,直到请求被最终处理或者到达链的末端。
以下是责任链模式的关键特性与组成部分:
-
抽象处理者(Handler):定义一个处理请求的接口(或抽象类),通常包括一个处理请求的方法(如
handleRequest()
)以及一个指向链中下一个处理者的引用(如nextHandler
)。具体的处理逻辑则留给子类实现。 -
具体处理者(Concrete Handlers):这些是抽象处理者的具体实现,每个类负责处理特定类型的请求。在接到请求时,它们首先检查自己是否能处理该请求。如果可以,就执行相应的处理逻辑并返回结果;如果不能,就通过其持有的下一个处理者引用将请求转发给链中的下一个对象。
-
客户端(Client):客户端创建责任链,并向链首(通常是第一个具体处理者)发送请求。客户端通常不知道请求在链中如何被具体处理,也不关心哪个处理者实际完成了请求。这种解耦使得客户端无需了解处理请求的具体细节,简化了客户端代码,并允许处理逻辑在不修改客户端的情况下灵活变更。
使用场景:
- 当需要使用不同方式处理不同种类的请求,且请求类型和处理顺序可能在运行时变化时。
- 当需要按特定顺序执行多个处理者,但又不想在客户端硬编码这个顺序。
- 敏感词过滤、权限审批流程、事件处理系统、窗口消息分发、异常处理栈等场景,都可以应用责任链模式。
优点:
- 降低耦合度:请求发送者无需知道请求将由哪个具体处理者处理,只需将请求发送到链的起点即可。
- 灵活性:可以动态地添加、删除或重新排列处理者,以改变请求处理的逻辑或优先级,而无需修改其他部分的代码。
- 开放扩展,封闭修改:遵循开闭原则,增加新的请求处理逻辑时,只需新增一个具体处理者类,不需要改动已有代码。
缺点:
- 调试复杂性:由于请求可能经过多个处理者,定位问题或跟踪请求处理过程可能变得困难。
- 请求可能得不到处理:如果没有正确配置链,或者没有处理者能处理特定请求,请求可能会沿链传递到最后仍未得到处理。
综上所述,责任链模式通过构建一个处理对象的链式结构,使请求可以在链中自由传递直至被合适的处理者处理,从而实现了请求发送者与接收者的解耦以及处理逻辑的灵活配置。
以下是一个简单的Java责任链模式的示例,我们将模拟一个审批流程,其中包含多个级别的审批人(初级经理、中级经理、高级经理),每个级别都有一定的审批权限(例如,批准一定金额范围内的报销申请)。当一个报销申请提交时,它会按照责任链依次传递,直到找到能够处理该申请的审批人为止。
// 抽象处理者角色:定义处理请求的抽象方法
public interface Approver {
void setNext(Approver nextApprover); // 设置下一级审批人
boolean approve(ExpenseRequest request); // 处理报销申请
}
// 请求类:报销申请
public class ExpenseRequest {
private double amount;
private String purpose;
public ExpenseRequest(double amount, String purpose) {
this.amount = amount;
this.purpose = purpose;
}
public double getAmount() {
return amount;
}
public String getPurpose() {
return purpose;
}
}
// 具体处理者角色:处理请求的具体实现类
public class JuniorManager implements Approver {
private Approver nextApprover;
@Override
public void setNext(Approver nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public boolean approve(ExpenseRequest request) {
if (request.getAmount() <= 1000.0) {
System.out.println("Junior Manager approved expense request for " + request.getPurpose());
return true;
} else if (nextApprover != null) {
return nextApprover.approve(request);
} else {
System.out.println("No approver found for the given expense request.");
return false;
}
}
}
public class MiddleManager implements Approver {
private Approver nextApprover;
@Override
public void setNext(Approver nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public boolean approve(ExpenseRequest request) {
if (request.getAmount() <= 5000.0) {
System.out.println("Middle Manager approved expense request for " + request.getPurpose());
return true;
} else if (nextApprover != null) {
return nextApprover.approve(request);
} else {
System.out.println("No approver found for the given expense request.");
return false;
}
}
}
public class SeniorManager implements Approver {
private Approver nextApprover;
@Override
public void setNext(Approver nextApprover) {
this.nextApprover = nextApprover;
}
@Override
public boolean approve(ExpenseRequest request) {
if (request.getAmount() <= 10000.0) {
System.out.println("Senior Manager approved expense request for " + request.getPurpose());
return true;
} else if (nextApprover != null) {
return nextApprover.approve(request);
} else {
System.out.println("No approver found for the given expense request.");
return false;
}
}
}
// 创建一个客户端程序来构建责任链并提交报销申请
public class ExpenseApprovalSystem {
public static void main(String[] args) {
Approver juniorManager = new JuniorManager();
Approver middleManager = new MiddleManager();
Approver seniorManager = new SeniorManager();
juniorManager.setNext(middleManager);
middleManager.setNext(seniorManager);
ExpenseRequest smallRequest = new ExpenseRequest(500.0, "Team Lunch");
ExpenseRequest mediumRequest = new ExpenseRequest(3000.0, "Office Supplies");
ExpenseRequest largeRequest = new ExpenseRequest(6000.0, "Conference Attendance");
juniorManager.approve(smallRequest);
juniorManager.approve(mediumRequest);
juniorManager.approve(largeRequest);
}
}
在这个示例中:
Approver
接口定义了处理报销申请的方法,以及设置下一级审批人的方法。JuniorManager
,MiddleManager
, 和SeniorManager
类实现了Approver
接口,各自定义了不同的审批权限。ExpenseApprovalSystem
类构建了责任链,并提交了不同金额的报销申请。申请会从初级经理开始,如果超出其审批权限,则自动传递到下一级审批人,直至找到合适的审批人或到达链尾。
运行 ExpenseApprovalSystem
类,可以看到不同级别的经理根据各自的审批权限对报销申请进行处理。
版权声明:本文标题:常用的设计模式汇总 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1727009305a1094107.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论