这两天开始学习高焕堂老师的Android的设计模式,感觉他的讲解非常透彻,把Android框架和设计模式结合讲解,在Android这个大而全的体系框架中,从整体上把握设计模式的应用,感觉对Android框架设计的理解加深了许多,希望以后能够同步学习进度,记录和分享学习心得。
我个人的理解,说到模式就离不开框架,设计模式和框架就是虚实的两面,就像武功心法和招式的关系一样。设计模式就类似于心法(虚),而框架就类似于招式(实),心法是教你在什么时候应该运用什么方法,以及如何运用才能发挥最好的效果,招式就是将心法运用在实战修炼当中得到的具体套路,有了套路才能以不变应万变,每一个招式都有一个固定的使用情景。从另一方面来说,模式(Template、Factory、State等)是一些专家针对一系列经常出现的问题所提炼出来的抽象解决思想;框架就是针对于某一个具体的使用环境(Android、Windows等),用以解决特殊领域的具体问题的。总的来说,框架是模式在特定的领域的具体实现。所以我觉得需要把模式的讲解放到具体的框架当中才能够加深理解。
【变与不变之分离】,将【变化】的部分作为接口留给应用实现,将【不变】的部分在框架内部实现。不同的开发人员实现不同的【变化】,便组成了形形色色的APP了。 ——高焕堂
白盒框架:采取继承机制实现,当程序编译时,编译程序建立框架类与应用类之间的沟通(继承机制)。白盒框架因为有编译器帮助,所以比较容易设计,而且藉由继承机制能够有效的减轻程序的开发复杂度,结构清晰;缺点是由于应用类和框架类是继承关系,故而两者的依赖程度较高,独立性较差,不易修改和拓展。
黑盒框架:采取接口机制实现,通过实现接口的双向引用来达到双向沟通的目的。黑盒框架能够有效的隐藏框架的内部实现机制,减少框架与应用的耦合程度。缺点是实现的结构复杂,运行前需要检查各个组件是否实现了双向接通。
Android框架的实现结合了白盒和黑盒的方式,至于具体实现的方式和原理,可以参考高焕堂老师的《Google Android应用框架原理与程序设计36计》。以后学到了,再继续分享。
目标:使用Template Method实现一个画弧线的小程序。
结构
代码
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);
}
下面用一个MP3的播放程序来观察Android中所用到的Templates模式。【不变】的部分属于框架,【变化】的部分属于应用。
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的用武之地。刚接触设计模式,希望高手来指出错误的地方,帮助我提高,谢谢。