Android框架设计模式(一)——Template Methods

引言

这两天开始学习高焕堂老师的Android的设计模式,感觉他的讲解非常透彻,把Android框架和设计模式结合讲解,在Android这个大而全的体系框架中,从整体上把握设计模式的应用,感觉对Android框架设计的理解加深了许多,希望以后能够同步学习进度,记录和分享学习心得。
我个人的理解,说到模式就离不开框架,设计模式和框架就是虚实的两面,就像武功心法和招式的关系一样。设计模式就类似于心法(虚),而框架就类似于招式(实),心法是教你在什么时候应该运用什么方法,以及如何运用才能发挥最好的效果,招式就是将心法运用在实战修炼当中得到的具体套路,有了套路才能以不变应万变,每一个招式都有一个固定的使用情景。从另一方面来说,模式(Template、Factory、State等)是一些专家针对一系列经常出现的问题所提炼出来的抽象解决思想;框架就是针对于某一个具体的使用环境(Android、Windows等),用以解决特殊领域的具体问题的。总的来说,框架是模式在特定的领域的具体实现。所以我觉得需要把模式的讲解放到具体的框架当中才能够加深理解。

一、框架

  • 基本思维:

    【变与不变之分离】,将【变化】的部分作为接口留给应用实现,将【不变】的部分在框架内部实现。不同的开发人员实现不同的【变化】,便组成了形形色色的APP了。 ——高焕堂

  • 框架分为两类

    1. 白盒框架:采取继承机制实现,当程序编译时,编译程序建立框架类与应用类之间的沟通(继承机制)。白盒框架因为有编译器帮助,所以比较容易设计,而且藉由继承机制能够有效的减轻程序的开发复杂度,结构清晰;缺点是由于应用类和框架类是继承关系,故而两者的依赖程度较高,独立性较差,不易修改和拓展。

    2. 黑盒框架:采取接口机制实现,通过实现接口的双向引用来达到双向沟通的目的。黑盒框架能够有效的隐藏框架的内部实现机制,减少框架与应用的耦合程度。缺点是实现的结构复杂,运行前需要检查各个组件是否实现了双向接通。

  • 实现工具和机制(设计模式同理):

    1. 卡榫函数:框架与应用程序之间的接口。框架通过卡榫函数来实现对应用类别的控制(调用应用类别的设计代码),从而实现应用框架与应用类时间的沟通。
    2. IOC(控制反转):也称“依赖倒置”,框架主导组件的装配和修饰(加载框架内部属性、程序员定义代码部分)。框架定义了接口而子类实现接口,框架上调用子类的接口(子类只负责实现这个接口的不同形式,从而形成不同的应用),从而将控制主权交给框架,实现控制反转。
  • Android中的框架

    Android框架的实现结合了白盒和黑盒的方式,至于具体实现的方式和原理,可以参考高焕堂老师的《Google Android应用框架原理与程序设计36计》。以后学到了,再继续分享。

三、Template Methods

简介:模板方法模式是设计框架的核心之一,把【不变】的部分放在框架内部,把【变化】部分留给应用类别实现。消除重复和公共的代码,使得子类在不改变一个框架结构前提下重新定义框架的某些特定可变部分。

UML图示:

应用场景:

  1. 框架搭建(配合IOC机制实现)
  2. 消除代码重复

java范例:

目标:使用Template Method实现一个画弧线的小程序。

  • 结构

  • 代码

    • Shape类——框架部分
    • Bird类——应用部分
    • Jpanel、JFrame——界面接口部分
import java.awt.*; 
public abstract class Shape{  public void template_paint(Graphics gr){      
   // 画天空背 景 
   gr.setColor(Color.black);   
   gr.fillRect(10,30, 200,100);        
   // 画前景 
   hook_paint(gr); 
}    
protected abstract void hook_paint(Graphics gr); 
} 
import java.awt.*; public class Bird extends Shape {  @Override  public void hook_paint(Graphics gr){     
// 画图(弧线)指令 
   gr.setColor(Color.cyan);     
   gr.drawArc(30,80,90,110,40,100);        
   gr.drawArc(88,93,90,100,40,80); 
   gr.setColor(Color.white);     
   gr.drawArc(30,55,90,150,35,75);    
   gr.drawArc(90,80,90,90,40,80);   
    }
}
import java.awt.*; 
import javax.swing.*; 
class JP extends JPanel { 
 public void paintComponent(Graphics gr){          
   super.paintComponents(gr);           
   Shape sp = new Bird();             
   sp.template_paint(gr);      
   }
} 
public class JMain extends JFrame { 
   public JMain(){  
      setTitle("");  
      setSize(350, 250);  
   } 
   public static void main(String[] args) { 
      JMain frm = new JMain();   
      JP panel = new JP(); 
      frm.add(panel);      
     frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frm.setVisible(true);   
     }
}  

上面的程序实现了框架对内(hook_paint())和对外(template_paint())接口的分离,隐藏了框架内部的实现原理。使得框架与客户端的耦合度降低,易拓展和修改。

//对外(客户端)——调用框架模板方法
public void paintComponent(Graphics gr){          
   super.paintComponents(gr);           
   Shape sp = new Bird();             
   sp.template_paint(gr);      
   }
