盘点那些开发中用到的设计模式(第二部分)

791 阅读8分钟

前言

上文我们已经学习了关于Retrofit框架用到的建造者模式,单例模式以及工厂方法模式,接下来将继续复习剩余的外观模式,代理模式,策略模式,欢迎各位和我一起学习,查漏补缺

外观模式

外观模式也叫门面模式,属于结构型模式,简单来说,就是外部与内部必须通过统一的对象来进行(门面对象),提供接口,使得子系统更易于使用

实现方式

我们在使用有些付费软件的时候都需要去登录或者充值的吧,这些都是第三方SDK来完成的,我们只用进行简单的接入就可以使用相应的功能了

创建门面对象

可以封装SDK中的登录和会员支付接口,供外部子APP去调用

class VideoSDK {

    fun login() {
        val loginManager = LoginManager()
        loginManager.login()
    }

    fun pay(money:Int) {
        val payManager = PayManager()
        payManager.pay(money)
    }

}

//登录系统
class LoginManager {
    fun login() {
        println("打开登录界面")
        println("进行登录操作")
        println("登录成功")
    }
}

//支付系统
class PayManager {
    fun pay(momey: Int) {
        println("生成订单信息")
        println("选择支付方式")
        println("支付成功:" + momey + "元")
    }
}

Android中的应用

在Android外观模式也应用广泛,例如Conext类,它封装了很多重要的操作,比如startActivity()、sendBroadcast()等,这就是开发者调用的统一入口。Context是一个抽象类,它只是定义了抽象接口,真正的实现在ContextImpl类中

这样以来我们就可以简单调用个方法就可以启动一个Activity,Context已经将其具体的细节都封装好了

总结

优点

  • 降低客户端与子系统之间的耦合度
  • 通过外观类对子系统的接口封装,更易于使用
  • 灵活性提高了,不管子系统如何变化,只要不印象我们的外观对象,可以自由修改,更好的划分层次

缺点

  • 增加新的子系统的话,外观对象需要改变里面的代码的,违背了开闭原则
  • 所有子系统的功能都要通过一个接口来实现的话,功能会会非常复杂

应用场景

  • 对于一些层次划分明显的,例如医院的模式,这就是为什么医院门口会有相应的接待员进行服务
  • 可以将一个子系统和客户端分离开来,这样子系统也可以移植到别的应用上去,具有一定的独立性

代理模式

无独有偶,这种模式如字面意思,简单来说就是为其他对象提供代理来控制对这个对象的访问

实现方式

一般我们使用代理模式有以下步骤

  1. 设置抽象主题类,这个是用来声明该对象和代理的公共方法
  2. 创建一个被代理类(被委托类),负责具体业务逻辑的执行,可以通过它来调用主体的方法
  3. 创建代理类,这个就是对被代理类的引用,在其中使用的接口方法去执行被代理类中对应的接口方法
  4. 最终的客户端类,这里就是使用代理模式的地方

可以说代理模式是属于结构型模式,下面我以代购为例子,来看看它的具体实现方式

首先创建抽象主题类

可以明确来说,代购一般都是有个购买方法,下面我创建People都有一个购买的方法

interface People {   
    //购买
    fun buy()
}

创建具体的主题类

在国内的人定义个具体的购买过程,有个buy()方法,继承People接口,重写购买方法

class BuyerRelease(people: People) : People {
    
    override fun buy() {
        println("境内需要买一个海外护肤品")
    }
}

使用代理类

这时候远在境外的代购人士需要知道你是谁,那个人想要买什么产品

class Oversea(people: People) :People {
    private var mPeople:People?= people


    override fun buy() {
        println("海外代购人员上线.....")
        mPeople?.buy() //直接调用被代理者方法
    }
}

上述实现其实是代理模式的中的静态代理,那顾名思义,还有一种是动态代理,简单来说就是需要在运行的时候通过反射去生成,那就没有了代理类的字节码文件,它们之间的关系是由运行的时候才确定的

代理接口InvocationHandler,实现该接口需要重写invoke()方法

class DynamicProxy(  //实现InvocationHandler接口
    //被代理的对象
    private val obj: Any
) : InvocationHandler {
    //重写invoke()方法
    @Throws(Throwable::class)
    override fun invoke(proxy: Any?, method: Method, args: Array<Any?>?): Any {
        println("海外动态代理调用方法: " + method.name)
        return method.invoke(obj, args) //调用被代理的对象的方法
    }
}

这样的话我们没必要像静态代理一样,接口新增一个方法就需要所有代理类实现这个方法,大大增加了代码的复杂程度,而动态代理就不一样了,不需要每一个方法都进行中转,可以灵活处理,复用性大大增强了

