设计模式 ~ 模板方法模式分析与实战

设计模式系列文章目录导读:

设计模式 ~ 面向对象 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 懒加载功能;变化的部分如 getLayoutIdlazyLoad 由子类来实现

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 思维导图:
超详细的Android技术栈

发布了161 篇原创文章 · 获赞 1125 · 访问量 125万+

猜你喜欢

转载自blog.csdn.net/johnny901114/article/details/100584000