//对内(框架内部)——调用内部卡榫函数(即沟通接口)
template_paint(Graphics gr){      
   // 画天空背 景 
   gr.setColor(Color.black);   
   gr.fillRect(10,30, 200,100);        
   // 画前景 
   hook_paint(gr); 
}  

四、Android 中的Templates范例

下面用一个MP3的播放程序来观察Android中所用到的Templates模式。【不变】的部分属于框架,【变化】的部分属于应用。

  • 效果:点击Play之后放音乐,Stop停止

    Android框架设计模式(一)——Template Methods_第1张图片

  • 结构

    Android框架设计模式(一)——Template Methods_第2张图片

  • 代码

    • Activity——控制器
    • Binder——IPC通信,连接Service与Activity
    • Service——后台播放服务
    • Button——前端按钮接口
package com.example.david.designmode;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
    private final int FP = LinearLayout.LayoutParams.MATCH_PARENT;
    private static MainActivity appRef = null;
    private MyButton btn_play, btn_stop, btn_exit;
    private IBinder ib;
    public static MainActivity getAppRef(){

        return  appRef;
    }
    public void btEvent(String data){

        setTitle(data);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        appRef = this;
        btn_play = (MyButton) findViewById(R.id.btn_play);
        btn_stop = (MyButton) findViewById(R.id.btn_stop);
        btn_exit = (MyButton) findViewById(R.id.btn_exit);

        btn_exit.setOnClickListener(this);
        btn_stop.setOnClickListener(this);
        btn_play.setOnClickListener(this);

        //启动服务
        startService(new Intent(this,MyService.class));
        //绑定服务
        bindService(new Intent(MainActivity.this,MyService.class),mConnection, Context.BIND_AUTO_CREATE);

    }
    //连接参数,监听该服务的状态,获得IBinder接口,从而通过模板函数(transact())回调实现IPC通信交互
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            ib = service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    public void onClick(View v) {
       switch (v.getId()){
           case R.id.btn_play:
               try {
                   setTitle("MP3 Music");
                   ib.transact(1,null,null,0);//模板函数,调用Binder中onTransact()卡榫函数,播放音乐
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
               break;
           case R.id.btn_stop:
               try {
                   setTitle("Stop");
                   ib.transact(2,null,null,0);//同上
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
               break;
           case R.id.btn_exit:
               break;
       }
    }
}
package com.example.david.designmode;
//Bind类进行界面Activity与播放MP3的Service之间的IPC通信
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;

/** * Created by David on 2016/3/8. */
public class MyBinder extends Binder {
    private MediaPlayer mPlayer = null;
    private Context context ;
    public MyBinder(Context context){
        this.context = context;
    }

    /** * 卡榫函数,建立应用于框架之间的连接和沟通 */
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        if (1 == code){
            this.play();
        }else if ( 2 == code){
            this.stop();
        }
        return true;
    }
    protected  void play(){
        if (mPlayer != null) return;//表示正在播放,故不用重播
        mPlayer = MediaPlayer.create(context,R.raw.libai);
        try {
            mPlayer.start();
        }catch (Exception e){
            Log.e("StartPlay","Error"+e.getMessage(),e);
        }
    }
    protected  void stop(){
        if (null != mPlayer){//停止播放,释放媒体,mPlayer置为null以待下一次播放
            mPlayer.stop();
            mPlayer.release();
            mPlayer = null;
        }
    }
}
package com.example.david.designmode;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {
    private IBinder mBinder = null;
    public MyService() {

    }
    @Override
    public void onCreate(){

        mBinder = new MyBinder(getApplicationContext());
    }

    @Override
    public IBinder onBind(Intent intent) {

        return  mBinder;
    }
}
package com.example.david.designmode;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.support.design.widget.*;

/** * Created by David on 2016/3/8. */
public class MyButton extends Button {

    private Context mContext;
    private AttributeSet mAttrs;
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        mAttrs = attrs;
    }

}

分析

上面的程序包含了几个不同的小框架,我们只截取IBinder机制的部分来观察Template 模式(另外还有Service机制中的Template模式大家就依葫芦画瓢的找):

@Override
    public void onClick(View v) {
       switch (v.getId()){
           case R.id.btn_play:
               try {
                   setTitle("MP3 Music");
                   ib.transact(1,null,null,0);//模板函数,调用Binder中onTransact()卡榫函数,播放音乐
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
               break;
           case R.id.btn_stop:
               try {
                   setTitle("Stop");
                   ib.transact(2,null,null,0);//同上
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
               break;
           case R.id.btn_exit:
               break;
       }
    }

上面呼叫的transact()函数就是定义域Template Method模式里面的模板函数,用于对外沟通;而transact()又呼叫onTransact()函数,实现对内沟通。这里【不变】的部分是transact(),【变化】的部分是onTransact()。我们来看transact()的源码:

/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */
    public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);//调用应用类别所重写的onTransact()函数
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

五、总结

Template Method 在Android框架中是无处不在的,因为它正是框架设计的基本核心。Template还可以使用在除了框架以外的地方,比如:消除重复代码。只要是把【不变】与【变化】分离开来就是Tamplate Method的用武之地。刚接触设计模式,希望高手来指出错误的地方,帮助我提高,谢谢。

你可能感兴趣的:(设计模式,android,框架,高焕堂)