十、观察者模式
定义:定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主体对象,当主体对象发生改变时,它的所有依赖者(观察者)都会受到通知并更新。
类型:行为型
适用场景
关联行为场景,建立一套触发机制
优点
观察者和被观察者之间建立一个抽象的耦合
观察者模式支持广播通信
缺点
观察者之间有过多的细节依赖、提高时间消耗及程序复杂度
使用要得当,千万不要循环调用
coding:
//1.抽象观察者
public interface LoginObserver {
void loginSuccess(String personalInfo);
}
//2.抽象主题
public interface ObservableManager {
void addListener(LoginObserver observable);
void removeListener(LoginObserver observable);
void notifyObserver(String personalInfo);
}
//3.具体主题
public class LoginActionManager implements ObservableManager {
private static List<LoginObserver> observers;
private LoginActionManager() {
}
static {
observers = new CopyOnWriteArrayList<>();
}
private static LoginActionManager mInstance = null;
public static LoginActionManager getInstance() {
if (mInstance == null) {
synchronized (LoginActionManager.class) {
if (mInstance == null) {
mInstance = new LoginActionManager();
}
}
}
return mInstance;
}
@Override
public void addListener(LoginObserver observer) {
observers.add(observer);
}
public synchronized void addListeners(List<LoginObserver> obs){
for (LoginObserver observer:obs){
observers.add(observer);
}
}
@Override
public void removeListener(LoginObserver observer) {
observers.remove(observer);
}
/**
* 登陆成功,通知所有监听者
* @param personalInfo
*/
@Override
public void notifyObserver(String personalInfo) {
for (LoginObserver observer : observers) {
observer.loginSuccess(personalInfo);
}
}
}
//4.具体观察者
public class MainPage implements LoginObserver {
@Override
public void loginSuccess(String personalInfo) {
System.out.println("MainPage收到用户登录信息:"+personalInfo);
}
}
public class PersonalPage implements LoginObserver {
@Override
public void loginSuccess(String personalInfo) {
System.out.println("PersonalPager收到登陆信息:"+personalInfo);
}
}
5.测试
public class Test {
public static void main(String[] args) {
PersonalPage personalPage = new PersonalPage();
MainPage mainPage = new MainPage();
List<LoginObserver> observers = new CopyOnWriteArrayList<>();
observers.add(personalPage);
observers.add(mainPage);
LoginActionManager.getInstance()
.addListeners(observers);
LoginActionManager.getInstance()
.notifyObserver("用户:JackChen");
}
}
输出:
PersonalPager收到登陆信息:用户:JackChen
MainPage收到用户登录信息:用户:JackChen
如上。
这里的场景是我们登陆成功后,比如个人中心页,主页都需要拿到我们登陆成功后的用户个人信息去更新界面或者进行其他逻辑操作。这里的被观察者就是登陆账号这一行为,观察者就是需要获取用户信息的界面。
从上我们也可以看出实现观察者模式所需的因素:
①抽象主题角色:
抽象主题一般是个接口,可以增加和删除观察者角色
②具体主题角色:
把所有观察者对象的引用保存在一个集合中,一般需要管理添加、删除观察者,最好做成一个管理类
③抽象观察者:
为所有的具体观察者定义一个接口,在收到信息更新后,通知具体观察者
④具体观察者
观察者模式应用场景很多,必须得掌握才行!
十一、责任链模式
https://www.liaoxuefeng.com/wiki/1252599548343744/1281319474561057
定义:
为请求创建一个接收此次请求对象的链
类型:行为型
适用场景
一个请求的处理需要多个对象当中的一个或几个协作处理
优点
请求的发送者和接收者(处理者)解耦
责任链可以动态组合(这点很重要)
缺点
责任链太长或者处理时间过长,影响性能
责任链有可能过多
可以使用责任链的场景有很多,比如android Okhttp里的拦截器处理就是责任链的变形,还有比如我们的一个流程需要多层审批,哪一层满足条件就哪一层处理。常见的,很多人都会接触到的就是这个费用报销了,不同级别的领导有不同的报销审批资格。当然,如果一个设计模式我们只能想出一种使用场景,那么绝对就是个菜比。这里我承认我是个菜比...
Demo:
* @description: 抽象处理器
*/
public interface Handler {
boolean process(CostInformation costInformation);
}
*
* @description: 报销流程 --责任链
*/
public class ReimbursementProcessChain {
private List<Handler> handlers = new ArrayList<>();
public void addHandler(Handler handler) {
this.handlers.add(handler);
}
public void removeHandler(Handler handler) {
if (handlers.size() > 0 && handlers.contains(handler)) {
handlers.remove(handler);
}
}
public void process(CostInformation costInformation) {
if (handlers.isEmpty()) return;
for (int i = 0; i < handlers.size(); i++) {
Handler handler = handlers.get(i);
boolean result = handler.process(costInformation);
System.out.println(costInformation.getApplicant() + "申请报销:" + costInformation.getAmount()
+ (result ? " dealt by:" : " transmit by:")
+ handler.getClass().getSimpleName());
if (result) {
System.out.println("----------finish--------");
break;
}
}
}
}
* @description: 参数
*/
public class CostInformation {
private String applicant;
private int amount;
public CostInformation(String applicant,int amount){
this.applicant =applicant;
this.amount =amount;
}
public String getApplicant() {
return applicant;
}
public void setApplicant(String applicant) {
this.applicant = applicant;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
* @description: 三级处理人 :经理
*/
public class ManagerHandler implements Handler {
@Override
public boolean process(CostInformation costInformation) {
if (costInformation.getAmount()<1000){
return true;
}
return false;
}
}
* @description: 二级处理人 :主管
*/
public class DirectorHandler implements Handler {
@Override
public boolean process(CostInformation costInformation) {
if (costInformation.getAmount() >= 1000 && costInformation.getAmount() < 10000) {
return true;
}
//---
return false;
}
}
* @description: 一级处理人 :CEO
*/
public class CEOHandler implements Handler{
@Override
public boolean process(CostInformation costInformation) {
//ceo can deal any amount of money ..
return true;
}
}
//客户端
public class Test {
public static void main(String[] args) {
ManagerHandler managerHandler = new ManagerHandler();
DirectorHandler directorHandler = new DirectorHandler();
CEOHandler ceoHandler = new CEOHandler();
ReimbursementProcessChain chain = new ReimbursementProcessChain();
chain.addHandler(managerHandler);
chain.addHandler(directorHandler);
chain.addHandler(ceoHandler);
CostInformation costInformation = new CostInformation("老李",1200);
CostInformation costInformation1 = new CostInformation("老张",19999);
chain.process(costInformation);
chain.process(costInformation1);
}
}
先简单说一下上面的实现:
就是报销审核我们一共有三层领导:经理,主管,老板。经理可以批准1000以下的报销,主管可以批准1000到10000的报销,老板可以审批无限金额的报销。 经理能审批的经理审,审不了的给主管,主管能审的主管审,审不了的给老板,老板能审的审,不能审的找老板娘。
代码里表现的流程就如上所说。
这里Handler,就是抽象处理者。其中的process方法,就是我们每个具体处理者必须实现的处理流程,这里返回布尔值,是说明此层的处理者是否已经处理了这个流程。
ManagerHandler/DirectorHandler/CEOHandler,就是具体处理者,都实现了自身的处理流程。
ReimbursementProcessChain:责任链,负责管理具体处理者以及多层处理者的处理顺序,可以说是责任链的核心所在。责任链中的处理顺序十分重要!!一定要依序处理!具体的来说就是handlers的add顺序贼吉尔重要!不然一个100块的报销,你跳过了经理和主管,直接给老板,社会性死亡...
廖学峰的一篇博客
可以参照他这个里面的责任链处理
我们来分析一下责任链模式实现所需要的内容:
①抽象处理者(一般为接口,返回值一般为bool,代表是否处理)
②具体处理者(会有多个,就是责任链上每一个点上的角色)
③责任链(持有所有处理者,处理方法是关键)
这里必须说一句,上面提到的只是我们常规使用的时候所需的必要元素,每种设计模式都不是一个简单demo能详尽的,可能存在多种变形,我这里只是以一个小白的视角来看此模式。能更方便我们使用此设计模式,最后能够根据具体场景自由变换,而不要被套在表面认知的壳子上。
中介者模式
中介者模式包装了一系列对象相互作用的方式,使得这些对象不必互相明显作用。使得多对多关系的类之间可以松耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。中介者模式中的中介者可以将多对多关系转化为一对多的相互作用。
适用场景
多个类相互耦合,形成了网络结构。也就是对象之间的交互操作很多且每个对象的行为操作都依赖于彼此时,为了防止在修改一个对象的行为时,同时涉及修改很多其他对象的行为,可采用中介者模式来解决紧耦合问题。
优点
①各个类之间解耦
②符合迪米特法则
③会将系统从网状结构编程以中介者为中心的星型结构。
缺点
中介者会庞大,变得复杂难以维护(一个项目中感觉能用到中介者的机会并不多,就算很多,也尽量控制个数在个级别,以上是我以为)
中介者模式UML图如下:
【摘自《图解设计模式》】
从上面我们需要提取一下中介者模式的关键组成部件:
①Mediator
抽象中介者角色,定义了Colleague角色和Mediator角色通信(通信方向M-->C)。(中介者控制多个具体Colleague角色的方法,需要每一个Colleague角色提供一个对应的方法,以此方便Mediator控制自己。这个可以是接口或者抽象类,Android设计模式里用的是抽象类,图解里用的是接口,本人更喜欢用接口。)
②ConcreateMediator
具体中介者角色,实现抽象接口
③Colleague
抽象同事类角色,定义了和Mediator进行通信的接口(方向 C-->M)。(就是上面说的每一个Colleague角色提供一个对象的方法)
④ConcreateColleagueA/B/C...
具体同事类角色,继承于抽象同事类,每个具体同事类都知道本身在小范围内的欣慰,而不知道它在大范围内的目的。
Demo1:
比如多国贸易,买石油,如果直接A to B,B to C,可能会导致使用方业务逻辑繁杂,可以提供一个中介者类,进行贸易往来的管理。
public class Country {
public String countryName;
public Country(String countryName){
this.countryName = countryName;
}
public void buyOil(Country producer){
System.out.println(this.countryName +"买了" +producer.countryName +"的石油");
}
}
//中介者类
public class TradeManager {
public static void buyOil(Country producer,Country consumer){
//do much thing
System.out.println(consumer.countryName +"购买了"+producer.countryName+"的石油");
}
}
public class Test {
public static void main(String[] args) {
method1();
}
private static void method1(){
Country america = new Country("美国");
Country china = new Country("中国");
Country japan = new Country("小日本");
Country england = new Country("英国");
//---do much thing
america.buyOil(china);
//---do much thing
china.buyOil(england);
//---do much thing
japan.buyOil(america);
//---do much thing
england.buyOil(japan);
}
private static void mediator(){
Country america = new Country("美国");
Country china = new Country("中国");
Country japan = new Country("小日本");
Country england = new Country("英国");
TradeManager.buyOil(america,china);
TradeManager.buyOil(china,england);
TradeManager.buyOil(japan,america);
}
}
上面的例子还是太浅了,下面我们以B站登录页面为例来尝试中介者模式.
因为截不了图,我就自己画了两张,嗯,还挺好看的...
image.png
可以看出B站登录方式有两种,一种是短信验证码登录;另一种是账号密码登录。两种不同的方式,会使标题、右侧切换登录方式按钮以及下方的面板有不同的样式。如果我们不用任何模式和封装来处理这个界面,那可以看出来,当我们切换登录方式时,要处理的ui元素还是比较多的,整个底部面板里的内容以及上方两个view的内容都会随之改变。虽说不一定非得用中介者模式来封装,但是这个场景倒也蛮符合中介者模式:多个控件对多个控件。我们可以改成一对多方式,通过一个Mediator来协调两种登录方式的UI显示,这个Mediator行使的职责实际上就是右侧切换按钮的点击事件。
Demo2:
* @description: 登录中介者
*/
public interface ILoginMediator {
//供各Colleague通知Mediator,从而让Mediator去协调处理
void login(LoginType type);
void createColleagues();
void switchLoginType(LoginType loginType);
}
public interface ILoginColleague {
void showLoginPanel();
void login();
default void register(){}
}
* @description: 负责管理登录页面的UI
*/
public class FakeLoginMediator implements ILoginMediator {
private ILoginColleague mSmsLogin;
private ILoginColleague mPwLogin;
//记录当前登录模式
private LoginType mCurrentLoginType = LoginType.SMS_LOGIN;
private FakeLoginMediator() {
createColleagues();
}
public static FakeLoginMediator create() {
return new FakeLoginMediator();
}
@Override
public void login(LoginType type) {
switch (type) {
case PW_LOGIN:
pwLogin();
break;
case SMS_LOGIN:
smsLogin();
break;
}
}
@Override
public void createColleagues() {
mSmsLogin = new SMSLoginColleague(this);
mPwLogin = new PWLoginColleague(this);
}
/**
* 右侧按钮切换登录方式
*
* @param loginType
*/
@Override
public void switchLoginType(LoginType loginType) {
mCurrentLoginType = loginType;
switch (loginType) {
case PW_LOGIN:
mPwLogin.showLoginPanel();
break;
case SMS_LOGIN:
mSmsLogin.showLoginPanel();
break;
}
toggleText();
}
private void smsLogin() {
//完善逻辑
System.out.println("短信登录");
}
private void pwLogin() {
//完善逻辑
System.out.println("密码登录");
}
private void toggleText() {
//根据mCurrentLoginType 显示标题和右侧文字
System.out.println("切换登录页文字");
}
}
* @description: 负责管理Panel里的UI
*/
public class PWLoginColleague implements ILoginColleague {
private ILoginMediator mMediator;
public PWLoginColleague(ILoginMediator mediator) {
if (mediator == null) {
throw new IllegalArgumentException("mediator can not be null!");
}
this.mMediator = mediator;
}
@Override
public void showLoginPanel() {
System.out.println("----切换成密码登录面板");
}
@Override
public void login() {
System.out.println("====密码登录");
mMediator.login(LoginType.PW_LOGIN);
}
@Override
public void register() {
}
}
* @description:
*/
public class SMSLoginColleague implements ILoginColleague {
private ILoginMediator mMediator;
public SMSLoginColleague(ILoginMediator mediator) {
if (mediator == null) {
throw new IllegalArgumentException("mediator can not be null!");
}
this.mMediator = mediator;
}
@Override
public void showLoginPanel() {
System.out.println("----切换成短信登录面板");
}
@Override
public void login() {
System.out.println("====短信登录");
mMediator.login(LoginType.SMS_LOGIN);
}
}
public enum LoginType {
SMS_LOGIN, PW_LOGIN
}
这里做一下简单的分析:
ILoginMediator,抽象中介者;里面定义的和Colleague角色交流的方式就是switchLoginType()。通过调用ConcreteColleague中和switchLoginType()对应的方法也就是ILoginColleague中的showLoginPanel(),使的不同的登录方式下展示不同的底部登录面板。
ILoginColleague,抽象同事;其中的定义的login()方法就是ConcreateColleague和ConcreateMediator交流的方式,也对应了ILoginMediator中的login()方法。因为两种登录方式最终都有登录行为,但是肯定是不同的请求,所以和中介者通信,让其决定到底请求哪个接口。
简单总结:
①中介者模式中,同事类中需要持有中介者,这样就可以和中介者之间通信。这样可以和其他同事类松耦合,让中介者决定接下来的行为。
②中介者负责同事类之间的通信,同事类之间是没有任何交互的!
抽象中介者中需要定义控制同事类的方法,而抽象同事中也需要定义向中介者汇报情况的方法。
享元模式
定义:享元模式提供了减少对象数量从而改善应用所需的对象结构的方式
运用共享技术有效地支持大量细粒度的对象
类型:结构型
适用场景
常用于系统底层的开发,以便解决系统的性能问题(比如String,int的缓冲池,缓冲池里有则直接返回,没有则创建)
系统中有大量相似对象、需要缓冲池的场景,需要大量复用的场景,只是少量复用的话,没必要用享元。
优点
①减少对象的创建,减低内存中对象的数量,降低系统的内存,提高效率
②减少内存之外的其他资源占用
缺点
关注内/外部状态、关注线程安全问题(感觉这个很重要哎)
使系统、程序的逻辑复杂化
扩展
内部状态:相当于属性,不随外部改变而改变
外部状态:随着环境改变而改变
享元模式的实现
实现享元模式需要三个部分:
1.抽象享元:定义需要共享的对象业务接口,非必须,但是为了保证代码容易理解,最好定义一下
2.具体享元类:实现抽象享元类的接口,完成具体逻辑,这个就是我们需要被分享的元。
3.享元工厂:用于创建具体享元类,维护相同的享元对象。内部使用了类似单例模式的方法,当请求对象已经存在时,直接返回对象,不存在时,再创建对象。享元工厂是享元模式的核心,它需要确保系统可以共享相同的对象。
关键代码:
用HashMap存储这些对象。
demo:
public interface IBookAction {
void sell(); //销售
void sealOff();//拆封
}
/**
* Create by rye
* at 2020-09-09
*
* @description: 具体享元实现
*/
public class Book implements IBookAction {
private String bookName;
private String author;
public Book(String bookName) {
this.bookName = bookName;
}
@Override
public void sell() {
System.out.println(bookName + "is sold one");
}
@Override
public void sealOff() {
System.out.println(bookName + "is sealOff one");
}
}
/**
* Create by rye
* at 2020-09-09
*
* @description: 享元模式 ->工厂类
*/
public class BookFactory {
private static final HashMap<String, Book> books = new HashMap<>();
public static Book getBook(String name) {
if (books.containsKey(name)) {
System.out.println("取出书籍:"+name);
return books.get(name);
} else {
Book book = new Book(name);
//日志
System.out.println("创建书籍:"+name);
books.put(name,book);
return book;
}
}
}
//调用
public class Test {
public static void main(String[] args) {
String[] bookNames =new String[]{"《时间简史》","《物种起源》","《麦田守望者》"};
for (int i =0;i<20;i++){
int pos = new Random().nextInt(3);
BookFactory.getBook(bookNames[pos]);
}
}
}
输出结果:
创建书籍:《时间简史》
创建书籍:《物种起源》
取出书籍:《时间简史》
取出书籍:《时间简史》
创建书籍:《麦田守望者》
取出书籍:《时间简史》
取出书籍:《麦田守望者》
取出书籍:《麦田守望者》
取出书籍:《麦田守望者》
取出书籍:《物种起源》
取出书籍:《时间简史》
取出书籍:《麦田守望者》
取出书籍:《时间简史》
取出书籍:《物种起源》
取出书籍:《时间简史》
取出书籍:《时间简史》
取出书籍:《时间简史》
取出书籍:《时间简史》
取出书籍:《麦田守望者》
取出书籍:《物种起源》
源码解析
image.png如图,Integer的缓存机制就是享元模式的最佳实践。Integer缓存-127~128的整数,超过这个数就需要new了。也是一个常见的面试题。
组合模式
桥接模式
定义:将抽象部分与它的具体实现部分分离,使他们都可以独立地变化
通过组合的方式建立两个类之间的联系,而不是继承
类型:结构型
使用场景
抽象和具体实现之间增加更多的灵活性
一个类存在两个或多个独立变化的维度,且这个两个或多个维度需要独立进行扩展
不希望使用继承,或因为多层继承导致系统类的个数剧增
优点
分离抽象部分及其具体实现部分
提高了系统的可扩展性
符合开闭原则
符合合成复用原则
缺点
增加了系统的理解与设计难度
需要正确地识别出系统汇中两个独立变化的维度。
案例
比如我们平常发消息,消息类型有普通消息、紧急消息,而发送消息的方式也有很多:邮件、微信、短信。试想一下,如果我们新增一个消息类型,如果不采用设计模式,那么需要一一实现其支持的发送方式。同理如果新增一个发送方式,比如qq,那么针对上面不同的消息类型我们也需要修改他们内部的实现,以支持新增的发送方式。
实际上,这是两个维度的变化,而且,这两个各自的变化互不影响!当然有人说,我新增了一个发送方式,但是某一个消息类型不支持,这不也是有所影响吗?实际上是理解有出入,新增的方式并不会影响消息的类型,这两个之间没有必然的联系,不能说新增了一种消息类型,就一定会新增一种发送方式,也不是说多了一种发送方式就多一个新的发送类型。两者是两个维度独立变化的因素,可以采用组合的方式处理,用桥梁模式把抽象部分和具体部分分离出来,也就是抽象和抽象组合,具体和具体组合的方式来进行设计:
Demo:
/**
* Create by rye
* at 2020-09-10
*
* @description: 维度一 : 发送方式
*/
public interface SendAction {
void send(String message,String toUser);
}
/**
* Create by rye
* at 2020-09-10
*
* @description: 维度二 : 消息类型 ,可用接口
*/
public abstract class AbstractMessage {
private SendAction mSendAction;
public AbstractMessage(SendAction action) {
this.mSendAction = action;
}
public void sendMessage(String message,String toUser){
this.mSendAction.send(message,toUser);
}
}
/**
* Create by rye
* at 2020-09-10
*
* @description: 消息类型具体实现
*/
public class NormalMessage extends AbstractMessage {
public NormalMessage(SendAction action) {
super(action);
}
}
/**
* Create by rye
* at 2020-09-10
*
* @description: 紧急消息
*/
public class UrgencyMessage extends AbstractMessage {
public UrgencyMessage(SendAction action) {
super(action);
}
@Override
public void sendMessage(String message, String toUser) {
super.sendMessage(message, toUser);
}
//扩展功能
public void notifyUser(){
}
}
/**
* Create by rye
* at 2020-09-10
*
* @description:
*/
public class EmailChannel implements SendAction {
@Override
public void send(String message, String toUser) {
System.out.println("邮件发送消息:"+message+" 给"+toUser);
}
}
/**
* Create by rye
* at 2020-09-10
*
* @description: 微信渠道
*/
public class WechatChannel implements SendAction {
@Override
public void send(String message, String toUser) {
System.out.println("微信发送消息:"+message+" 给"+toUser);
}
}
/**
* Create by rye
* at 2020-09-10
*
* @description: 测试维度实现方式不同情况下的现象:抽象类+接口 && 接口+接口
*/
public class Test {
public static void main(String[] args) {
testMessage();
}
private static void testAbstractWay(){
NormalMessage message = new NormalMessage(new EmailChannel());
message.sendMessage("哈喽,WDNMD","李二狗");
UrgencyMessage urgencyMessage = new UrgencyMessage(new WechatChannel());
urgencyMessage.sendMessage("唯独你不懂","李二狗");
}
private static void testMessage(){
XXMessage xxMessage = new XXMessage("你在哪啊","李二狗");
xxMessage.sendMessage(new WechatChannel());
}
}
从上面可以看出,桥接模式的实现必须部分:
1.维度一 抽象类或接口
2.维度二 抽象类或接口
3.维度一抽象 以组合方式持有 维度二抽象
菜鸟教程里的解释很清晰:
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。
模板方法模式
定义:定义了一个算法的框架,并允许子类为一个或多个步骤提供实现
模板方法使得子类可以不改变算法结构的情况下,重新定义算法的某些步骤
类型:行为型
适用场景
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复
优点
提供复用性
提供扩展性
符合开闭原则
缺点
类数目增加
增加了系统实现的复杂度
继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要重写(这个就有点坑了)
这个暂且保留,使用场景还需要确定。
迭代器模式
定义:提供一种方法,顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示
类型:行为型
适用场景
①访问一个集合对象的内容而又无需暴露它的内部表示(个人理解是对一些重要的数据集合进行隔离保护,最好不好让外部知晓其内部是如何实现的)
②为遍历不同的集合结构提供一个统一的接口
优点
分离了集合对象的遍历行为
缺点
类的个数成对增加,新增一个集合,需要配对实现其迭代器,一定程度上增加了系统的复杂度
虽然使用范围很广,用的很多,但是自己定义的基本上很少。
关键代码
定义迭代器接口,提供hasNext,next方法
Demo:
/**
* 实体类
*/
public class Course {
private String name;
public Course(String name){
this.name = name;
}
public String getName(){
return name;
}
}
/**
* 抽象迭代器,可全局使用
*/
public interface RIterator {
boolean hasNext();
Object next();
boolean isLast();
}
/**
* 获取具体的迭代器,数据仓库需要实现
*/
public interface RContainer {
RIterator getIterator();
//除了获取迭代器外,此接口也可以实现其他操作数据类的方法
void addCourse(Course course);
void deleteCourse(Course course);
}
/**
* 具体迭代器
*/
public class CourseIterator implements RIterator {
private List<Course> dataList;
private int position;
private Course course;
public CourseIterator(List<Course> dataList) {
this.dataList = dataList;
}
@Override
public boolean hasNext() {
return position < dataList.size();
}
@Override
public Object next() {
course = dataList.get(position);
position++;
return course;
}
@Override
public boolean isLast() {
if (position < dataList.size()) {
return false;
}
return true;
}
}
/**
* 获取具体的迭代器,数据仓库需要实现
*/
public interface RContainer {
RIterator getIterator();
//除了获取迭代器外,此接口也可以实现其他操作数据类的方法
void addCourse(Course course);
void deleteCourse(Course course);
}
/**
* 保存数据的仓库
*/
public class CourseRepository implements RContainer {
private List<Course> dataList;
public CourseRepository() {
dataList = new ArrayList<>();
}
@Override
public RIterator getIterator() {
return new CourseIterator(dataList);
}
@Override
public void addCourse(Course course) {
dataList.add(course);
}
@Override
public void deleteCourse(Course course) {
if (dataList.contains(course)) {
dataList.remove(course);
}
}
}
public class Test {
public static void main(String[] args) {
Course course = new Course("Java");
Course course2 = new Course("Python");
Course course3 = new Course("C++");
Course course4 = new Course("Flutter");
RContainer repository = new CourseRepository();
repository.addCourse(course);
repository.addCourse(course2);
repository.addCourse(course3);
printCourses(repository);
repository.addCourse(course4);
printCourses(repository);
}
private static void printCourses(RContainer repository){
RIterator iterator = repository.getIterator();
while (!iterator.isLast()){
Course course = (Course) iterator.next();
System.out.println(course.getName());
}
}
}
由上可以看出,实现迭代器模式的几个必须类和接口:
①实体类
②抽象迭代器
③抽象仓库(主要是为了获取具体的迭代器)
④数据仓库(实现抽象仓库接口,拿到具体的迭代器)
⑤具体迭代器
策略模式
定义:策略模式定义了一系列的算法,并将每一个算法封装起来,并且使他们可以相互替换。
使用场景
针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
需要安全地封装多种同一类型的操作时。
出现同一个抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时。
Demo:
策略模式可使用的场景实际上是非常非常多,只要有if-else且粒度较大的地方,策略模式理论上都可以用。使用场景里第一条就是针对同一类型问题的多种处理方式。
场景一:最近正好用到了策略模式,需求是播放器V1升级到V2,因为V2是新引入的,稳定性尚不可知,只能灰度发布V2。所以我们在使用V2的同时,必须保持V1的稳定性,且V1的业务逻辑V2也必须全部接入,使用V1还是V2由产品决定灰度比例了。这就是播放器有两套策略:V1和V2。策略模式就可以上去了。因为这个场景已经写过策略模式了,再写一遍用处不大,所以就看场景二吧。
场景二:比如我们打开一个电视视频app,就拿b站的云视听来说吧:
image.png
可以看到第一个卡片是天官赐福,很明显这是一个PGC(Professionally-generated Content 专业生产内容)视频;但是这个卡片实际上也可以配置成UGC(User-generated Content 用户生产内容)视频,或者是LIVE(直播)视频。
相当于这个卡片有三种配置策略,不同的策略的卡片跳转的详情页、播放的一些逻辑也不尽相同。看看一般的写法:
public void normalStrategy(String type) {
if (type == "UGC") {
//UGC 初始化,执行各种操作
} else if (type == "PGC") {
// 初始化,执行各种操作
} else if (type == "LIVE") {
// 初始化,执行各种操作
}
}
通过if-else来判断是何种类型,然后去执行对应的逻辑。
这样会造成几个问题:
①每次新增一个卡片类型,就会新增一条判断,就跟叠积木一样,很不稳当。如果能依赖注入,上层注入选择好的策略,然后去执行对应卡片的逻辑,稳定性和可扩展性会更好一些。
②一般在一个类中加了这种判断,基本上都会无意中添加每个选择里的部分逻辑。比如如果是UGC,可能就会操作一些UGC参数抽离、调用对应方法的逻辑。很容易会违背单一职责原则。
所以通过策略模式抽离:
//策略类
public interface ICardConfigStrategy {
void goPlay();
void jumpDetailPage();
default void println(Class<? extends ICardConfigStrategy> clazz, String msg) {
System.out.println(clazz.getSimpleName() + ":" + msg);
}
}
//具体三个策略
public class LiveCard implements ICardConfigStrategy{
@Override
public void goPlay() {
println(this.getClass(),"goPlay..");
}
@Override
public void jumpDetailPage() {
println(this.getClass(),"jumpDetailPage..");
}
}
public class PgcCard implements ICardConfigStrategy {
@Override
public void goPlay() {
println(this.getClass(),"goPlay");
}
@Override
public void jumpDetailPage() {
println(this.getClass(),"jumpDetailPage");
}
}
public class UgcCard implements ICardConfigStrategy {
@Override
public void goPlay() {
println(this.getClass(),"goPlay");
}
@Override
public void jumpDetailPage() {
println(this.getClass(),"jumpDetailPage");
}
}
//Context
//可以不声明此接口,但最好还是声明
public interface IStrategyChoiceController extends ICardConfigStrategy {
void setStrategy(ICardConfigStrategy strategy);
}
public class StrategyChoiceController implements IStrategyChoiceController {
private ICardConfigStrategy mCurrentStrategy;
@Override
public void setStrategy(ICardConfigStrategy strategy) {
this.mCurrentStrategy = strategy;
}
@Override
public void goPlay() {
mCurrentStrategy.goPlay();
}
@Override
public void jumpDetailPage() {
mCurrentStrategy.jumpDetailPage();
}
}
//客户端调用
public static void main(String[] args) {
IStrategyChoiceController strategy = new StrategyChoiceController();
strategy.setStrategy(new UgcCard());
strategy.goPlay();
}
这样通过setStrategy方法注入对应的策略类,每个策略类都负责对应策略的所有实现,遵守了单一职责原则。
实际上,上层还是会有if-else的判断的,毕竟到底是UGC还是PGC亦或是LIVE,总得有个判断不是。
只不过依赖策略模式有以下几个好处:
①统一了各个策略的实现。UGC有的操作PGC也有,通过统一的接口声明,结构上就很清晰
②遵守了单一职责原则。各个策略类负责各自的实现,职责单一。减少了Context的压力。
所以实现策略模式所需的元素就很简单:
①抽象策略类
②具体策略类
③上下文Context(注入对应策略)
状态模式
定义:
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
使用场景:
①一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
②代码中有大量与对象状态有关的条件语句。例如一个操作中含有庞大的多分支语句,且这些分支依赖于该对象的状态。
Tips:
状态模式和策略模式的结构几乎完全一样,但它们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的;策略模式的行为是彼此独立、可相互替换的。
https://zhuanlan.zhihu.com/p/91912672
上面这篇文章解释的很清楚,说几点最重要的:
①状态模式重点在于各状态的切换,从而做不同的事情;而策略模式更侧重于根据具体情况选择策略,并不涉及切换。
②状态模式不同状态下做的事情不同,而策略模式做的都是同一件事。比如聚合支付平台,有支付宝、微信、银联支付,虽然策略不同,但最终做的事都是同一件事:支付。反观状态模式,不同状态下做的都是不同的事,不能相互替换。
可举的实际开发中用到的例子还是挺多的。
1.比如登录和非登录状态下,对于展示的用户信息、会员信息、登录按钮的显隐等都各不相同。
2.或者一个按钮在有焦点态和失去焦点态、选中态三种状态的样式各不相同。
我们就第一个例子coding:
//State(各种状态的统一操作)
public interface ILoginState {
void dealPersonalInfo();
void dealVipStatus();
void dealLoginBtn();
default void println(String msg) {
System.out.println(msg);
}
}
//具体状态类
public class LoginState implements ILoginState {
@Override
public void dealPersonalInfo() {
println("showPersonalInfo");
}
@Override
public void dealVipStatus() {
println("dealVipStatus---hasManyStatus");
}
@Override
public void dealLoginBtn() {
println("hideLoginBtn");
}
}
public class LogoutState implements ILoginState {
@Override
public void dealPersonalInfo() {
println("hidePersonInfo");
}
@Override
public void dealVipStatus() {
println("dealVipStatus---onlyOneStatus");
}
@Override
public void dealLoginBtn() {
println("showLoginBtn");
}
}
//Context
//抽象context,可以不加,最好是加上
public interface ILoginController extends ILoginState {
void login();
void logout();
}
public class LoginController implements ILoginController {
private ILoginState mState;
public void setState(ILoginState mState) {
this.mState = mState;
}
@Override
public void login() {
setState(new LoginState());
}
@Override
public void logout() {
setState(new LogoutState());
}
@Override
public void dealPersonalInfo() {
mState.dealPersonalInfo();
}
@Override
public void dealVipStatus() {
mState.dealVipStatus();
}
@Override
public void dealLoginBtn() {
mState.dealLoginBtn();
}
}
//客户端
public static void main(String[] args) {
ILoginController loginController = new LoginController();
loginController.login();
loginController.dealLoginBtn();
loginController.dealVipStatus();
}
可以看出来状态模式和策略模式的操作基本相同,虽然操作看起来相同,但是代码本质上要处理的事情却是不尽相同.具体的区别上面已经说过了,这里就不赘述了.
从上面可以看出,要实现状态模式也是三个部分:
①抽象状态类(或者是接口,定义不同状态下的统一行为)
②具体状态类(处理不同状态下的各个行为)
③Context(上下文,管理状态)
模板方法
emmm...这个方法就算不知道设计模式的,也肯定很多人都在用。
定义:
定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(通俗的说,就是我们可以在基类中定义一些抽象方法,这些抽象方法会在基类的某些默认方法中调用。之所以定义成抽象方法,是因为不同的子类的部分逻辑不同)
使用场景:
嗯,就很多...
Demo:
这个实在没什么好写的。
public abstract class VideoDetailAct {
abstract void buildParams();
public void play() {
buildParams();
System.out.println("参数构造完毕...goPlay");
}
}
public class UgcDetailAct extends VideoDetailAct {
@Override
void buildParams() {
System.out.println("构造UGC视频参数....");
}
}
public class PgcDetailAct extends VideoDetailAct {
@Override
void buildParams() {
System.out.println("构造PGC播放参数完毕...");
}
}
public class Test {
public static void main(String[] args) {
VideoDetailAct detailAct = new PgcDetailAct();
detailAct.play();
}
}
实现模板方法所需元素:
①抽象类
②抽象类中默认函数中调用的抽象函数
③子类
备忘录模式
行为型模式
定义:
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将对象恢复到原先保存的状态。
使用场景:
①需要保存一个对象在某个时刻的状态或部分状态。
②一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
看一下备忘录模式的UML类图:
备忘录模式有三个组成部分:
①Originator(发起人):
负责创建一个备忘录,可以记录、恢复自身的内部状态。同时有一点很重要,只要它一个可以设置备忘录的状态!不允许其他类来调用备忘录类Memento的构造函数与相关方法。如果其他类调用了导致在备忘录中保存的历史状态发生改变,通过撤销操作所回复的状态就再是真实的历史状态,备忘录模式也就失去了本身的意义。
②Memento(备忘录):
备忘录角色,用于存储Originator的内部状态。
③Caretaker(看守人):
负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象,只有简简单单的一个存储功能。
优点:
给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便的回到某个历史的状态。
实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:
消耗资源,如果类的成员变量过多,势必会占用很大的资源。
网友评论