1 设计模式
设计模式是一种通用的解决方案,是解决软件开发中经常出问题的最佳实践,可以在不同场景使用。
设计模式提供了一种通用的数据和概念,用于描述和交流解决方案。
常用的设计模式包含:
1、工厂模式:用于创建对象的模式,将对象的创建过程封装起来,使代码更加灵活和可扩展。
2、单例模式:确保一个类只有一个实例,并提供全局访问点。
3、适配器模式:将一个类的接口转换成客户端所期望的另一个接口,使得原本不兼容的类可以协同工作。
4、观察者模式:定义对象之间的一对多依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
5、策略模式:定义一系列算法,将每个算法都封装起来并使它们可以相互替换,以达到不同的行为。
6、建造者模式:将一个复杂对象的构建过程分解到多个简单对象的构建过程,使得构建过程更加灵活和可控。
7、装饰者模式:动态地给一个对象添加一些额外的职责,而不需要修改原始对象的代码。
8、外观模式:
9、迭代器模式:
10、模版方法模式:定义一个算法骨架,将一些步骤延迟到子类中实现,以达到不同的行为。
2 单例模式
单例模式是一种对象创建型模式,其有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当你想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例;
2、避免对资源的多重占用(比如写文件操作)
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号
2、WEB中的计数器,不能每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。
注意事项:gerInstance()方法中需要使用同步锁synchronized(Singleton class)防止多线程同时进入造成Instance被多次实例化。
为了防止在外部对单例类实例化,将其构造函数的可见性设为private;在单例类内部定义了一个Singleton类型的静态对象作为可供外部访问的唯一实例。
3 观察者模式
引入
设计模式可以分为三类:创建性、结构性、行为型
a 主要解决“对象的创建”问题
b 主要解决“类或者对象的组合或组装”问题
c 主要解决“类或对象之间的交互”问题
行为型设计模式比较多,有11个,占了23种经典设计模式的一半。他们分别是:观察者模式、模版模式、策略模式、责任链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
观察者模式在实际使用中比较多。
《设计模式》定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
虽然使用场景很多,比价经典、常用的方式如下:
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
public interface Observer {
void update(Message message);
}
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverTwo is notified.");
}
}
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
应用场景
举个例子。一个P2P投资理财系统,用户注册成功之后,发送投资体验金。
虽然注册接口做了两件事,注册和发放体验金,违反单一职责原则。但是,如果没有扩展和修改的需求,现在的代码实现是可以接受的。如果非要用观察者模式,就需要引入更多的类和更加复杂的代码结构,反而是一种过渡设计。
相反,如果需求频繁比那栋,比如,用户注册成功之后,不再发放体验金,而是改为发送优惠券,并且还要给用户发送一封“欢迎注册成功”的站内信。这种情况下,就需要频繁改动register()函数中的代码,违反开闭原则。而且,如果注册成功之后需要执行的后续操作越来越多,那register()函数的逻辑会越来越复杂,也就影响到代码的可读性和可维护性。
这个时候观察者模式就派上用场了。利用观察者模式,重构代码如下:
public interface RegObserver {
void handleRegSuccess(long userId);
}
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver implements RegObserver {
private NotificationService notificationService;
@Override
public void handleRegSuccess(long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
public class UserController {
private UserService userService; // 依赖注入
private List<RegObserver> regObservers = new ArrayList<>();
// 一次性设置好,之后也不可能动态的修改
public void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
for (RegObserver observer : regObservers) {
observer.handleRegSuccess(userId);
}
return userId;
}
}
当需要添加新的观察者的时候,比如,用户注册成功之后,推送用户注册信息给大数据征信系统,基于观察者的代码实现,UserController类的register()函数完全不需要修改,只需要在添加一个实现了RegObserver接口的类,并且通过setRegObserver()函数将它注册到UserController类中即可。
不过,可能会看到,当把发送体验金替换为发送优惠券的时候,需要修改RegPromotionObserver类中的handleRegSuccess()函数的代码,这个还是违反开闭原则呀?确实是的,不过,相对于register()函数来说,handleRegSuccess()函数的逻辑要简单很多,修改更不容易出错,引入bug的风险更低。
实际上,设计模式要干的事情就是解耦。创建型模式是将创建和使用代码解耦,结构性模式是将不同功能代码解耦,行为型模式是将不同行为解耦,具体到观察者模式,是将观察者和被观察者代码解耦。 借用设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚低耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。
基于不同应用场景的不同实现方式
观察者模式的应用场景非常广泛,小到代码层面的解耦,大道架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
- 上面的实现方式是同步阻塞的实现;
- 根据需要也可以改为异步非阻塞的实现。加入注册解耦是一个调用比较频繁,且对性能非常敏感的接口,这时候可以启动一个新的线程来执行观察者的handleResSuccess()函数,这样register的结果可以及时返回给客户端。
- 如何实现一个异步非阻塞的观察者模式呢?建单的做法是,在每个handleRegSuccess()函数中,创建一个新的线程执行代码。不过,还有更优雅的实现方式,就是基于EventBus来实现。
观察者模式不同应用场景下,有不同的实现方式,包括:同步阻塞、异步非阻塞、进程内、进程间的实现方式。
- 同步阻塞是最经典的实现方式,主要是为了代码解耦;
- 异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;
- 进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。
异步非阻塞观察者模式的简易实现
我们有两种实现方式:
- 一种是,在每个handlerRegSuccess()函数中创建一个新的线程执行代码逻辑
- 一种是,在UserController的register()函数中使用线程池来执行每个观察者的handleRegSuccess()函数。
// 第一种实现方式,其他类代码不变,就不再重复罗列
public class PegPromotionObserver implements RegOberver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(long userId){
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
promotionService.issueNewUserExperienceCash(userId);
}
});
thread.start();
}
}
// 第二种实现方式,其他类代码不变,就没有再重复罗列
public class UserController {
private UserService userService;// 依赖注入
private List<RegObserver> regObservers = new ArrayList<>();
private Executor executor;
public UserController(Executor executor) {
this.executor=executor;
}
public void setRegObservers(List<RegObserver> observers){
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
// 省略输入参数的校验代码
// 省略userService.register()异常的try-catch代码
long userId = userService.register(telephone,password);
for(RegObserver observer : regObservers) {
executor.execute(new Runnable() {
@Override
public void run(){
observer.handleRegSuccess(userId);
}
});
}
return userId;
}
}
对于第一种实现方式,频繁的创建和销毁线程会比较耗时,并且并发线程数无法控制,创建过多的线程会导致堆栈溢出。第二种实现方式,尽管利用了线程池解决了第一种实现方式的问题,但是线程池、异步执行逻辑都耦合在了register()函数中,增加了这部分业务代码的维护成本。
如果我们的需求更加极端一段,需要在同步阻塞和异步非阻塞之间灵活切换,那就要不停地修改UserController的代码。除此之外,如果在项目中,不止一个业务模块需要用到异步非阻塞观察者模式,那这样的代码实现也无法做到复用。
我们知道,框架的作用有:隐藏实现细节,降低开发难度,做到代码复用,解耦业务与非业务代码,让程序员聚焦业务开发。针对异步非阻塞观察者模式,我们也可以将它抽象成框架达到这样的效果,而这个框架就是EventBus。
EventBus需求
EventBus翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus就是一个比较著名的EventBus框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。
现在,我们就通过例子来看一下,Guava EventBus具有哪些功能。还是用那个用户注册的例子,我们用Guava EventBus重新实现一下,代码如下示例:
public class UserController {
private UserService userService;
private EventBus eventBus;
private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 20;
public UserController() {
// eventBus=new EventBus(); // 同步阻塞模式
eventBus = new AsyncEventBus(Executors.newFixedThreadPool(DEFAULT_EVENTBUS_...));
}
public void setRegObservers(List<Object> observers){
for(Object observer:observers){
eventBus.register(observer);
}
}
public Long register(String telephone,String password){
// 省略
// 省略
long userId = userService.register(telephone,password);
eventBus.post(userId);
return userId;
}
}
RegPromotionObserver
RegNotificationObserver
类不变-----看错了!!!看解释才注意
public class RegPromotionObserver {
private PromotionService promotionService;//依赖注入
@Subscribe
public void handleRegSuccess(long userId){
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver {
private NotificationService notificationService;
@Subscribe
public void handleRegSuccess(long userId) {
notificationService.sentInboxMessage(userId,"...");
}
}
利用EventBus框架实现的观察者模式,跟从零开始编写的观察者模式相比,从大的流程上来说,实现思路大致一样,都需要定义Observer,并且通过register()函数祖册Observer,也都需要通过调用某个函数(比如,EventBus中的post()函数)来给Observer发送消息(在EventBus中消息被称作时间event)。
但在实现细节方面,又有所区别。基于EventBus,我们不需要定义Observer接口,任意类型的对象都可以注册到EventBus中,通过@SubScribe注册来标明哪个类中的哪个函数也接收被观察者发送的消息。
接下来,我们详细地讲一下,Guava EventBus的几个主要的类和函数。
(1)EventBus、AsynEventBus
Guava EventBus对外暴露的所有可调用接口,都封装在EventBus类中。其中,EventBus实现了同步阻塞的观察者模式,AsynEventBus继承自EventBus,提供了异步非阻塞的观察者模式,具体使用如下:
- EventBus eventBus = new EventBus; // 同步阻塞模式
- EventBus eventBus = new
AsyncEventBus(Executors.newFixedThreadPool(8)); // 异步
(2)register()函数
EventBus类提供了register()函数用来注册观察者。具体的函数定义如下。它可以接收任何类型(Object)的观察者。而在经典的观察者模式的实现中,register()函数必须接受实现了同一Observer接口的类对象。
- public void register(Object object)
(2)unregister()函数
相对于register()函数,unregister()函数用来从EventBus中删除某个观察者。
- public void unregister(Object object);
(3)post函数
EventBus类提供了post()函数,用来给观察者发送消息。
- public void post(Objext event);
跟经典的观察者模式不同之处在于,当我们调用post()函数发送消息的时候,并非把消息发送给所有的观察者,而是发送给可匹配的观察者。所谓的可匹配指的是,能接受的消息类型是发送消息(post函数定义中的event)类型的父类。举个例子来解释一下。
比如,AObserver能接收的消息类型是XMsg,BObserver能接收的消息类型是YMsg,CObserver能接收的消息类型是ZMsg。其中XMsg是YMsg的父类,当我们如下发送消息的时候,相应能接收到消息的可匹配观察者如下所示:
XMsg xMsg = new XMsg();
YMsg yMsg = new YMsg();
ZMsg zMsg = new ZMsg();
post(xMsg); => AObserver接收到消息
post(yMsg); => AObserver、BObserver接收到消息
post(zMsg); => CObserver接收到消息
那每个Observer能接收的消息类型是在哪里定义的呢?我们来看下GuavaEventBus最特别的一个地方,那就是@Subscribe注解。
(4)@Subscribe注解
EventBus通过@Subscribe注解来标明,某个函数接收那种类型的消息。具体的使用代码如下所示。在DObserver类中,我们通过@Subscribe注解了两个函数f1();f2()。
public DObserver {
//...省略其他属性和方法...
@Subscribe
public void f1(PMsg event) { //... }
@Subscribe
public void f2(QMsg event) { //... }
}
通过register()函数将DObserver类对象注册到EventBus的时候,EventBus会根据@Subscribe注解找到f1() 和 f2(),并且将两个函数能接收的消息类型记录下来(PMsg->f1, QMsg->f2)。当我们通过post()函数发送消息(比如QMsg消息)的时候,EventBus会通过之前的记录( QMsg->f2),调用相应的函数(f2)。
原理
我们重点来看下,EventBus中两个核心函数register()和post()的实现原理。弄懂了他们,基本上就弄懂了整个EventBus框架。下面两张图是这两个函数的实现原理图。
从图中我们可以看到,最关键的一个数据结构是Observer注册表,记录了消息类型和可接受消息函数的对应关系。当调用register()函数注册观察者的时候,EventBus通过解析@Subscribe注解,生成Observer注册表。当调用post()函数发送消息的时候,EventBus通过注册表找到相应的可接收消息的函数,然后通过Java的反射语法来动态地创建对象、执行函数。对于同步阻塞模式,EventBus在要给线程内一次执行相应的函数。对于异步非阻塞模式,EventBus通过一个线程池来执行相应的函数。
总结
设计模式要干的事情就是解耦,创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦,具体到观察者模式,它将观察者和被观察者代码解耦。
借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则,高内聚低耦合等特性,一次来控制和应对代码的复杂性,提高代码的可扩展性。
观察者模式的应用场景非常广泛,小到代码层层面的解耦,大到框架层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。不同的应用场景和需求下,这个模式也有截然不同的实现方式,有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
- 请对比一下“生产者 - 消费者”模型和观察者模式的区别和联系。
发布-订阅模型,是一对多的关系,可以以同步的方式实现,也可以以异步的方式实现。
生成-消费模型,是多对多的关系,一般以异步的范式实现。
两者都可以达到解耦的作用。
关于观察者模式内容参考地址:https://blog.youkuaiyun.com/zhizhengguan/article/details/122325700