Android框架设计模式(四)——Adapter Method


一、适配器模式介绍


适配器在平常在生活中是经常会用到的,特别是电子产品。像手机、电脑、家用电器都会用到适配器来转换电压的大小,以提供合适的电压。适配器就是把原来不符合要求的电压、电路信号转换成合适的电压和信号。简单来说,它就是一个接口转换器。

我们使用适配器的本质原因是:当我们的系统已经确定了一个标准,但已有的资源与现有标准不兼容,而且又无法或者不便修改这个标准的时候,就需要用适配器来使得不兼容的被使用方包装成已有的标准供已有的系统使用


什么是适配器模式?


定义:

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使得原本因接口不匹配(接口名、返回参数、输入参数等)而无法一起工作的两个类能够在一起工作。

分类:

在软件程序设计模式中,适配器模式分为两种:类适配器、对象适配器。

  • 类适配器

    • 概念:通过实现目标Target接口以及继承Adaptee(需要被适配的类)来实现接口转换。把Adaptee的接口转换成Target需要的接口。

    • UML图

      这里写图片描述

  • 对象适配器

    • 概念:与类适配器也一样的目的:把Adaptee的接口转换成Target需要的接口。但是不同的是,对象适配器是使用代理关系链接到Adaptee类,即将Adaptee作为Adapter中的成员,由Adapter作为代理来实现Adaptee的功能。

    • UML图

    这里写图片描述


适配器应用于什么场景?


1.系统需要使用现有的类,而此类的接口不符合系统的需要,即接口不兼容(最普通的适配器);

2.想要建立一个可以重复使用的类,用于将一些彼此之间关联不大的一些类,包括一些可能在将来引进的类一起工作(Binder接口链接Activity与Service,Activity和Service两者本就可以独立运行,通过Binder接口,将Service端的服务适配成Activity端能够识别的transact()方法)。

3.需要一个统一的输出接口,而输入端的类型不可预知(BaseAdapter就是将ListView、GridView等控件与多变的自定义View结合使用的适配器,输入端类型为自定义多变的类型,而输出端得到的总是View类型)


二、Android框架中的适配器模式应用


一般来说,现在都倾向于使用对象适配器,因为对象适配器能够将被适配对象的方法隐藏,而如果使用类适配器的话,由于继承的缘故,使得适配器也继承了被适配对象的方法,这样会暴露被适配对象。因此,Android中也是使用的对象适配器,通过代理的方法来实现适配。


范例一:ListView+BaseAdapter+自定义View


ListView与BaseAdapter的结合是适配器使用情景的第三种情况。即:需要一个统一的输出接口,而输入端的类型不可预知。
自定义View千变万化,不同View接口又各异,因此通过适配器来提供统一的输出接口,能够使得ListView达到以【不变应万变的效果】。

下面的UML图,反应了一般的应用中Activity、ListView、BaseAdapter、自定义View之间的联系。这里的观察者模型只是我自己为了简化而使得BaseAdapter直接实现Observable(通知者接口),ListView实现Observer接口。实际上BaseAdapter与ListView还有更加深层次的继承关系,而且观察者模型是对象观察者模型(即观察者和通知者是作为类成员,通过代理实现的),而不是基于接口实现的观察者模型。

通俗UML图:

这里写图片描述


关键代码分析:

在BaseAdapter中,最重要的方法就是getView()。它是链接ListView容器和其中的ItemView的桥梁,getView(),是一个统一的接口,它固定返回的是View类型的参数,因此无论是哪种类型的自定义视图,由于它们都是View的子类,因此ListView都能够识别。这就是BaseAdapter中的适配方法,输出是不变的,而输入可以是变化的。

//getView()方法将自定义View进行适配,对各个子View进行装配,最
//后将其装入一个统一的View之中,返回给ListView。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null){
                holder = new ViewHolder();
                convertView = View.inflate(getBaseContext(),R.layout.activity_audiocable,null);
                holder.mImageCover = convertView.findViewById(R.id.img_cover);
                holder.mTextTitle = convertView.findViewById(R.id.txt_title);
                convertView.setTag(holder);
            }
            holder = (ViewHolder) convertView.getTag();
            holder.mTextTitle.setText((String) getItem(position).getTitle());
            holder.mImageCover.setImageResource((String) getItem(position).getCoverRes());
            return convertView;
 }

  class ViewHolder{
            TextView mTextTitle;
            ImageView mImageCover;
        }

