基础总结--系统设计

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,提供了异步非阻塞的观察者模式,具体使用如下:

  1. EventBus eventBus = new EventBus; // 同步阻塞模式
  2. EventBus eventBus = new
    AsyncEventBus(Executors.newFixedThreadPool(8)); // 异步

(2)register()函数
EventBus类提供了register()函数用来注册观察者。具体的函数定义如下。它可以接收任何类型(Object)的观察者。而在经典的观察者模式的实现中,register()函数必须接受实现了同一Observer接口的类对象。

  1. public void register(Object object)

(2)unregister()函数
相对于register()函数,unregister()函数用来从EventBus中删除某个观察者。

  1. public void unregister(Object object);

(3)post函数
EventBus类提供了post()函数,用来给观察者发送消息。

  1. 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); => AObserverBObserver接收到消息
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值