michaelye1988 发表于 2013-1-30 04:04:30

策略模式--从源码TabHost中看策略模式

什么是策略模式:
 
      针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
      应当由客户端自己决定在什么情况下使用什么具体策略模式。
      策略模式不适合于处理同时嵌套多于一个算法的情形。
 
针对的设计原则:
 
封装变化的。
多用组合,少用继承。
针对接口编程,不针对实现编程。
 
优缺点:
 
优点:
1. 提供了管理相关的算法族的办法。
2. 提供了可以替换继承关系的办法。
3. 可以避免使用多重条件转移语句。
 
 缺点:
1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2. 策略模式造成很多的策略类。
 
下面我们结合下源码中的TabHost的实现,来介绍下源码中如何使用策略模式
如果你在本地有下载源码的话,你可以在源码中找到这个类,在我的电脑中,它的位置如下:
F:\android-sdk\sources\android-15\android\widget\TabHost.java
 
这里引用林家男孩博客中的一张图:
http://dl.iteye.com/upload/attachment/0075/8663/5c1f6861-d1ef-318e-9562-8fce4e6afc6f.jpg
这张图很好地说明了TabHost.java中的模式结构。
 
首先TabHost继承自FrameLayout,TabHost由两部分构成,一个是TabWidget,一个是TabSpace.
TabWidget用来指定和监听TabHost切换的,不是我们今天要讨论的范围。
TabSpec就是我们今天策略模式的主角了,下面对它进行分析。
 
TabHost可以看做是由一个个独立的用于点击来切换显示面板的Indicator和所对应的显示内容的面板Content组成。
也就是说一个Indicator对应一个Content,这在源码中对应着的是TabSpec这个类,换句话说:每个TabSpec对应着一个Indicator和相应的显示内容的面板Content。这两个地方也是经常需要变动的。因为可能需要定制各种各样的TabHost,比如,使用自定义的View来作为Indicator,当然显示内容的面板也需要是可以被定制的。
那么这时候我们就需要将变化的东西抽象出来。以便它可以被很方便地替换。
 
举个例子来说:
我要定制一个TabSpec,需要满足两个基本条件:
1.一个Indicator
2.一个Content
这个时候我有可能仅仅想使用文字作为Indicator,那么我用LabelIndicatorStrategy来满足我的要求,假如这个时候,我想换成图片加文字的方式来作为Indicator,怎么办呢?很简单,我可以换成LabelAndIconIndicatorStrategy类来满足要求。显示面板Content也是一样道理。
当然,实现这些需要编程技巧和java的特性,针对接口编程,多态等。这个可以去查找相关资料。
 
 
下面直接上源码:
 
TabSpec:
 
/**   * A tab has a tab indicator, content, and a tag that is used to keep   * track of it.This builder helps choose among these options.   *   * For the tab indicator, your choices are:   * 1) set a label   * 2) set a label and an icon   *   * For the tab content, your choices are:   * 1) the id of a {@link View}   * 2) a {@link TabContentFactory} that creates the {@link View} content.   * 3) an {@link Intent} that launches an {@link android.app.Activity}.   */    public class TabSpec {      private String mTag;      private IndicatorStrategy mIndicatorStrategy;      private ContentStrategy mContentStrategy;      private TabSpec(String tag) {            mTag = tag;      }      /**         * Specify a label as the tab indicator.         */      public TabSpec setIndicator(CharSequence label) {            mIndicatorStrategy = new LabelIndicatorStrategy(label);            return this;      }      /**         * Specify a label and icon as the tab indicator.         */      public TabSpec setIndicator(CharSequence label, Drawable icon) {            mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);            return this;      }      /**         * Specify a view as the tab indicator.         */      public TabSpec setIndicator(View view) {            mIndicatorStrategy = new ViewIndicatorStrategy(view);            return this;      }      /**         * Specify the id of the view that should be used as the content         * of the tab.         */      public TabSpec setContent(int viewId) {            mContentStrategy = new ViewIdContentStrategy(viewId);            return this;      }      /**         * Specify a {@link android.widget.TabHost.TabContentFactory} to use to         * create the content of the tab.         */      public TabSpec setContent(TabContentFactory contentFactory) {            mContentStrategy = new FactoryContentStrategy(mTag, contentFactory);            return this;      }      /**         * Specify an intent to use to launch an activity as the tab content.         */      public TabSpec setContent(Intent intent) {            mContentStrategy = new IntentContentStrategy(mTag, intent);            return this;      }      public String getTag() {            return mTag;      }    } Indicator接口:
IndicatorStrategy
 
/**   * Specifies what you do to create a tab indicator.   */    private static interface IndicatorStrategy {      /**         * Return the view for the indicator.         */      View createIndicatorView();    } 
Content接口:
ContentStrategy
 
