设计模式系列文章目录导读:
设计模式 ~ 面向对象 6 大设计原则剖析与实战
设计模式 ~ 模板方法模式分析与实战
设计模式 ~ 观察者模式分析与实战
设计模式 ~ 单例模式分析与实战
设计模式 ~ 深入理解建造者模式与实战
设计模式 ~ 工厂模式剖析与实战
设计模式 ~ 适配器模式分析与实战
设计模式 ~ 装饰模式探究
设计模式 ~ 深入理解代理模式
设计模式 ~ 小结
模板方法模式的定义
模板方法模式 (Template Method Parttern
) 是一种非常简单、应用非常广泛的模式
什么是模板方法模式:定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤
总结成一句话就是:父类封装算法不可变的部分,可变的部分子类通过继承来扩展
模板方法模式涉及两个角色:
-
抽象模板角色(Abstract Template)
抽象模板也就是抽象类,里面包含模板方法,模板方法也就是上面说的算法框架,模板方法由一个或者几个抽象方法组成,抽象方法由子类来实现
-
具体模板角色(Concrete Template)
该角色实现抽象模板中的一个或多个抽象方法
模板方法模式类图如下所示:
根据上面的类图可以很容易实现模板方法模式:
public abstract class AbstractClass {
//模板方法,封装固定算法逻辑
public final void templateMethod(){
operate1();
System.out.println("处理相关逻辑...");
operate2();
System.out.println("处理相关逻辑2...");
}
protected abstract void operate1();
protected abstract void operate2();
}
// 子类实现父类的抽象操作
public class ConcreteClass extends AbstractClass {
@Override
protected void operate1() {
System.out.println("操作1");
}
@Override
protected void operate2() {
System.out.println("操作2");
}
}
// 测试
public class Client {
public static void main(String[] args) {
ConcreteClass concreteClass = new ConcreteClass();
concreteClass.templateMethod();
}
}
// 控制台输出:
操作1
处理相关逻辑...
操作2
处理相关逻辑2...
模板方法模式的优点
模板方法模式的优点主要有三个:
- 封装不变部分,扩展可变部分
- 提取公共代码,便于维护
- 行为由父类控制,子类负责实现
模板方法模式的应用场景
- 多个子类有公共方法,并且逻辑基本相同
- 可以把重要的、复杂的、核心算法设计成模板方法,变化的部分交给子类来实现
- 重构代码时,模板方法模式是经常使用的模式,将相同的代码抽取到父类中
项目实践
Android Fragment 懒加载
熟悉 Android
开发的都知道,当 ViewPager
配合 Fragment
使用的时候,会出现两个现象:
- Fragment 从不可见又变成可见时 View 会被重建
- Fragment 不可见时也会调用网络请求
一般界面的承载都是放在 Fragment
中, 如果这个 Fragment
放在 ViewPager
中都会出现这样的问题
我们可以通过 模板方法模式
将这个通用的固定的逻辑封装在父类(BaseFragment
)中
下面我们来看下如何解决这些问题
ViewPager
配合 Fragment
使用的时候,会触发 setUserVisibleHint
方法,在这个方法里可以判断 Fragment
是否可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isSetUserVisibleHintInvoked = true;
if (isVisible = getUserVisibleHint()) {
onVisible();
}
}
//当前界面可见状态,且执行了 onCreateView 方法才去加载数据
private void onVisible() {
if (isVisible && isInvokedOnCreateView) {
lazyLoad();
}
}
/**
* 加载数据的操作直接放在该方法里
*/
protected void lazyLoad() {
// 由子类实现
}
然后在 onCreateView
里判断处理 onVisible
// 保存已经创建的布局
private View rootView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
isInvokedOnCreateView = true;
// 如果已经创建过,则断开和父View之间的关系,然后返回
// Fragment 从不可见又变成可见时 View 会被重建
if (null != rootView) {
ViewGroup parent = (ViewGroup) rootView.getParent();
if (null != parent) {
parent.removeView(rootView);
}
} else {
rootView = createView(inflater, container);
}
// 说明不是和 ViewPager 一起用
if (!isSetUserVisibleHintInvoked) {
isVisible = true;
}
// 判断是否需要
onVisible();
return rootView;
}
protected View createView(LayoutInflater inflater, ViewGroup container) {
return inflater.inflate(getLayoutId(), container, false);
}
// 由子类来实现
protected abstract int getLayoutId();
上面的基本上实现了 Fragment
的懒加载功能,因为父类不知道子类的布局是什么样的所以子类需要实现 getLayoutId
方法,如果有网络请求的话,父类也不知道具体的请求是什么,所以子类需要实现 lazyLoad
综上 BaseFragment
封装了固定不变的算法:Fragment
懒加载功能;变化的部分如 getLayoutId
和 lazyLoad
由子类来实现
Android BaseListFragment 封装通用列表
在实际开发中,其实很多界面都是列表形式的,不管是那种列表一般都会包含以下几个功能:
- 下拉刷新功能
- 加载更功能
- 加载更多失败点击Item重试
- 列表页码的管理
一开始开发的时候没有这样的一个 BaseListFragment
类,列表页面不同的人实现,有不同的风格,而且有些实现方案还有些问题
这个时候可以将列表的通用功能封装到父类,利于将来的维护和扩展,把这样的父类姑且叫做 BaseListFragment
BaseListFragment
是不知道子类的具体业务是什么,所以必须让子类来实现
/**
* 加载列表数据
*/
protected abstract void loadListData();
不管是 刷新
还是 加载更多
都是调用 loadListData
/**
* 下拉刷新
*/
private void refresh() {
mIsRefreshing = true;
mRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (!mAutoRefresh) {
mRefreshLayout.setRefreshing(false);
mAutoRefresh = true;
} else {
mRefreshLayout.setRefreshing(true);
}
// 加载列表数据
loadListData();
}
});
}
// 加载更多
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (bottom(recyclerView)) {
if (canLoadMore() && mAdapter.getListCount() != 0) {
if (!mIsLoadingMore) {
mIsLoadingMore = true;
loadListData();
}
}
}
}
});
父类除了不知道子类的具体业务,也不知道子类要渲染怎样的列表,所以 Adapter
的创建也必须是子类做
protected abstract BaseListAdapter createAdapter();
当然还有其他的逻辑如加载更多的方式是 pageIndex
还是 cursorMark
游标的方式,还有对异常的处理等等,这些具体的实现细节在这里就不贴出来了
总之通过 模板方法模式
重构列表功能后,实现新的列表界面会更加简单,只需要关注自身相关的业务逻辑和具体的展示逻辑即可,实现相应的几个抽象方法即可,数据请求成功后调用父类的渲染方法即可;另一方面也利于代码的维护,将来如果发现封装的逻辑有问题只需要修改 BaseListFragment
即可
小结
一般来说,模板方法封装的是固定的算法逻辑,所以一般都需要加上 final 关键字,防止子类覆写模板方法
模板方法模式是一个使用非常广泛的模式,是日常开发中必须掌握的设计模式
掌握它能够帮助我们更好的重用代码,维护代码更加方便。
Reference
- 《设计模式之禅》
- 《Java设计模式及实践》
- 《Java设计模式深入研究》
- 《设计模式(Java版)》
如果你觉得本文帮助到你,给我个关注和赞呗!
另外,我为 Android 程序员编写了一份:超详细的 Android 程序员所需要的技术栈思维导图。
如果有需要可以移步我的 GitHub -> AndroidAll,里面包含了最全的目录和对应知识点链接,帮你扫除 Android 知识点盲区。 由于篇幅原因只展示了 Android 思维导图: