IT行业,一直讲一句话,拼到最后都拼的是“内功”,而内功往往就是指我们处理问题的思路、经验、想法,而对于开发者来说,甚至对于产品也一样,都离不开一个“宝典”,就是设计模式。今天我们一起借助Android源码去探索一下设计的六大基本原则。同时结合我工作经验中的两个例子,来总结实践一下。
1.背景&定义
定义:
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
理解:
设计模式是什么?设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
所以,以我自己的理解,设计模式就是一套为了让我们开发写出复用性、可靠性、扩展性更好的代码的经验集合。
2.六大原则定义
2.1. 开闭原则(Open Close Principle )
简言之,对扩展开放,对修改关闭。我们在想要对一个类的功能进行变更的时候,可以通过扩展的方式实现,而不是通过修改这个类本身。那会带来很多灾难性的事故,因为依赖这个类的功能模块可能有很多,一处修改可能导致多处需要修改、同时这个带来的风险是您所不能承受的。
2.2. 单一职责原则(Single Responsibility Priciple )
这个很好理解,就是一个类尽量只负责一个功能。例如:图片加载控件,需要将图片的加载和缓存进行分开。
2.3. 里氏代换原则(Liskov Substitution Principle)
任何基类出现的地方,都可以用子类来替换。这不就是指,Java的多态性之一继承吗?
2.4. 依赖倒置原则(Dependence Inversion Principle)
针对接口编程,依赖于抽象而不依赖于具体。我们在代码编程中,如果想做到以不变应万变,这个原则是基础。您试想,如果一个功能、框架、APP的时候,一层一层的都是对象嵌套调用,那您想想,如果想要修改中间某个对象的功能,此时是不是灾难性的事故现场。所以在编程中,尽量把一个类的进行抽象,其他依赖于这个对象的功能模块,可以去调用这个接口去完成对象具体功能的调用。
2.5. 接口隔离原则(Interface Segregation Principle)
**使用多个隔离的接口,比使用单个接口要好。**既然上一个依赖倒置原则,让我们在代码设计开发中,要把一个类的功能通过接口公开出去,从而让其他依赖模块,可以依赖接口编程。那么自然而然有另外一个问题,这个对象很负责,他有很多功能,那此时我们应该把这个对象的多个功能,分别封装成不同的接口,而不是写在一个接口类中。
2.6. 最少知道(迪米特)原则(Demeter Principle)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
3.实战到底
3.1 ImageLoader框架
大家在APP开发中,应该99.9%的开发人员,都用过了各种图片加载框架,例如Picasso、Glide、Fresco,您有没有过这样的冲动去自己写一个类似的图片加载框架呢?接下来,我们就以此为例,边实战边去熟悉一下六大基本原则。
整体设计模式Demo代码
3.1.1 V1.0 基础需求
基础需求:实现一个图片加载框架,支持传入url以及Imageview,可以去下载完成,并且展示,如果此图片已经下载过,则直接加载,不用再次下载。
ImageLoader .java
package com.itbird.design.principle;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 自定义图片加载框架
* Created by itbird on 2022/3/28
*/
public class ImageLoader {
private static final String TAG = ImageLoader.class.getSimpleName();
//这里使用LruCanche算法,做内存存储,原理其实就是基于hashmap,存储key、value
private LruCache<String, Bitmap> mImageCache = null;
//下载线程池
private ExecutorService mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private Handler mHandler;
private static volatile ImageLoader mInstance = null;
private ImageLoader(Context context) {
mHandler = new Handler(context.getMainLooper());
initLruCache();
}
/**
* 初始化LruCanche算法
* 需要注意两点,一个是最大内存大小,一个sizeOf每个value的大小
* 必须指定这两个,因为会涉及到LruCache的存儲于回收
*/
private void initLruCache() {
//获取虚拟机最大内存
int memorySize = (int) (Runtime.getRuntime().maxMemory() / 1024);
//以最大内存的四分之一去作为算法缓存
mImageCache = new LruCache<String, Bitmap>(memorySize / 4) {
@Override
protected int sizeOf(String key, Bitmap value) {
//每张图片占用的内存大小
return value.getAllocationByteCount() / 1024;
}
};
}
public static ImageLoader getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(context);
}
}
}
return mInstance;
}
/**
* 给view设置图片
*
* @param url
* @param imageView
*/
public void setImageView(String url, ImageView imageView) {
if (TextUtils.isEmpty(url) || imageView == null) {
return;
}
Bitmap bitmap = mImageCache.get(url);
Log.e(TAG, "" + bitmap);
if (bitmap == null) {
downloadImage(url, imageView);
} else {
imageView.setImageBitmap(bitmap);
}
}
/**
* 下载图片
*
* @param url
* @param imageView
*/
private void downloadImage(String url, ImageView imageView) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadUrlBitmap(url);
Log.e(TAG, "url = " + url);
Log.e(TAG, "" + bitmap.getByteCount());
//cackback主线程
mHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
if (mImageCache != null) {
mImageCache.put(url, bitmap);
}
}
});
}
/**
* 真正的下图图片函数
*
* @param urlString
* @return
*/
private Bitmap downloadUrlBitmap(String urlString) {
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
Bitmap bitmap = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
bitmap = BitmapFactory.decodeStream(in);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] bytes = bos.toByteArray();
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
}
测试Activity代码MainActivity.java
package com.itbird.design;
import android.os.Bundle;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.itbird.design.principle.ImageLoader;
public class MainActivity extends AppCompatActivity implements Observer, UIHandler.IHandler {
Button button;
TextView textView;
ImageView imageView;
UIHandler mUIHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mUIHandler = new UIHandler(this);
//测试六大基本原则
testPrinciple();
}
private void initView() {
button = findViewById(R.id.button);
textView = findViewById(R.id.textview);
imageView = findViewById(R.id.imageview);
}
/**
* 图片地址集合
*/
private final String url[] = {
};
private final int MSG_SET_IMAGE_VIEW = 1;
@Override
public void handleMessage(Message message) {
if (message == null) {
return;
}
switch (message.what) {
case MSG_SET_IMAGE_VIEW:
ImageLoader.getInstance(this).setImageView((String) message.obj, imageView);
break;
}
}
}
运行一下
观察代码,功能的确都是了,但是大家是否发现一个问题,这个多的功能怎么都写在了一个类中,这个类耦合性好高。最简单的,我们可以分为图片下载、图片缓存、图片加载三个功能。
3.1.2 V1.2 优化需求-单一职责优化
可以从图中看出,我们分别把图片下载、图片缓存、图片加载分为了三个功能类,图片加载单例类中,通过持有另外两个类的对象,而分别调用下载、缓存功能。
代码如下:
package com.itbird.design.principle;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;
/**
* 自定义图片加载框架
* Created by itbird on 2022/3/28
*/
public class ImageLoader implements ImageDownload.DownloadCallback {
private static final String TAG = ImageLoader.class.getSimpleName();
private static volatile ImageLoader mInstance = null;
private ImageCache mImageCache;
private ImageDownload mImageDownload;
private ImageLoader(Context context) {
mImageCache = new ImageCache();
mImageDownload = new ImageDownload(context, this);
mImageCache.initLruCache();
}
public static ImageLoader getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(context);
}
}
}
return mInstance;
}
/**
* 给view设置图片
*
* @param url
* @param imageView
*/
public void setImageView(String url, ImageView imageView) {
if (TextUtils.isEmpty(url) || imageView == null) {
return;
}
Bitmap bitmap = mImageCache.getCache(url);
Log.e(TAG, "" + bitmap);
if (bitmap == null) {
mImageDownload.downloadImage(url, imageView);
} else {
imageView.setImageBitmap(bitmap);
}
}
@Override
public void downloadSuccess(String url, Bitmap bitmap) {
if (mImageCache != null) {
mImageCache.putCache(url, bitmap);
}
}
}
3.1.3 V1.3 自定义缓存框架需求-依赖倒置&里氏代换
这时我们在基础需求上,增加一个需求,现在要求,不单单要有内存缓存,还要用硬盘缓存,而且还需要支持外界去设置自己的缓存实现框架
这时重新回来审视我们的代码,是不是感觉一团糟?因为虽然单一职责了,但是需求稍微有点变动,我们的主要功能类代码都需要去修改,因为三个功能类之间,完全是依赖于具体实现的。
这是去思考一个问题,缓存嘛,不过硬盘、内存、网盘,有的共同点是什么?不就是put、get、remove,最多再加一个init,所以我们把这部分缓存的共同操作,抽象为统一的行为接口。ICache.java
package com.itbird.design.principle.v3;
/**
* 缓存功能接口
* Created by itbird on 2022/3/28
*/
public interface ICache<T> {
/**
* 初始化缓存
*/
public void init();
/**
* 获取缓存
*
* @param url
* @return
*/
public T getCache(String url);
/**
* 添加缓存
*
* @param url
* @param bitmap
*/
public void putCache(String url, T bitmap);
/**
* 移除缓存
*
* @param url
*/
void removeCache(String url);
}
相关的实现类为:
package com.itbird.design.principle.v3;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import com.itbird.design.APP;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 硬盘缓存实现
* Created by itbird on 2022/2/28
*/
public class DiskCache implements ICache<Bitmap> {
private String TAG = DiskCache.class.getSimpleName();
private File mRootDir = null;
@Override
public void init() {
mRootDir = APP.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
@Override
public Bitmap getCache(String url) {
return BitmapFactory.decodeFile(mRootDir.getAbsolutePath() + url.substring(url.lastIndexOf("/")) + ".png");
}
@Override
public void putCache(String url, Bitmap bitmap) {
File file = new File(mRootDir.getAbsolutePath() + url.substring(url.lastIndexOf("/")) + ".png");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d(TAG, file.getAbsolutePath());
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void removeCache(String url) {
if (TextUtils.isEmpty(url)) {
return;
}
File file = new File("/sdcard/Pictures/" + url);
if (file == null || !file.exists()) {
return;
}
file.delete();
}
}
package com.itbird.design.principle.v3;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.util.LruCache;
/**
* 内存缓存功能类
* Created by itbird on 2022/3/28
*/
public class MemoryCache implements ICache<Bitmap> {
//这里使用LruCanche算法,做内存存储,原理其实就是基于hashmap,存储key、value
private LruCache<String, Bitmap> mImageCache = null;
/**
* 初始化LruCanche算法
* 需要注意两点,一个是最大内存大小,一个sizeOf每个value的大小
* 必须指定这两个,因为会涉及到LruCache的存儲于回收
*/
@Override
public void init() {
//获取虚拟机最大内存
int memorySize = (int) (Runtime.getRuntime().maxMemory() / 1024);
//以最大内存的四分之一去作为算法缓存
mImageCache = new LruCache<String, Bitmap>(memorySize / 4) {
@Override
protected int sizeOf(String key, Bitmap value) {
//每张图片占用的内存大小
return value.getAllocationByteCount() / 1024;
}
};
}
public Bitmap getCache(String url) {
if (mImageCache != null) {
mImageCache.get(url);
}
return null;
}
public void putCache(String url, Bitmap bitmap) {
if (mImageCache != null) {
mImageCache.put(url, bitmap);
}
}
@Override
public void removeCache(String url) {
if (mImageCache != null && TextUtils.isEmpty(url)) {
mImageCache.remove(url);
}
}
}
ImageLoader修改为
package com.itbird.design.principle.v3;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;
/**
* 自定义图片加载框架
* Created by itbird on 2022/3/28
*/
public class ImageLoader implements ImageDownload.DownloadCallback {
private static final String TAG = ImageLoader.class.getSimpleName();
private static volatile ImageLoader mInstance = null;
private ICache mImageCache;
private ImageDownload mImageDownload;
private ICache<Bitmap> mDefaultCache = new MemoryCache();
private ImageLoader(Context context) {
mImageDownload = new ImageDownload(context, this);
}
public void setImageCache(ICache cache) {
mImageCache = cache;
mImageCache.init();
}
public static ImageLoader getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(context);
}
}
}
return mInstance;
}
/**
* 给view设置图片
*
* @param url
* @param imageView
*/
public void setImageView(String url, ImageView imageView) {
if (TextUtils.isEmpty(url) || imageView == null) {
return;
}
if (mImageCache == null) {
mImageCache = mDefaultCache;
mImageCache.init();
}
Bitmap bitmap = (Bitmap) mImageCache.getCache(url);
Log.e(TAG, "" + bitmap);
if (bitmap == null) {
mImageDownload.downloadImage(url, imageView);
} else {
imageView.setImageBitmap(bitmap);
}
}
@Override
public void downloadSuccess(String url, Bitmap bitmap) {
if (mImageCache != null) {
mImageCache.putCache(url, bitmap);
}
}
}
具体的修改后的类图为:
外界调用时:
//V3版本
ImageLoader.getInstance(this).setImageCache(new DiskCache());
ImageLoader.getInstance(this).setImageView((String) message.obj, imageView);
从类图看,是不是很清晰呢?
小结
1)不单单实现了新需求功能,可以支持外界去自定义实现自己的缓存框架,而且ImageLoader与ImageCache之间完全是依赖于接口编程的。
2)依赖倒置:依赖于接口编程,从类图中就可以看到了,依赖于接口编程的好处,从这里也可以看到,外界想要替换自己的缓存实现类,完全不用修改我们已有类。
3)里氏代换:所有基类出现的地方,都可以用子类来替代。ImageLoader中,把外界设置的具体的实现类(缓存基类的子类),替换为rivate ICache mImageCache;
对象,从而在封装层面,只面向接口编程。
3.1.4 V1.4 接口隔离原则的应用
接口隔离原则将非常庞大、臃肿的接口拆分成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署,让客户端依赖的接口尽可能地小。
我们在上面硬盘缓存、文件读写、数据库读写的时候,经常会写这样的代码。
finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
大家平常开发中,是否有过这样的想法?文件读写、数据库读写本来就是在代码编写中经常遇到的操作,经常在类中写这样的代码,一方面忘记了,那就会引入灾难性的后果,另外一方面,代码不美观而且类的可读性,发现少了。这段代码可能我们平时都这么写,各种try…catch嵌套,都是些简单的代码,但是一旦多了,那基于上面两方面的原因,问题就多了。
我们查看源码,发现这些流操作了,实际上都是实现了Closeable接口。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package java.io;
public interface Closeable extends AutoCloseable {
void close() throws IOException;
}
所以基于接口隔离原则,我们可以把这部分,封装如下:
package com.itbird.utils;
import java.io.Closeable;
import java.io.IOException;
public class CloseUtils {
/**
* 关闭 Closeable
* @param closeable
*/
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码简洁了很多!保证了代码的重用性。close 方法的基本原理就是依赖于 Closeable 抽象而不是具体实现(这其实也是依赖倒置),并且建立在最小化依赖原则的基础,它只需要知道这个对象是可关闭,其他的一概不关心,也就是这里的接口隔离原则。
Bob大叔(Robert C Martin)在21世纪早期将单一职责、开闭原则、里氏替换、接口隔离以及依赖倒置(也称为依赖反转)5个原则定义为SOLID原则,指代了面向对象编程的5个基本原则。当这些原则被一起应用时,它们使得一个软件系统更清晰、简单、最大程度地拥抱变化。
3.2 业务、数据、界面分离封装
在刚刚接触android的时候,或者说,现在依然有很大一部分的APP开发者,在开发过程中,总是习惯在一个Activity、Fragment中几乎完成了所有的功能。例如网络请求、数据加载、业务逻辑处理、界面加载、界面动画。
后来渐渐的我们接触到了MVC、MVP各种官方的框架,懂得了模块的分离、解耦(MVC),懂得了通过依赖于抽象去分离各个模块彻底解耦(MVP)。
但是官方的MVP框架的确已经帮我们做了很多,但是依然不够,接下来,我们基于官方给的MVP框架,结合六大基本原则,去封装更加适宜于项目的MVP框架。
整体设计模式Demo代码
3.2.1 V1.0 Google官方的MVP
官方Demo怎么去做的?
我们为了方便去分析,我这里简化代码,我们逐步分析,BaseView 和 BasePresenter,BaseView谷歌是这么写的(其实就是view的接口,展示view),以下样例为了理解,我简化处理了部分代码
BaseView.java
package com.itbird.design.principle.mvp.google;
/**
* Google Demo
* Created by itbird on 2022/2/25
*/
public interface BaseView<T> {
//View中,设置presenter对象,使View可以持有Presenter对象引用
void setPresenter(T presenter);
}
BasePresenter
package com.itbird.design.principle.mvp.google;
/**
* Google Demo
* Created by itbird on 2022/2/25
*/
public interface BasePresenter {
}
TaskDetailContract
package com.itbird.design.principle.mvp.google;
/**
* Google Demo
* Created by itbird on 2022/2/25
*/
public interface TaskDetailContract {
interface View extends BaseView<Presenter> {
//界面UI刷新方法
void updateTextView(String s);
}
interface Presenter extends BasePresenter {
void loadDataFromModel();
}
}
TaskGoogleActivity
package com.itbird.design.principle.mvp.google;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.itbird.design.R;
public class TaskGoogleActivity extends AppCompatActivity implements TaskDetailContract.View {
private static final String TAG = TaskGoogleActivity.class.getSimpleName();
private TaskDetailContract.Presenter mPresenter;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.textview);
Log.e(TAG, TAG + " onCreate");
new TaskGooglePresenter(this);
}
@Override
public void setPresenter(TaskDetailContract.Presenter presenter) {
mPresenter = presenter;
}
@Override
public void updateTextView(String s) {
mTextView.setText(s);
}
}
TaskGooglePresenter
package com.itbird.design.principle.mvp.google;
public class TaskGooglePresenter implements TaskDetailContract.Presenter {
private static final String TAG = TaskGooglePresenter.class.getSimpleName();
TaskDetailContract.View mView;
public TaskGooglePresenter(TaskDetailContract.View view) {
mView = view;
mView.setPresenter(this);
}
@Override
public void loadDataFromModel() {
//TODO :loaddata,此处可以用model、或者进行业务操作
//调用界面方法,进行数据刷新
mView.updateTextView("loaddata success!!!");
}
}
小结
我们单从设计来说, Google MVP Demo的确向我们展示了MVP的优点:
1)业务、数据、视图分离、解耦,从六大基本原则上来说,开闭、单一职责、里氏代换、依赖倒置、接口隔离、最少知道,基本都做到了。
2)由于完全分离,所以我们很方便针对于每层去选取对应的单元测试模式,例如针对于数据层可以使用(Junit+Mockito)、视图层可以使用(AndroidJunitRunner+Espresso)、业务层可以使用(Junit+Mockito)
3)通过Contract 契约类,完全将视图、业务相关的接口,封装放在了一个地方,简单明了,针对于开发者来说,不容易忘记和辅助养成习惯但是,但是对于我们实际开发使用来说,依然有以下几点问题:
1)setPresenter接口完全没有必要存在,因为Presenter对象一定是在View类中new出来的,我既然都有它自己的对象了,我干嘛还要在Presenter内部,去调用mView.setPresenter(this);,再返回View中,再去保存一个引用,代码看着很怪
2)我们知道MVP的精髓在与,P、V肯定要相互交互,他们互相要持有对方的引用,通过上面一点,我们知道Presenter对象一定是在View类中new出来的,所以View肯定有Presenter对象的引用,这个没问题了。但是Presenter要想拥有View的引用,只能通过Presenter构造方法、或者Presenter内部有一个setView方法,让开发者自己去调用setView或者实现带参构造方法,从设计角度来说,这明显是一个坑,因为一个框架的设计,是为了更加方便开发,而不是让开发人员设置这个、设置那个、必须调用某个方法。既然是必须调用的方法,我们应该通过框架去内部消化掉,而不是给开发者制造麻烦
。
3)Presenter中拥有View的引用,如果activity、fragment销毁,但是presenter依然在执行某些任务,这样会导致activity、fragment无法GC回收,导致内存泄露,甚至与崩溃,所以这也是一个框架必须解决的问题。
3.2.2 V1.1 My MVP V1
基于上面提出的三点,我们去优化Google的MVP框架。
我们首先将第一点和第二点通过抽象来解决一下。IPresenter
package com.itbird.design.principle.mvp.v1;
/**
* 自定义MVP框架,BasePresenter
* Created by itbird on 2022/2/25
*/
public interface IPresenter {
/**
* 与view班定
*
* @param view
*/
void onAttach(IView view);
/**
* 与view解绑
*/
void onDetach();
/**
* 是否与view已经班定成功
*
* @return
*/
boolean isViewAttached();
/**
* 获取view
* @return
*/
IView getView();
}
IView
package com.itbird.design.principle.mvp.v1;
/**
* 自定义MVP框架,BaseView
* Created by itbird on 2022/2/25
*/
public interface IView {
}
接下来是借助activity生命周期,对presenter的初始化进行封装BaseActivity
package com.itbird.design.principle.mvp.v1;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
/**
* Created by itbird on 2022/3/29
*/
public abstract class BaseActivity extends Activity implements IView {
IPresenter mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = createPresenter();
if (mPresenter != null) {
mPresenter.onAttach(this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDetach();
mPresenter = null;
}
}
abstract IPresenter createPresenter();
}
BasePresenter
package com.itbird.design.principle.mvp.v1;
import java.lang.ref.WeakReference;
/**
* Created by itbird on 2022/3/29
*/
public class BasePresenter<V extends IView> implements IPresenter {
WeakReference<V> mIView;
@Override
public void onAttach(IView iView) {
mIView = new WeakReference<>((V) iView);
}
@Override
public void onDetach() {
mIView = null;
}
@Override
public V getView() {
if (mIView != null) {
mIView.get();
}
return null;
}
@Override
public boolean isViewAttached() {
return mIView != null && mIView.get() != null;
}
}
3.2.3 V1.2 My MVP V2
看上图类图,我们依然发现有一些不满足的点:
1)activity中,依然需要初始化mPresenter
@Override
IPresenter createPresenter() {
mTaskPresenter = new TaskMyPresenter();
return mTaskPresenter;
}
是否可以做到在activity中,自己像presenter中调用view一样,自己一句话getView就可以搞定
2)观察类图,其实IView、IPresenter没有必要存在,因为毕竟只是baseActivity、basePresenter的行为
所以改造如下:BasePresenter
package com.itbird.design.principle.mvp.v2;
import java.lang.ref.WeakReference;
/**
* Created by itbird on 2022/3/29
*/
public abstract class BasePresenter<V> {
WeakReference<V> mIView;
public void onAttach(V iView) {
mIView = new WeakReference<>(iView);
}
public void onDetach() {
mIView = null;
}
public V getView() {
if (mIView != null) {
mIView.get();
}
return null;
}
public boolean isViewAttached() {
return mIView != null && mIView.get() != null;
}
}
BaseActivity
package com.itbird.design.principle.mvp.v2;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
/**
* Created by itbird on 2022/3/29
*/
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends Activity {
T mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = initPresenter();
if (mPresenter != null) {
mPresenter.onAttach((V) this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.onDetach();
mPresenter = null;
}
}
public T getPresenter() {
return mPresenter;
}
abstract T initPresenter();
}
此时,契约类,不再需要依赖BasePresenter/BaseView相关接口,而且View中也可以自己获取到presenter的引用了。
package com.itbird.design.principle.mvp.v2;
/**
* my Demo
* Created by itbird on 2022/2/25
*/
public interface TaskMyContract {
interface View {
//界面UI刷新方法
void updateTextView(String s);
}
interface Presenter {
void loadDataFromModel();
}
}
package com.itbird.design.principle.mvp.v2;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.itbird.design.R;
public class TaskMyActivity extends BaseActivity<TaskMyContract.View, TaskMyPresenter> implements TaskMyContract.View {
private static final String TAG = TaskMyActivity.class.getSimpleName();
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.textview);
Log.e(TAG, TAG + " onCreate");
mPresenter.loadDataFromModel();
}
@Override
TaskMyPresenter initPresenter() {
return new TaskMyPresenter();
}
@Override
public void updateTextView(String s) {
mTextView.setText(s);
}
}
此时的类图,是否更加明确,而且上面各个问题都已经得到了解决。