设计模式实践-模板模式
什么是模板模式?什么时候用?
模板模式,其实在我们日常开发中,算是除了单例模式之外,经常会使用的一种设计模式。例如我们安卓开发中,界面类一般为Activity或Fragment。它们提供了一些系列回调函数,就像流程一样。
其实这就是模板模式的一种体现,Activity或Fragment怎么new出来的,怎么被栈管理的,怎么添加到手机界面上的,各种复杂的事情,这些我们一般统统都不用管,我们只要在特定的回调做事情就好了。
而一般我们都会在Activity或Fragment上做自己的一套封装,添加一些抽象方法,界面类继承复写方法即可,这些大家肯定非常熟悉了。其实它有个名字,就叫模板模式。
怎么实现模板模式?
-
基类,一般为抽象类,定义不变的基本流程,不能决定的事情,就提供抽象方法给子类,抽象方法可以在基类声明也可以在接口中声明(如果其他种类的流程也是一致时,一般会抽取到接口)。
-
子类,继承基类,实现抽象方法,实现特定逻辑。
模板模式实践,打造你的基类
就以我们最熟悉的Activity和Fragment为例。一般我们会在Activity的onCreate()回调中,一般有这几个步骤:
-
设置布局
-
查找控件
-
绑定控件
-
请求数据
而如果我们的界面非常复杂,这些操作都全写在onCreate()方法中,就会非常多,所以一般我们都会抽取函数,那么我们每次都需要写这些重复代码么?不!这时就需要用到模板模式。
实现步骤
- 声明接口,由于我们在Activity和Fragment中做事情的流程是一样的,也会经历上面4个流程,所以我们抽取一个接口,让基类去实现。
public interface LayoutCallback {
/**
* 设置布局之前回调,一般在该回调中获取前面跳转传递的数据
*/
void onLayoutBefore();
/**
* 获取布局LayoutId,会设置到布局中
*/
int onInflaterViewId();
/**
* 查找View和给View进行相关设置等
*/
void onBindView(View view);
/**
* 设置数据,请求接口等
*/
void setData();
}
- 声明和创建基类,我们一般继承Activity或它的子类
public abstract class BaseActivity extends AppCompatActivity implements LayoutCallback {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在设置布局前调用
onLayoutBefore();
//获取子类布局,设置到布局中
int id = onInflaterViewId();
if (id != -1) {
setContentView(id);
}
//设置完毕,回调子类进行查找控件
onBindView(findViewById(android.R.id.content));
//回调子类,让其请求数据
setData();
}
}
//...
public abstract class BaseFragment extends Fragment implements LayoutCallback {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//回调子类获取前面传递的参数
onLayoutBefore();
}
@Nullable
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//回调子类,设置布局
return inflater.inflate(onInflaterViewId(), container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//View创建后,回调子类查找、绑定控件
onBindView(getView());
//设置数据
setData();
}
}
- 创建子类,例如关于界面,继承基类,复写抽象方法
public class AboutFragment extends BaseFragment {
private TopBar vTopBar;
public static AboutFragment newInstance() {
return new AboutFragment();
}
@Override
public int onInflaterViewId() {
return R.layout.me_about;
}
@Override
public void onBindView(View view) {
findView(view);
bindView();
}
private void findView(View view) {
vTopBar = view.findViewById(R.id.top_bar);
}
private void bindView(View view) {
//配置标题和返回键
vTopBar.setTitle(R.string.me_setting_about);
vTopBar.addLeftBackImageButton().setOnClickListener(v -> {
if (getActivity() != null) {
getActivity().finish();
}
});
//...
}
@Override
public void setData() {
//显示当前版本
String version = PackageUtil.getVersionName(getActivity());
//...
//如果需要请求接口,再请求...
}
}
总结
优点:
-
将流程在基类中规定,如果不能修改在方法中可以加final修饰,方式流程背篡改。
-
复用代码,一些公用代码可以定义在基类,达到面向对象的封装和复用。
-
基类中不能决定的事情,提供抽象方法,让子类复写,来决定逻辑流向。
缺点:
-
必须要理解基类的逻辑流向,否则不知道在哪个复写什么方法,如果基类非常复杂,容易造成阅读困难。(写好注释!)
-
不宜在基类提供过多的抽象方法,容易造成过度设计。如果某种情况和基类的逻辑流向不一致时,可能会导致需要修改基类。