/**   * Specifies what you do to manage the tab content.   */    private static interface ContentStrategy {      /**         * Return the content view.The view should may be cached locally.         */      View getContentView();      /**         * Perhaps do something when the tab associated with this content has         * been closed (i.e make it invisible, or remove it).         */      void tabClosed();    } Indicator接口实现类:
LabelIndicatorStrategy
LabelAndIconIndicatorStrategy
ViewIndicatorStrategy
 
 
/**   * How to create a tab indicator that just has a label.   */    private class LabelIndicatorStrategy implements IndicatorStrategy {      private final CharSequence mLabel;      private LabelIndicatorStrategy(CharSequence label) {            mLabel = label;      }      public View createIndicatorView() {            final Context context = getContext();            LayoutInflater inflater =                  (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);            View tabIndicator = inflater.inflate(mTabLayoutId,                  mTabWidget, // tab widget is the parent                  false); // no inflate params            final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);            tv.setText(mLabel);            if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {                // Donut apps get old color scheme                tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);                tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));            }            return tabIndicator;      }    }    /**   * How we create a tab indicator that has a label and an icon   */    private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {      private final CharSequence mLabel;      private final Drawable mIcon;      private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {            mLabel = label;            mIcon = icon;      }      public View createIndicatorView() {            final Context context = getContext();            LayoutInflater inflater =                  (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);            View tabIndicator = inflater.inflate(mTabLayoutId,                  mTabWidget, // tab widget is the parent                  false); // no inflate params            final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);            final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);            // when icon is gone by default, we're in exclusive mode            final boolean exclusive = iconView.getVisibility() == View.GONE;            final boolean bindIcon = !exclusive || TextUtils.isEmpty(mLabel);            tv.setText(mLabel);            if (bindIcon && mIcon != null) {                iconView.setImageDrawable(mIcon);                iconView.setVisibility(VISIBLE);            }            if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {                // Donut apps get old color scheme                tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);                tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));            }            return tabIndicator;      }    }    /**   * How to create a tab indicator by specifying a view.   */    private class ViewIndicatorStrategy implements IndicatorStrategy {      private final View mView;      private ViewIndicatorStrategy(View view) {            mView = view;      }      public View createIndicatorView() {            return mView;      }    } 
Content接口实现类:
ViewIdContentStrategy
FactoryContentStrategy
IntentContentStrategy
 
 
    /**   * How to create the tab content via a view id.   */    private class ViewIdContentStrategy implements ContentStrategy {      private final View mView;      private ViewIdContentStrategy(int viewId) {            mView = mTabContent.findViewById(viewId);            if (mView != null) {                mView.setVisibility(View.GONE);            } else {                throw new RuntimeException("Could not create tab content because " +                        "could not find view with id " + viewId);            }      }      public View getContentView() {            mView.setVisibility(View.VISIBLE);            return mView;      }      public void tabClosed() {            mView.setVisibility(View.GONE);      }    }    /**   * How tab content is managed using {@link TabContentFactory}.   */    private class FactoryContentStrategy implements ContentStrategy {      private View mTabContent;      private final CharSequence mTag;      private TabContentFactory mFactory;      public FactoryContentStrategy(CharSequence tag, TabContentFactory factory) {            mTag = tag;            mFactory = factory;      }      public View getContentView() {            if (mTabContent == null) {                mTabContent = mFactory.createTabContent(mTag.toString());            }            mTabContent.setVisibility(View.VISIBLE);            return mTabContent;      }      public void tabClosed() {            mTabContent.setVisibility(View.GONE);      }    }    /**   * How tab content is managed via an {@link Intent}: the content view is the   * decorview of the launched activity.   */    private class IntentContentStrategy implements ContentStrategy {      private final String mTag;      private final Intent mIntent;      private View mLaunchedView;      private IntentContentStrategy(String tag, Intent intent) {            mTag = tag;            mIntent = intent;      }      public View getContentView() {            if (mLocalActivityManager == null) {                throw new IllegalStateException("Did you forget to call 'public void setup(LocalActivityManager activityGroup)'?");            }            final Window w = mLocalActivityManager.startActivity(                  mTag, mIntent);            final View wd = w != null ? w.getDecorView() : null;            if (mLaunchedView != wd && mLaunchedView != null) {                if (mLaunchedView.getParent() != null) {                  mTabContent.removeView(mLaunchedView);                }            }            mLaunchedView = wd;            // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get            // focus if none of their children have it. They need focus to be able to            // display menu items.            //            // Replace this with something better when Bug 628886 is fixed...            //            if (mLaunchedView != null) {                mLaunchedView.setVisibility(View.VISIBLE);                mLaunchedView.setFocusableInTouchMode(true);                ((ViewGroup) mLaunchedView).setDescendantFocusability(                        FOCUS_AFTER_DESCENDANTS);            }            return mLaunchedView;      }      public void tabClosed() {            if (mLaunchedView != null) {                mLaunchedView.setVisibility(View.GONE);            }      }    } 很容易看出
针对TabSpec来说,将接口定义成内部变量:
 
private IndicatorStrategy mIndicatorStrategy;private ContentStrategy mContentStrategy; 再通过setter方法很容易地控制它的Indicator和Content。
比如:
 
/**         * Specify a label as the tab indicator.         */      public TabSpec setIndicator(CharSequence label) {            mIndicatorStrategy = new LabelIndicatorStrategy(label);            return this;      }这就是策略模式。
 
 
为了达到动态地改变类的行为的目的,我们需要将变化的部分,从不变的部分中抽象出来。由于需要实现动态替换,就需要利用面向对象的多态特性。我们需要利用接口,也就是针对接口编程。也就是说,相关的算法,需要具有同样的一个接口。本例中
LabelIndicatorStrategy,LabelAndIconIndicatorStrategy,ViewIndicatorStrategy是相关的一组算法,它们实现了Indicator接口。
 
ViewIdContentStrategy,FactoryContentStrategy,IntentContentStrategy是相关的一组算法,它们实现了Content接口。
你就可以很方便地通过多态(接口调用)来实现算法的替换。
这样一来,算法的变化就独立于使用算法的客户了,客户自己决定在什么情况下使用什么具体策略模式。
 
以上仅是个人见解,如果有不足的地方希望指出,共同进步,感谢一下参考链接。
参考链接:
http://bj007.blog.51cto.com/1701577/649063
http://bj007.blog.51cto.com/1701577/643572
http://blog.csdn.net/icrs23/article/details/1913871
 
 
 
 
 
 
页: [1]
查看完整版本: 策略模式--从源码TabHost中看策略模式