您现在的位置是:首页 >技术交流 >设计模式系列网站首页技术交流

设计模式系列

朽木成才 2023-06-08 20:00:02
简介设计模式系列

一,单例模式

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

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

  • 构造函数不对外开放,一般为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 {
        @Override
        public void method() {
            System.out.print("我是历史类");
        }
    }

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

抽象工厂类

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

具体的工厂实现

    public class ConcreteFactory extends Factory {
        @Override
        public 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
         */
        @Override
        public <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> {
        @Override
        public void add(String id,File file) {
            //添加文件数据
        }

      @Override
      public void remove(File file) {
        //删除文件数据
      }

    @Override
    public void update(String id,File file) {
        //更新文件数据
    }

    @Override
    public Object query(String id) {
        //查询文件数据
        return null;
    }
    }

SharedPreferences存储实现

    public class SharedStorage extends ICallBackStorage<String> {
        @Override
        public void add(String id, String s) {
            //添加SharedPreferences数据
        }
    @Override
    public void remove(String s) {
        //删除SharedPreferences数据
    }

    @Override
    public void update(String id, String s) {
        //更新SharedPreferences数据
    }

    @Override
    public Object query(String id) {
        //查询SharedPreferences数据
        return null;
    }
    }

数据库存储实现

    public class SqlStorage extends ICallBackStorage<Object> {
        @Override
        public void add(String id, Object o) {
            //添加数据
        }

    @Override
    public void remove(Object o) {
        //删除数据
    }

    @Override
    public void update(String id, Object o) {
        //更新数据
    }

    @Override
    public 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 {
        @Override
        public <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 {
        @Override
        public void submit() {
            System.out.println("老板欠工资,申请仲裁");
        }
    
        @Override
        public void burden() {
            System.out.println("银行流水,证明欠薪");
        }
    
        @Override
        public void defend() {
            System.out.println("证据确凿");
        }
    
        @Override
        public void finish() {
            System.out.println("诉讼成功");
        }
    }

代理律师

 public class Lawyer implements ILawsuit {
        private ILawsuit mLawsuit;//持有一个具体被代理的引用

    public Lawyer(ILawsuit lawsuit) {
        mLawsuit = lawsuit;
    }

    @Override
    public void submit() {
        mLawsuit.submit();
    }

    @Override
    public void burden() {
        mLawsuit.burden();
    }

    @Override
    public void defend() {
        mLawsuit.defend();
    }

    @Override
    public 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;
        }
        @Override
        public 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);
            //获取被代理类李明的ClassLoader
            ClassLoader 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);
        }
    
        @Override
        public void send() {
            Notification n = builder.build();
            n.contentView = new RemoteViews(context.getPackageName(), R.layout.test);
            nm.notify(0, n);
        }
    
        @Override
        public void cancel() {
            nm.cancel(0);
        }
    }

256dp Height的Notification实现

    public class NotifyBig extends Notify {
        public NotifyBig(Context context) {
            super(context);
        }
    
        @Override
        public 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);
        }
    
        @Override
        public void cancel() {
            nm.cancel(0);
        }
    }

headsUpContentView的Notification实现


    public class NotifyHeadsUp extends Notify {
        public NotifyHeadsUp(Context context) {
            super(context);
        }
    
        @Override
        public 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);
        }
    
        @Override
        public 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);
            }
        }
    
        @Override
        public void send() {
            notify.send();
        }
    
        @Override
        public 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 {
        @Override
        public void nextChannel() {
    
        }
    
        @Override
        public void prevChannel() {
    
        }
    
        @Override
        public void turnUp() {
    
        }
    
        @Override
        public void turnDown() {
    
        }
    }

开机状态类

    /**
     * 开机状态
     */
    public class PowerOnState implements TvState {
        @Override
        public void nextChannel() {
            System.out.print("下一频道");
        }
    
        @Override
        public void prevChannel() {
            System.out.print("上一频道");
        }
    
        @Override
        public void turnUp() {
            System.out.print("增大音量");
        }
    
        @Override
        public 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;
        }
    
        @Override
        public void powerOn() {
            setTvState(new PowerOnState());
            System.out.print("开机啦");
        }
    
        @Override
        public 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语句,代码可读性差而且也不易于维护。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。