> 文章列表 > 设计模式系列

设计模式系列

设计模式系列

文章目录

  • 一,单例模式
    • 1.1 懒汉模式(不建议采用)
    • 1.2 双检查模式(大部分场景可用)
    • 1.3 静态内部类模式(最佳实现)
    • 1.4 枚举单例
    • 1.5 容器模式
  • 二,工厂模式
    • 2.1 定义
    • 2.2 场景1
    • 2.3 场景2
    • 2.4 总结
  • 三,代理模式
    • 3.1 代理模式的作用
    • 3.2 代理模式可以分为两种
    • 3.3 代理模式实战
  • 四,状态模式
    • 4.1 基本概念
    • 使用场景
    • 4.2 示例

一,单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这样可以避免产生多个对象消耗过多的资源。

单例模式主要有几个关键点:

  • 构造函数不对外开放,一般为private
  • 通过一个静态方法或者枚举返回单例类对象
  • 确保单例类的对象有且只有一个,尤其是在多线程环境下
  • 确保单例类对象在反序列化时不会重新构建对象

1.1 懒汉模式(不建议采用)

声明一个静态对象,第一次调用getInstance时进行初始化。

public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}

可以看出getInstance()方法添加了synchronized 关键字,多线程下保证同步。但是问题是每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源,因此不建议用这种模式。

1.2 双检查模式(大部分场景可用)

public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}

可以看出双检查模式下既能够在需要的时候初始化,又能保证线程安全,而且同步锁只执行一次。

1.3 静态内部类模式(最佳实现)

    public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonHolder.instance;}private static class SingletonHolder {private static final Singleton instance = new Singleton();}}

双检查模式虽然在一定程度上解决资源消耗,多余同步,线程安全等问题,但是在一些情况下依然会出现失效。
静态内部模式可以有效解决这些问题,在第一次调用getInstance()方法虚拟机加载SingletonHolder,保证了线程安全和实例唯一。

1.4 枚举单例

    public enum  Singleton {INSTANCE;public void test(){System.out.print("test");}}

枚举单例是最简单,也是最有效的。枚举实例的创建是线程安全的,并且本身就是单例的。还有枚举即使在反序列化的情况下也是单例的。

1.5 容器模式

    public class Singleton {private static Map<String, Object> objectMap = new HashMap<>(0);private Singleton() {}public static void addInstance(String key, Object instance) {if (!objectMap.containsKey(key)) {objectMap.put(key, instance);}}public static Object getService(String key) {return objectMap.get(key);}}

这种方式也能实现单例,把实例类保存在集合中,也能保证唯一性,并且可以管理多种类。

在项目开发中如何选择单例,这得看具体的需求,和自身的开发环境等问题。

二,工厂模式

2.1 定义

工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

2.2 场景1

现在有一个需求,图书分类。图书馆有很多书,我们要对书进行分类,有历史类,科学类等。

首先我们要创建图书的抽象类

public abstract class Book {/*** 抽象方法*/public abstract void method();}

具体的分类实现

    //历史类public class HistoryBook extends Book {@Overridepublic void method() {System.out.print("我是历史类");}}//科学类public class ScienceBook extends Book {@Overridepublic void method() {System.out.print("我是科学类");}}

抽象工厂类

    public abstract class Factory {/*** 抽象工厂方法* @return*/public abstract Book createBook();}

