LCODER设计模式一:创建型模式
本文目录:
设计模式分为创建型模式、结构型模式和行为模式。 创建型模式: 提供了创建对象的机制。是这一篇博客将要介绍的内容。 结构型模式: 介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。 行为模式: 负责对象间的高效沟通和职责委派。
为什么要学习设计模式呢? 因为设计模式是针对软件设计中常见问题的工具箱,其中的工具就是各种警告实践验证的解决方案。熟悉了设计模式,会让你的代码更加的健壮。
面向对象的六大原则
单一职责原则 : (Single Responsibility Principle SRP) 就一个类而言,应该仅有一个引起它变化的原因。 开闭原则 :(Open Close Principle OCP)软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。 里式替换原则 :(Liskov Substitution Principle LSP)子类可以扩展父类的功能,但不能改变父类的功能。 依赖倒置原则:(Dependence Inversion Principle DIP)面向接口编程。模块间的依赖通过抽象发送,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。 接口隔离原则:(InterfaceSegregation Principles ISP)类间的依赖关系应该建立在最小的接口上。 迪米特原则:(Law of Demeter LOD)一个对象应该对其他对象有最少的了解。
通过一个ImageLoader的例子来看其中的单一职责原则、开闭原则、依赖倒置原则
该ImageLoader的需求是可以实现图片的加载和缓存。 第一版代码:一个类走天下。
/**
* 第一版代码: 一个类走天下(使用一个类实现所有的功能)
*/
public class ImageLoader {
// 图片缓存
LruCache<String, Bitmap> mImageCache;
// 线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Handler mUiHandler = new Handler(Looper.myLooper());
public ImageLoader(){
initImageCache();
}
private void initImageCache() {
// 计算运行时可使用的最大内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用内存作为缓存
int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() /1024;
}
};
}
public void displayImage(String url,ImageView imageView){
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downLoadImage(url);
if(bitmap == null){
return;
}
if(imageView.getTag().equals(url)){
updateImageView(imageView,bitmap);
}
mImageCache.put(url,bitmap);
}
});
}
private void updateImageView(final ImageView imageView,final Bitmap bitmap){
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
public Bitmap downLoadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
第一版的代码,像不像我们刚开始写代码的时候会写出来的代码?耦合严重,所有的功能在同一个类里面实现,随着日后功能增多,ImageLoader类会越来越大,代码也越来越复杂,图片加载系统越来越脆弱,改一个功能可能会牵一发动全身。如果遵循单一职责原则,把ImageLoader拆分一下,把各个功能独立出来,各个功能都能独立修改代码,不会互相产生影响。
说干就干,把ImageLoader一拆为二,ImageLoader只负责图片的加载工作,创建一个ImageCache负责图片缓存的逻辑。 如下图所示:
ImageLoader代码修改如下:
/**
* 第二版代码:
* 1.新建一个ImageCache,专门用来处理缓存的逻辑
* 2.修改ImageLoader的代码,ImageLoader只负责图片显示的逻辑
*/
public class ImageLoader {
ImageCache mImageCache = new ImageCache();
// 线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Handler mUiHandler = new Handler(Looper.myLooper());
public ImageLoader(){
}
public void displayImage(String url,ImageView imageView){
Bitmap bitmap = mImageCache.get(url);
if(bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downLoadImage(url);
if(bitmap == null){
return;
}
if(imageView.getTag().equals(url)){
updateImageView(imageView,bitmap);
mImageCache.put(url,bitmap);
}
}
});
}
private void updateImageView(final ImageView imageView,final Bitmap bitmap){
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
public Bitmap downLoadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
同时创建一个ImageCache,专门处理图片缓存的逻辑。
public class ImageCache {
// 图片缓存
LruCache<String, Bitmap> mImageCache;
public ImageCache(){
initImageCache();
}
private void initImageCache() {
// 计算运行时可使用的最大内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用内存作为缓存
int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() /1024;
}
};
}
public void put(String url,Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public void get(String url,Bitmap bitmap){
mImageCache.get(url);
}
}
上面解决了ImageLoader耦合严重的问题,但是Android系统应用的内存有限,不可能把图片只存在缓存中,还需要把图片存在SD卡中,随着业务逻辑的优化,除了MemoryCache,还需要一个DiskCache。
public class DiskCache {
static String cacheDir = "sdcard/cache/";
// 从sd卡中获取图片
public Bitmap get(String url){
return BitmapFactory.decodeFile(cacheDir + url);
}
public void put(String url, Bitmap bmp) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.closeIO(fileOutputStream);
}
}
}
现在项目中有了DiskCache和MemoryCache双缓存,用户就可以先从内存中获取缓存,如果没有再去SD卡中获取缓存,SD卡没有再去网络上下载图片,大大提升了性能,要实现这个需求,就需要一个DoubleCache。这里为了增加代码可读性,把ImageCache更名为MemoryCache。
public class DoubleCache {
MemoryCache mMemoryCache = new MemoryCache();
DiskCache mDiskCache = new DiskCache();
// 先从内存缓存中获取图片,如果没有,再从SD卡中获取
public Bitmap get(String url){
Bitmap bitmap = mMemoryCache.get(url);
if(bitmap == null){
bitmap = mDiskCache.get(url);
}
return bitmap;
}
public void put(String url,Bitmap bitmap){
mMemoryCache.put(url,bitmap);
mDiskCache.put(url, bitmap);
}
}
因为需要将图片缓存到SD卡中,ImageLoader的代码也要变:增加
/**
* 第三版代码:
* 1.新建一个ImageCache,专门用来处理缓存的逻辑
* 新增一个DiskCache,添加SD卡缓存
* 新增双缓存管理类 Double
* 2.修改ImageLoader的代码,ImageLoader只负责图片显示的逻辑
*
*/
public class ImageLoader {
MemoryCache mImageCache = new MemoryCache();
DiskCache mDiskCache = new DiskCache();
DoubleCache mDoubleCache = new DoubleCache();
boolean isUseDoubleCache = false;
boolean isUseDiskCache = false;
// 线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Handler mUiHandler = new Handler(Looper.myLooper());
public ImageLoader(){
}
public void displayImage(String url,ImageView imageView){
// 判断使用那种缓存
Bitmap bitmap = null;
if(isUseDoubleCache){
bitmap = mDoubleCache.get(url);
}else if(isUseDiskCache){
bitmap = mDiskCache.get(url);
}else{
bitmap = mImageCache.get(url);
}
if(bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downLoadImage(url);
if(bitmap == null){
return;
}
if(imageView.getTag().equals(url)){
updateImageView(imageView,bitmap);
mImageCache.put(url,bitmap);
}
}
});
}
private void updateImageView(final ImageView imageView,final Bitmap bitmap){
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
public Bitmap downLoadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
public void setUseDiskCache(boolean useDiskCache){
this.isUseDiskCache = useDiskCache;
}
public void setUseDoubleCache(boolean useDoubleCache){
this.isUseDoubleCache = useDoubleCache;
}
}
上面的代码确实解决了双缓存的问题,但是,现在问题又来了,就是用户每增加一种缓存的方式,就需要去修改ImageLoader的代码,这显然是不合理的。 如果此时我们遵循开闭原则,当软件需要变化时,尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现,那么代码的可扩展性就会很强。
如下图所示:
定义一个接口ImageCache,这个接口中定义get和put方法,MemoryCache、DiskCache、DoubleCache都实现这个接口。
public interface ImageCache {
public void put(String url, Bitmap bitmap);
public Bitmap get(String url);
}
public class MemoryCache implements ImageCache{
// 图片缓存
LruCache<String, Bitmap> mImageCache;
public MemoryCache(){
initMemoryCache();
}
private void initMemoryCache() {
// 计算运行时可使用的最大内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用内存作为缓存
int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() /1024;
}
};
}
public void put(String url,Bitmap bitmap){
mImageCache.put(url,bitmap);
}
public Bitmap get(String url){
return mImageCache.get(url);
}
}
public class DiskCache implements ImageCache{
static String cacheDir = "sdcard/cache/";
// 从sd卡中获取图片
public Bitmap get(String url){
return BitmapFactory.decodeFile(cacheDir + url);
}
public void put(String url, Bitmap bmp) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseUtils.closeIO(fileOutputStream);
}
}
}
public class DoubleCache implements ImageCache{
MemoryCache mMemoryCache = new MemoryCache();
DiskCache mDiskCache = new DiskCache();
// 先从内存缓存中获取图片,如果没有,再从SD卡中获取
public Bitmap get(String url){
Bitmap bitmap = mMemoryCache.get(url);
if(bitmap == null){
bitmap = mDiskCache.get(url);
}
return bitmap;
}
public void put(String url,Bitmap bitmap){
mMemoryCache.put(url,bitmap);
mDiskCache.put(url, bitmap);
}
}
在ImageLoader中添加一个setImageCache(ImageCache)方法,使用哪种缓存方式就传入哪种缓存方式。
/**
* 第四版代码:
* 1.创建一个接口ImageCache MemoryCache、DiskCache、DoubleCache都实现这个接口。
* 2.ImageLoader里面添加一个设置ImageCache的方法,使用哪种缓存方式就传入哪种缓存方式。
*/
public class ImageLoader {
ImageCache mCache = new MemoryCache();
// 线程池
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Handler mUiHandler = new Handler(Looper.myLooper());
public ImageLoader(){
}
public void displayImage(String url,ImageView imageView){
Bitmap bmp = mCache.get(url);
if (bmp == null) {
downloadImageAsync(url, imageView);
} else {
imageView.setImageBitmap(bmp);
}
}
private void downloadImageAsync(String url, ImageView imageView) {
Bitmap bmp = downLoadImage(url);
mCache.put(url, bmp);
imageView.setImageBitmap(bmp);
}
private void updateImageView(final ImageView imageView,final Bitmap bitmap){
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
public Bitmap downLoadImage(String imageUrl){
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(urlConnection.getInputStream());
urlConnection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 设置缓存策略
*
* @param cache 缓存
*/
public void setImageCache(ImageCache cache) {
mCache = cache;
}
}
通过上面的改造,现在用户就算想要添加一种缓存策略,也不需要再修改ImageLoader,只需要增加一种缓存方式,并实现ImageCache即可。这就遵循了开闭原则:对扩展开放,对修改关闭。 另外,在上面的ImageLoader中,我们创建了一个缓存类ImageCache。这个地方遵循了依赖倒置原则,图片缓存类,依赖于抽象,并且有一个默认的实现。
ImageCache mCache = new MemoryCache();
通过这个例子,我们初步的了解了面向对象的三大原则,初尝了设计模式的好处,代码也在几次的修改之后,变得更加的健壮。
下面就开始设计模式之旅吧。
创建型模式
1.工厂方法模式
工厂方法模式,是Java中最常用的设计模式之一。具体的定义就是:定义一个用于创建对象的接口,让子类决定实例化哪个工厂类。 实现: 我们创建一个Furit接口和实现Furit接口的实体类。创建一个水果工厂类FuritFactory。如下图所示:
创建一个接口Fruit。创建Apple和Banana实现这个接口。
public interface Fruit {
void print();
}
public class Apple implements Fruit{
@Override
public void print() {
System.out.print("Apple");
}
}
public class Banana implements Fruit{
@Override
public void print() {
System.out.printf("Banana");
}
}
创建一个工厂,生成水果。
public class FruitFactory {
public Fruit getFruit(String type){
if(type == null){
return null;
}
if(type.equalsIgnoreCase("APPLE")){
return new Apple();
} else if(type.equalsIgnoreCase("BANANA")){
return new Banana();
}
return null;
}
}
需要哪种水果,就传入哪种水果的类型即可,这种方法比较简洁,像这样的方式又称为简单工厂模式或者是静态工厂模式,它是工厂模式的一个弱化版本。 总得来说,工厂方法模式是一个很好的设计模式,但其缺点也是显而易见的,每次添加新产品时,就要编写一个新的产品类。这必然会导致类的结构复杂化,所以,在某些情况比较简单是,是否使用工厂模式,就需要权衡利弊了。
2. 抽象工厂模式
抽象工厂模式,是围绕一个超级工厂创建其他工厂。它的定义是:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类。
实现:创建一个超级水果工厂,这个工厂既可以生成苹果也可以生成香蕉,再分别创建一个苹果工厂和香蕉工厂,继承自这个超级水果工厂,苹果工厂只生产苹果,香蕉工厂只生产香蕉。UML图如下图所示:
创建抽象的苹果类,这个类有红苹果和青苹果两个子类。 创建抽象的香蕉类,这个类有黄香蕉和青香蕉两个子类。
public abstract class AbstractApple {
public abstract void print();
}
public class RedApple extends AbstractApple{
@Override
public void print() {
System.out.printf("RedApple");
}
}
public class GreenApple extends AbstractApple{
@Override
public void print() {
System.out.printf("GreenApple");
}
}
public abstract class AbstractBanana {
public abstract void print();
}
public class YellowBanana extends AbstractBanana{
@Override
public void print() {
System.out.printf("YellowBanana");
}
}
public class GreenBanana extends AbstractBanana{
@Override
public void print() {
System.out.printf("GreenBanana");
}
}
创建一个超级水果工厂类,这个类有两个子类,苹果工厂类和香蕉工厂类
public abstract class AbstractFruitFactory {
public abstract Apple getApple();
public abstract Banana getBanana();
}
生产苹果的工厂只实现生产苹果的方法。
public class AppleFactory extends AbstractFruitFactory{
@Override
public Apple getApple() {
return new Apple();
}
@Override
public Banana getBanana() {
return null;
}
}
生产香蕉的工厂只实现生产香蕉的方法。
public class BananaFactory extends AbstractFruitFactory{
@Override
public Apple getApple() {
return null;
}
@Override
public Banana getBanana() {
return new Banana();
}
}
以上,就实现了一个抽象工厂模式。 抽象工厂方法模式的优点:分离了接口与实现,而缺点也非常的显而易见,就是类文件的爆炸性增加,不太容易扩展新的产品类,每增加一个产品类就需要修改抽象工厂,所有的具体实现都会被修改。
3. 单例模式
单例模式,是Java设计模式中最简单的设计模式之一。它的定义是:确保一个类只有一个实例,而且自行实例化并向整个系统提供整个实例。 实现:UML图如下所示:
单例模式的几种实现:
1、饿汉式
这种方式比较常用,但容易产生垃圾对象。这种方式就是在类加载时就创建实例,浪费内存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
2、懒汉式
这种方式就是在使用时才创建实例。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种实现最大的问题是线程不安全,没有加锁synchronized。严格意义上,它并不能算单例模式。
3、懒汉式(线程安全)
这种方式是线程安全的,能在多线程中很好的工作,保证在多线程中只会产生一个实例,但是,效率很低,因为客户端99%不需要同步。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4、DCL(Double Check Lock)双重校验锁
这种方式既能保证线程安全,且当有两个线程同时调用new Singleton()时,如果第一个线程已经创建了Singleton实例,第二个线程等待第一个线程执行完成后进入第二重锁中发现singleton不为空,直接返回singleton,节省资源。 其中使用volatile关键字,是为了确保每次获取singleton对象都是从主内存中获取,避免DCL失效的问题。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
5、静态内部类单例模式。
这种方式是最推荐使用的单例模式的实现方式。 这种方式的原理是:当调用getInstance()时,会调用SingletonHolder.INSTANCE,此时SingletonHolder才会初始化,这是因为当类初始化时,其静态域也会初始化,由于是静态的,JVM只会加载一次,JVM保证了线程安全。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6、枚举单例
这种方式最简洁,同时因为默认枚举实例的创建是线程安全的,并且在任何情况下,枚举实例都是一个单例,这种方式其实是最好的单例的实现方式。
public enum Singleton {
INSTANCE;
public void dosth() {
System.out.printf("do sth");
}
}
4. 建造者模式 (Builder模式)
使用多个简单的对象一步一步构建成一个复杂的对象。这种设计模式,可以讲一个复杂的构建与其表示相分离,使得通用的构建过程可以创建不同的表示。
实现:UML图如下所示:
一个简单的组装电脑的过程,首先创建一个Computer类(Product)
public abstract class Computer {
protected String mBoard; // 主板
protected String mDisplay; // 显示器
protected String mOS; // 操作系统
protected Computer(){
}
// 设置主板
public void setBoard(String board) {
this.mBoard = board;
}
// 设置显示器
public void setDisplay(String display) {
this.mDisplay = display;
}
// 设置操作系统
public void setOS();
@Override
public String toString() {
return "Computer{" +
"mBoard='" + mBoard + ''' +
", mDisplay='" + mDisplay + ''' +
", mOS='" + mOS + ''' +
'}';
}
}
一个具体的电脑:MacBook继承自Computer
public class MacBook extends Computer{
protected MacBook(){
}
@Override
public void setOS() {
mOS = "MAC OS";
}
}
创建Builder类
public abstract class Builder {
// 设置主板
public abstract void buildBoard(String board);
// 设置显示器
public abstract void buildDisplay(String display);
// 设置操作系统
public abstract void buildOS();
// 创建Computer
public abstract Computer createComputer();
}
具体的Builder类,MacBookBuilder
public class MacBookBuilder extends Builder{
private Computer mComputer = new MacBook();
@Override
public void buildBoard(String board) {
mComputer.setBoard(board);
}
@Override
public void buildDisplay(String display) {
mComputer.setDisplay(display);
}
@Override
public void buildOS() {
mComputer.setOS();
}
@Override
public Computer createComputer() {
return mComputer;
}
}
Director类,负责构造Computer
public class Director {
Builder mBuilder = null;
public Director(Builder builder){
mBuilder = builder;
}
public void construct(String board,String display){
mBuilder.buildBoard(board);
mBuilder.buildDisplay(display);
mBuilder.buildOS();
}
}
使用建造者模式,创建MacBook。
public static void main(String[] args) {
Builder builder = new MacBookBuilder();
Director director = new Director(builder);
director.construct("英特尔","惠普显示器");
}
在上面的例子中,通过具体的MacBookBuilder来构建了MacBook对象,而Director封装了构建复杂产品对象的过程,对外隐藏构建细节。Builder与Director以前讲一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的对象。
转载自:https://juejin.cn/post/7130916808055324686