范例二:Activity+Binder+MediaPlayer


Activity+Binder+Mediaplayer中,Binder充当适配器。这是适配器使用的第二种情况:想要建立一个可以重复使用的类,用于将一些彼此之间关联不大的一些类,包括一些可能在将来引进的类一起工作。
我们启动服务的过程中,Binder就是一个适配器,它提供了transact()方法给框架调用,onTransact()给应用类别实现,而后面的onTransact()方法就是适配方法,通过onTransact方法与不同的后台服务实现对接(多媒体控制、任务下载)。

通俗UML图:

这里写图片描述


关键代码分析:

我们来看一看适配方法onTransact(),顺便提一下,如果从框架的角度来看,则onTransact()方法是一个hook方法。onTransact()方法的任务就是负责与多媒体、以及后台任务进行对接。在onTransact()方法中就调用多媒体的控制方法(任务控制方法),从而为前端提供服务。
注意:Activity只知道通过Binder调用transact()方法,其余的都是通过onTransact()进行适配。

public class mp3Player_Adapter extends Binder{  
   private MediaPlayer mPlayer = null;  
   private Context ctx;    
   public mp3Player_Adapter(Context cx){  ctx= cx; }

//通过onTransact()方法完成调用play()和stop(),对MP3进行播放和
//停止的控制。
 @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags)throws android.os.RemoteException { 

   reply.writeString(data.readString()+ " mp3");           
   if(code == 1)  
     this.play();        
   else if(code == 2)  
     this.stop();        
   return true;  
}  
//play()、stop()都是不能被客户端识别的方法,需要通过onTransact()来进行适配
public void play(){   
  if(mPlayer != null) 
    return;   
 mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);   
 try { 
   mPlayer.start();   
 } catch (Exception e) {  
    Log.e("StartPlay", "error: " + e.getMessage(), e); 
 }  
}  
public void stop(){   
  if (mPlayer != null) {  
   mPlayer.stop(); 
   mPlayer = null; 
  } 
 }
} 

public class ac01 extends Activity implements OnClickListener {  
   private final int WC = 
   LinearLayout.LayoutParams.WRAP_CONTENT;  
   private final int FP = LinearLayout.LayoutParams.FILL_PARENT;  
   private Button btn, btn2, btn3;  
   public TextView tv;  
   private IBinder ib = null; 

public void onCreate(Bundle icicle) { 
 //......初始化
 startService(in);   //启动服务
 //绑定服务
 bindService(in, mConnection, Context.BIND_AUTO_CREATE);  }

 //服务连接,用于通知Activity服务连接的情况,同时返回IBinder接口  
 private ServiceConnection mConnection = new ServiceConnection() {     
 public void onServiceConnected(ComponentName className, IBinder ibinder)  {  
   ib = ibinder;  
  }      
 public void onServiceDisconnected(ComponentName className) {}   
};

 public void onClick(View v) {    
 switch (v.getId()) {   
   case 101:    
    Parcel pc = Parcel.obtain();    
    Parcel pc_reply = Parcel.obtain();        
    pc.writeString("playing");    
    try { 
    //调用适配器方法操作MP3播放     
      ib.transact(1, pc, pc_reply, 0);  
      tv.setText(pc_reply.readString());    
    } catch (Exception e) {  
      e.printStackTrace(); 
      }    
  break;   
  case 102:    
   pc = Parcel.obtain();    
   pc_reply = Parcel.obtain();    
   pc.writeString("stop");    
   try { 
   //调用适配器方法操作MP3停止
     ib.transact(2, pc, pc_reply, 0);     
     tv.setText(pc_reply.readString());    
   } catch (Exception e) { 
    e.printStackTrace(); 
   }    
 break;   
 case 103: 
    finish();  
 break;   
 } 
}} 

三、适配器模式与其他模式的配合


在安卓的组件使用过程中,每一个组件都不止是使用一种设计模式而已,它们都是结合了许多设计模式而形成的。单独的设计模式是无法设计出实用性、拓展性很好的组件。上面所举的两个Android适配器模式的应用也是包含了许多其他设计模式的,只是我们从不同的角度分析而已。下面是我对上面两个例子进行的分析,因为没有分析的很深(源代码的继承链和关系很复杂,只分析表面的几层),就列出一些模式的组合,当然还有其他的模式,这里我深究不来,也暂时不愿深究。