具体的工厂实现

    public class ConcreteFactory extends Factory {@Overridepublic Book createBook() {return new HistoryBook();//历史类实现//return new ScienceBook();//科技类实现}}

调用工厂实现图书分类

      Factory factory = new ConcreteFactory();Book book = factory.createBook();book.method();

到这里整个实现都完成了,代码很简单。主要就是四大模块,第一是抽象图书类,第二是具体的实现图书类,第三是抽象工厂类,第四是具体的实现工厂类。如果我们想实现哪个分类就在ConcreteFactory中实现即可,但是这样不同的实现都需要改ConcreteFactory 中的代码,因此我们对上面实现方式进行改造。

改造抽象工厂类

public abstract class Factory {/*** 抽象工厂方法* @return*/public abstract <T extends Book> T createBook(Class<T> cla);}

改造抽象工厂实现类

    public class ConcreteFactory extends Factory {/*** 抽象工厂具体实现* @param cla* @param <T>* @return*/@Overridepublic <T extends Book> T createBook(Class<T> cla) {Book book=null;try {book= (Book) Class.forName(cla.getName()).newInstance();}catch (Exception e){e.printStackTrace();}return (T)book;}}

程序中调用,需要哪个分类就传入那个分类的类型

      Factory factory = new ConcreteFactory();Book book = factory.createBook(HistoryBook.class);//book = factory.createBook(ScienceBook.class);book.method();

2.3 场景2

我们知道在Android中数据存储有五种方式:SharedPreferences,SQLite,文件,ContentProvider,网络,像前三中存储方式比较类似,我们可以把他们抽象化为统一的接口,进行统一的调用和管理。

抽象接口,提供统一的数据操作接口。

public abstract class ICallBackStorage<T> {/*** 添加数据* @param id* @param t*/public abstract void add(String id,T t);/*** 删除数据* @param t*/public abstract void remove(T t);/*** 更新数据* @param id* @param t*/public abstract void update(String id,T t);/*** 查询数据* @param id*/public abstract Object query(String id);}

文件存储实现

    public class FileStorage extends ICallBackStorage<File> {@Overridepublic void add(String id,File file) {//添加文件数据}@Overridepublic void remove(File file) {//删除文件数据}@Overridepublic void update(String id,File file) {//更新文件数据}@Overridepublic Object query(String id) {//查询文件数据return null;}}

SharedPreferences存储实现

    public class SharedStorage extends ICallBackStorage<String> {@Overridepublic void add(String id, String s) {//添加SharedPreferences数据}@Overridepublic void remove(String s) {//删除SharedPreferences数据}@Overridepublic void update(String id, String s) {//更新SharedPreferences数据}@Overridepublic Object query(String id) {//查询SharedPreferences数据return null;}}

数据库存储实现

    public class SqlStorage extends ICallBackStorage<Object> {@Overridepublic void add(String id, Object o) {//添加数据}@Overridepublic void remove(Object o) {//删除数据}@Overridepublic void update(String id, Object o) {//更新数据}@Overridepublic Object query(String id) {//查询数据return null;}}

抽象工厂类

    public abstract class Factory {/*** 抽象工厂方法* @return*/public abstract <T extends ICallBackStorage> T createBook(Class<T> cla);}

抽象工厂实现类

    public class ConcreteFactory extends Factory {@Overridepublic <T extends ICallBackStorage> T createBook(Class<T> cla) {ICallBackStorage<T> io=null;try {io= (ICallBackStorage<T>) Class.forName(cla.getName()).newInstance();}catch (Exception e){e.printStackTrace();}return (T)io;}}

调用方式

      Factory factory = new ConcreteFactory();ICallBackStorage io = factory.createBook(SqlStorage.class);io.add("1", "第一条数据");

2.4 总结

工厂模式的优点:

  • 分离接口与实现,隔离了具体类的生产,使得客户端并不需要知道什么被创建。
  • 当一个产品中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
  • 增加新的产品类时更加的灵活,易用。符合 “开闭原则”。

工厂模式的缺点:

  • 类文件爆炸性增加,扩展新的产品类方法不太容易,每当增加一个产品类方法所有的具体实现类都需要修改。

三,代理模式

基本概念:代理模式也成为委托模式,目标对象为其对象提供一种代理对象,其他对象通过代理对象来控制对目标对象的访问。

3.1 代理模式的作用

代理模式就相当于中介,生活中我们要找房子,法律诉讼,代购都可以理解为代理模式。在程序中,有些模块我们是无法访问的,因为有些模块不能对外公开。但是我们又想用模块中的一些功能,这时代理就产生了,我们可以写个代理类帮我们实现。在我们日常开发中,如果我们要重构代码,当我们要更改目标对象时,目标对象被很多地方引用,因此修改成本太高。此时使用代理模式,通过对代理对象的修改,而完成最终的业务需求。

实现生活中的代理模式,以法律诉讼为例,如果要打官司肯定需要找个律师为自己的诉讼代理人,李明因为老板欠薪要打官司。

诉讼接口类:

    public interface ILawsuit {void submit();//提交申请void burden();//进行举证void defend();//开始辩护void finish();//完成诉讼}

具体诉讼人

    public class LiMing implements ILawsuit {@Overridepublic void submit() {System.out.println("老板欠工资,申请仲裁");}@Overridepublic void burden() {System.out.println("银行流水,证明欠薪");}@Overridepublic void defend() {System.out.println("证据确凿");}@Overridepublic void finish() {System.out.println("诉讼成功");}}

代理律师

 public class Lawyer implements ILawsuit {private ILawsuit mLawsuit;//持有一个具体被代理的引用public Lawyer(ILawsuit lawsuit) {mLawsuit = lawsuit;}@Overridepublic void submit() {mLawsuit.submit();}@Overridepublic void burden() {mLawsuit.burden();}@Overridepublic void defend() {mLawsuit.defend();}@Overridepublic void finish() {mLawsuit.finish();}}

客户类

    public class Test {public static void main(String[] args){//构造一个李明ILawsuit liming=new LiMing();//构造一个代理律师并将李明作为构造参数ILawsuit lawyer=new Lawyer(liming);//律师提交诉讼申请lawyer.submit();//律师进行举证lawyer.burden();//律师替代李明进行辩护lawyer.defend();//完成诉讼lawyer.finish();}}

到这里整个代理就结束了,当然一个律师可以为多个人代理,只需要再定义个类实现ILawsuit即可。

3.2 代理模式可以分为两种

  • 静态代理:静态代理如上述那样,在代码运行前代理类的class编译文件已经存在。缺点,代理对象需要与目标对象实现一样的接口,会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。
  • 动态代理:动态代理和静态代理恰恰相反,动态代理是通过反射机制动态生成代理对象的,在程序没有执行前不需要知道代理是谁。

动态代理类

可以看出代理类不需要实现目标对象的接口。

    public class DynamicProxy implements InvocationHandler {private Object obj;//被代理引用public DynamicProxy(Object obj){this.obj=obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result=method.invoke(obj,args);//调用被代理类对象的方法return result;}}

修改客户类,实现动态代理

    public class Test {public static void main(String[] args) {//构造一个李明ILawsuit liming = new LiMing();//构造一个动态代理DynamicProxy proxy = new DynamicProxy(liming);//获取被代理类李明的ClassLoaderClassLoader loader = liming.getClass().getClassLoader();//动态构造一个代理者律师ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]{ILawsuit.class}, proxy);//律师提交诉讼申请lawyer.submit();//律师进行举证lawyer.burden();//律师代替李明进行辩护lawyer.defend();//完成诉讼lawyer.finish();}}

static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h )
该方法是在Proxy类中是静态方法,接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

3.3 代理模式实战

在Android开发中,我们经常会用到通知栏的功能,也就是NotificationManager。NotificationManager如何使用也很简单,但是API的不同,很多方法,样式也不一样,因此我们就要做适配。为每种不同的Notification样式定义一个抽象类,这里以正常64dp Height,256dp Height,和 headsUpContentView为例。

    public abstract class Notify {private Context context;private NotificationManager nm;private NotificationCompat.Builder builder;public Notify(Context context) {this.context = context;nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);builder = new NotificationCompat.Builder(context);builder.setSmallIcon(R.mipmap.ic_launcher).setContentIntent(PendingIntent.getActivity(context, 0,new Intent(context, MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT));}/*** 发送一条通知*/public abstract void send();/*** 取消一条通知*/public abstract void cancel();}

64dp Height的Notification实现

    public class NotifyNormal extends Notify {public NotifyNormal(Context context) {super(context);}@Overridepublic void send() {Notification n = builder.build();n.contentView = new RemoteViews(context.getPackageName(), R.layout.test);nm.notify(0, n);}@Overridepublic void cancel() {nm.cancel(0);}}

256dp Height的Notification实现

    public class NotifyBig extends Notify {public NotifyBig(Context context) {super(context);}@Overridepublic void send() {Notification n = builder.build();n.contentView = new RemoteViews(context.getPackageName(), R.layout.test);n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.test);nm.notify(0, n);}@Overridepublic void cancel() {nm.cancel(0);}}

headsUpContentView的Notification实现

public class NotifyHeadsUp extends Notify {public NotifyHeadsUp(Context context) {super(context);}@Overridepublic void send() {Notification n = builder.build();n.contentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);n.headsUpContentView = new RemoteViews(context.getPackageName(), R.layout.activity_main);nm.notify(0, n);}@Overridepublic void cancel() {nm.cancel(0);}}

代理类

    public class NotifyProxy extends Notify {private Notify notify;public NotifyProxy(Context context) {super(context);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {notify = new NotifyHeadsUp(context);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {notify = new NotifyBig(context);} else {notify = new NotifyNormal(context);}}@Overridepublic void send() {notify.send();}@Overridepublic void cancel() {notify.cancel();}}

以上就实现了整个代理过程,是不是很简单。

四,状态模式

4.1 基本概念

状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

使用场景

  • 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
  • 代码中包含大量与对象状态有关的条件语句(if-else 或switch-case)。

4.2 示例

模拟电视遥控器操作,在关机状态下,遥控器只能开机。在开机状态下,遥控器可以控制音量等。

定义电视操作接口

    /*** 电视状态接口*/public interface TvState {void nextChannel();//下一频道void prevChannel();//上一频道void turnUp();//音量增加void turnDown();//音量减小}

关机状态类

    /*** 关机状态,此时只有开机功能是有效的*/public class PowerOffState implements TvState {@Overridepublic void nextChannel() {}@Overridepublic void prevChannel() {}@Overridepublic void turnUp() {}@Overridepublic void turnDown() {}}

开机状态类

    /*** 开机状态*/public class PowerOnState implements TvState {@Overridepublic void nextChannel() {System.out.print("下一频道");}@Overridepublic void prevChannel() {System.out.print("上一频道");}@Overridepublic void turnUp() {System.out.print("增大音量");}@Overridepublic void turnDown() {System.out.print("减小音量");}}

电源操作接口

/*** 电源操作*/public interface PowerController {void powerOn();void powerOff();}

控制器

    public class TvController implements PowerController {TvState mTvState;public void setTvState(TvState tvState) {this.mTvState = tvState;}@Overridepublic void powerOn() {setTvState(new PowerOnState());System.out.print("开机啦");}@Overridepublic void powerOff() {setTvState(new PowerOffState());System.out.print("关机啦");}public void nextChannel(){mTvState.nextChannel();}public void prevChannel(){mTvState.prevChannel();}public void turnUp(){mTvState.turnUp();}public void turnDown(){mTvState.turnDown();}}

使用方式,关机后再调用音量减小不起作用

      TvController tvController = new TvController();//开机tvController.powerOn();//下一频道tvController.nextChannel();//增加音量tvController.turnUp();//关机tvController.powerOff();//减小音量tvController.turnDown();

到这里一个简单的状态模式就完成了,我看可以看出没有一个地方用到条件判断。状态模式的好处就是使我们的代码条理更加清晰,状态维护更加的方便。如果使用硬编码的方式,会出现很多if-else语句,代码可读性差而且也不易于维护。