Android中的应用

最典型的例子就是retrofit网络请求库,这里简要提及下,为后文做铺垫

  1. 编写api接口
interface xxxApi {
    @POST("app/xxxx")
    @FormUrlEncoded
    fun sendEmailCode(@Field("email") email: String?): Observable<BaseResponse<String>>
}
  1. 初始化Retrofit
var retrofit: Retrofit = Builder()
    .baseUrl("https://xxxxxx")
    .build()
  1. 动态创建当前实例,注意这里的create的方法就是使用到了动态代理模式
        val service:xxxApi = retrofit.create(xxxApi::class.java)
        service.sendEmailCode(xxxx)

总结

下面我们来总结下代理模式有什么优点和缺点

优点

  • 降低耦合,让模块和系统之间
  • 可以控制调用者的访问权限,起到一种保护的作用

缺点

  • 由于我们增加了代理对象,所以可能会造成请求的处理速度变慢
  • 有些代理模式实现起来非常复杂,会增加系统实现的复杂度

适用场景

  • 一个对象不能或者不想直接访问另一个对象时,可以通过一个代理对象来间接访问
  • 被访问的对象不想暴露全部内容时,可以通过代理去掉不想被访问的内容

策略模式

策略模式属于行为型模式,提供了一系列的算法,形成一个算法组给客户端调用,这样客户端可以根据不同的策略模式来解决不同的问题

实现方式

看到这里,对于策略模式还是一知半解,我们来举个例子来实现下,以休闲时间选择选择娱乐方式为例

定义个公共接口

这里就是我们如何选择的休闲娱乐方式

interface ChoiceStragety {
    //娱乐方式
    fun chase() //娱乐方法
}

创建具体策略类

实现抽象策略类的接口,具体的娱乐方式

class ShoppingStrategy : ChoiceStragety {
    override fun chase() {
        println("逛街咯~")
    }
}

class MoviesStrategy : ChoiceStragety {
    override fun chase() {
        println("看电影咯~")
    }
}

class GameStrategy : ChoiceStragety {
    override fun chase() {
        println("玩游戏咯~")
    }
}

创建环境类

这里是需要操作不同的娱乐方式策略,可以使用不同的方式

class Context(//定义抽象策略类
    private val chaseStragety: ChoiceStragety
) {
    fun chase() { //执行具体策略对象的策略
        chaseStragety.chase()
    }

    init { //构造方法传递具体策略对象过来
    }
}

Android中的应用

  1. 我们使用到的ListView都需要设置Adapter,根据实际的需求使用不同的Adapter,这里就运用到了策略模式
    listView = (ListView)findViewById(R.id.list_view);
    
    //使用ArrayAdapter
    listView.setAdapter(new ArrayAdapter<String>(this,R.id.item,new String[] {"one","two"}));
    
     //使用BaseAdapter
    listView.setAdapter(new BaseAdapter() {
        @Override
        public int getCount() {
            return 0;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return null;
        }
    });

这里看下ListView的源码

  public class ListView extends AbsListView {//相当于环境类
        @Override
        public void setAdapter(ListAdapter adapter) {//设置策略,即adapter
            //其他代码略
        }
    }

    public interface ListAdapter extends Adapter {//抽象策略接口
        
    }
    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {//具体策略类BaseAdapter,实现ListAdapter接口
        
    }
    public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {//具体策略类ArrayAdapter,继承BaseAdapter,即实现ListAdapter接口
        
    }
  • 设置不同的Adapter(即不同的策略),我们就可以写出符合我们需求的ListView布局
  1. 我们常用的属性动画就是里面setInterpolator也是有用到策略模式的,感兴趣的小伙伴可以去详细了解下

总结

在Android开发中,策略模式也是用的非常多的,就比如说,让自己重新实现一个ViewPager,并且带有Indicator,会不会用到策略模式呢?

优点

  • 策略模式等级结构定义了一个算,把公共代码移到父类当中,从而避免重复的代码
  • 提供了可以替换继承关系的方法
  • 避免使用多重条件判断语句

缺点

  • 客户端调用的时候必须要知道所有的策略类,所以简单来说,策略模式只适用于那些客户端知道所有的算法或者行为的情况
  • 通过依赖于环境的状态保存到客户端里面,将策略类设计成可共享的

应用场景

  • 试想一下,如果一个应用里有许多类似的类,区别仅在于它们的行为,就可以使用策略模式动态选择其中一种行为
  • 一个对象中有很多行为,如果不使用策略模式的话,就只好使用多重条件判断的语句