情景一:适配器+观察者+模板+策略+组合 = BaseAdapter+ListView+自定义View


整体UML图

这里写图片描述


模式分析(不同的视角决定)
  • 适配器模式
    从兼容的角度来看,那么BaseAdapter中的四个方法(getItemId()、getItem()、getView()、getCount())都是适配器方法,他们链接了ListView和ListView镶嵌的那些不确定的多变的自定义View对象。ListView的装配和初始化只识别这四个方法,适配器将装入的View对象按照这四个方法分别进行适配,输出符合ListView要求的接口和参数。

  • 观察者模式
    从数据更新的角度来看,那么BaseAdapter与ListView之间存在着通知者/观察者的联系。BaseAdapter是一个通知者,而ListView是一个观察者。BaseAdapter中存放着ListView的数据集合,每当数据集合有更新的时候,就会调用notifyDataChanged()方法,通知ListView进行更新。同时,在ListView更新完毕之后,也可以调用相应的方法反馈给BaseAdapter。

  • 模板模式
    从框架变与不变的分离的角度上来看,BaseAdapter、ListView的继承链上都存在着模板模式。即他们都有框架父类,然而框架父类定义了【不变】的部分,然后应用程序(程序员继承的类别)部分实现【变化】的部分。

  • 策略模式
    单从变化的分离来看,那么BaseAdapter中还存在着策略模式的应用。getView()即是一个策略方法,对于不同的界面getView()会有不同的装配策略,但是他们的结果和参数都一样,它承担着变化的部分。我们实现不同的界面就会继承BaseAdapter实现不同的getView(),正好就是策略模式的体现。

  • 组合模式
    从自定义View本身的布局来看,自定义View是一个由不同布局和控件组合而形成的,它是组合模式的经典呈现,将系统提供给我们的(或者我们自己实现的一个View)组合成一个新的绚丽的视图。从这个角度上来看,它便存在着组合模式的应用。


情景二:适配器+观察者+模板 = Service + Activity + 自定义服务


整体UML图

这里写图片描述


模式分析(不同的视角决定)
  • 适配器模式
    从适配器模式的角度分析,Binder就是一个适配器类别,它通过一个transact()接口对多媒体(MP3、MP4)、下载任务等不同的对象进行适配,客户端只需要调用transact(),接口的不兼容问题已经由Binder类别进行了适配。

  • 观察者模式
    从Activity与Service通信的角度来看,Service与Activity还存在着通知者/观察者关系。Service是通知者,Activity是观察者。这点可以通过绑定服务体现,我们调用了bindService()后,当服务绑定了之后,Service会调用onBind方法返回一个IBinder接口,然后通过调用ServiceConnection中的onServiceConnect()方法返回一个IBinder接口给Activity客户端。完成Activity与Service的通信。

  • 模板模式
    从框架中的变与不变分离的角度来看,Service和Activity中各自都存在着模板模式。Service中transact()是模板方法,而onTransact()是hook(卡隼)方法;Activity中callApplicationOnCreate是模板方法,而onCreate()是hook(卡隼)方法。我们程序实现的部分就是卡隼方法,而程序框架部分就是模板方法。

  • 工厂模式
    从对象的产生角度来看,Service与Activity中也各自都存在着工厂模式的应用。对于Activity来说,onCreate()就是一个工厂方法,它创建了系统运行需要的对象;对于Service来说,onCreate()也是一个工厂方法,它产生了Binder对象,然后再返回给客户端Activity。


四、总结


适配器模式是一种在系统已经存在且比较大的情况下,突然增加某些模块之间的联系,而原有的接口又不符合的情况下才使用的。在Android中,适配器模式的使用是每个Android工程师都再熟悉不过的了,不过在使用适配器的过程中,我们需要明白适配器模式与其他模式混合使用的情况,同时也需要明白适配器存在的原因,什么时候需要用适配器,什么时候不需要使用适配器。而且,同样的一个适配器模式的应用中,可能会存在其他不同的设计模式思想,设计模式之间并不是独立的,而是相互渗透相互依赖的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值