从零开始学Android架构(一)——什么是设计模式?

·  阅读 1295

前言

不少人会觉得架构师是一个高大上的岗位,只有技术顶尖的人才能胜任,但其实它并没有这么高大上,大部分的架构师,都只是开发经验非常丰富,并且热爱学习,善于知识迁移和总结。应用的架构是一件非常成熟,有非常多的经验提供我们借鉴的事情,我们可以从Android的架构中学习大型项目的架构思路,我们也可以从Android的局部中学习框架的精髓,如binder的设计,framework的设计,我们也可以深入到Android的代码细节中去看看具体的实现,看看什么样的场景使用什么的设计模式,如何写出更优雅的代码。除了Android,我们还可以从Github或者其他的开源项目或者文章中学习各种优秀的架构思路,如分层架构,微内核架构,插件化架构等等。

我会通过几篇文章,来讲解我对架构的理解,文章主要为三个主题,分别介绍什么是架构,什么是框架,什么是设计模式

架构,框架,设计模式,这三点,是从项目的整体到代码的细节粒度不断缩小的过程。架构是一个高屋建瓴的角度,除了考虑如何解决大需求,还要考虑技术方案,可扩展性,性能,安全,成本等多个维度,用建房子来比喻,架构是敲定房屋风格,建筑材料,选定施工队,画好设计图纸的流程。当这些都确定后框架就上场了,框架用来解决局部如何设计,比如我们在业务功能开发时,是采用mvp还是mvvm的框架,在访问网络时,是采用Retrofit还是Okhttp等等,一个项目,涉及到了多个框架,框架犹如建房子时确定如何建造地基,如何进行脚手架搭建等局部流程,最后,就是具体编写代码时,采用什么样的设计模式,能够让代码更加的优雅,更加的解耦,这一点就像建房子时,安装窗户,如何安装墙砖等流程。

在这篇文章,我主要先讲第一个主题:什么是设计模式,这个主题也是最容易入手的,每一个开发新手,都可以在项目开发中,使用几个设计模式,即使没用过设计模式,也在代码中看到过各种各样的设计模式。

设计模式

我们在做事情时,很多事情都是有套路的,这个套路是前人总结的经验,什么情况下按照什么套路去做,一般都能把事情做的很顺利,写代码也不例外,在写代码的过程中,很多场景都有固定的写法套路,这个套路就是设计模式,设计模式是由GOF总结出的一套开发人员在实际开发中经常面临的场景的解决方案,设计模式并不是GOF发明的,GOF只是总结了这些套路,通过采用这些套路,也就是设计模式,我们写出的代码更加可靠性,解耦,复用性更高,也更容易被其他人理解,设计模式主要分为三种类型:创建型,结构型,行为型

我们先从创建型的设计模式开始,这也是最简单的设计模式。

创建型

创建型设计模式主要用来解决如何创建对象,虽然我们可以直接通过new的方式来创建一个对象,但是很多场景下,直接通过new创建对象并不是一种很好的方式,这时,使用创建型的设计模式就能很好的帮我们解决问题。创建型的设计模式包括了单例、原型、工厂方法、抽象工厂、建造者5种,他们的主要特点都是将对象的创建与使用分离。

下面就来看一下如何使用创建型的设计模式,什么场景下使用什么样的创建型设计模式。

工厂方法和抽象工厂

笔者曾经开发过一个分享的功能,弹起分享面板后,根据点击的分享图标,比如qq,微信,微博等等,需要创建对应的分享的对象实例。如果不用设计模式,就需要在每个图标的点击事件中去创建对应类型的分享实例,伪代码如下。

wxIcon.setOnClickListener(()->{
	WXShare wxShare = new WXShare;
	wxShare.share();
})

qqIcon.setOnClickListener(()->{
	QQShare qqShare = new QQShare;
	QQShare.share();
})

wbIcon.setOnClickListener(()->{
	WBShare wxShare = new WBShare;
	wbShare.share();
})
复制代码

这样创建对象的方式有一种缺点:随着要分享的类型越来越多,需要直接创建的分享实例也越来越多,这意味这这个对象中需要打交道的对象也越来越多,这就违背了单一职责原则,分享面板的职责就是分享,创建各种类型的分享实例,不是分享面板的职责,上述的分享实例的创建还是很简单的,但是很多时候,对象的创建是很复杂的事情,需要初始化很多数据,所以直接在点击事件中创建分享的实例,不是一种比较好的方法。

上面的场景就可以用工厂方法来改造,改造步骤如下

  1. 首先定义分享接口以及实现分享接口的实体类
public interface IShare{
	void share();
}

public WXshare implements IShare{
	public void share(){
		//doSomething;
	}
}

public QQShare implements IShare{
	public void share(){
		//doSomething;
	}
}

public WBShare implements IShare{
	public void share(){
		//doSomething;
	}
}
复制代码
  1. 定义工厂类ShareFactory
public class ShareFactory{
    public IShare createShareInstance(String shareType){     
      if(shapeType.equalsIgnoreCase("WX")){
         return new WXshare();
      } else if(shapeType.equalsIgnoreCase("QQ")){
         return new QQShare();
      } else if(shapeType.equalsIgnoreCase("WB")){
         return new WBShare();
      }
      return null;
   }
}
复制代码
  1. 使用工厂类型
ShareFactory shareFactory = new ShareFactory();

wxIcon.setOnClickListener(()->{
	IShare wxShare = shareFactory.createShareInstance("WX");
	wxShare.share();
})

qqIcon.setOnClickListener(()->{
	IShare qqShare = shareFactory.createShareInstance("QQ");
	QQShare.share();
})

wbIcon.setOnClickListener(()->{
	IShare wxShare = shareFactory.createShareInstance("WB");
	wbShare.share();
})
复制代码

可以看到,使用工厂方法后,对象的创建工作就全部放在工厂方法对象中进行了,这也符合单一职责原则:对象创建,就让专门做这事的对象去做。

接下来我们在看看什么是抽象工厂,把他和工厂方法放在一起讲,是因为他们很相似,不同点是抽象工厂是由创建一种类型的对象实例,变成创建多种类型的对象实例。还是以我在上面提到了分享的例子,在开发完分享功能后,我又接了一个新的需求:接入第三方快捷登录。

在登陆界面中,当我点击qq的图标时,就进入qq的快捷登录,点击微信的图标时,就进入微信的快捷登录,点击微博的图标时,就进入微博的快捷登录。我们此时也可以使用工厂方法,为第三方登录的实例创建新建一个工厂方法,但是我们还有更好的创建方法:将登录的实例创建和分享的实例创建放在同一个工厂方法里面,这就是抽象工厂。为什么可以这样做呢?因为第三方分享和第三方登录时属于一个产品族,他们都引用了共同的第三方提供的SDK,并且这样做的好处也是为了符合单一职责原则。下面看看如何使用抽象工厂。

  1. 接着为登录创建接口以及实现类
public interface ILogin{
	void login();
}

public WXLogin implements Login{
	public void login(){
		//doSomething;
	}
}

public QQLogin implements Login{
	public void login(){
		//doSomething;
	}
}

public WBLogin implements Login{
	public void login(){
		//doSomething;
	}
}
复制代码
  1. 为分享和登录创建抽象工厂
public abstract class AbstractFactory {
   public abstract IShare createShareInstance(String type);
   public abstract ILogin createLoginInstance(String type) ;
}
复制代码
  1. 扩展抽象工厂,创建分享和登录的实体对象
public class ShareFactory extends AbstractFactory {
    
   @Override
   public IShare createShareInstance(String type){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      } else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      return null;
   }
   
   @Override
   public ILogin createLoginInstance(String type) {
      return null;
   }
}

public class LoginFactory extends AbstractFactory {
    
   @Override
   public IShare createShareInstance(String type){
      return null;
   }
   
   @Override
   public ILogin createLoginInstance(String type) {
      if(color == null){
         return null;
      }        
      if(color.equalsIgnoreCase("RED")){
         return new Red();
      } else if(color.equalsIgnoreCase("GREEN")){
         return new Green();
      } else if(color.equalsIgnoreCase("BLUE")){
         return new Blue();
      }
      return null;
   }
}
复制代码
  1. 创建一个工厂生成器,用来创建不同类型的工厂
public class FactoryProducer {
   public static AbstractFactory getFactory(String scene){
      if(scene.equalsIgnoreCase("Login")){
         return new LoginFactory();
      } else if(scene.equalsIgnoreCase("Share")){
         return new ShareFactory();
      }
      return null;
   }
}
复制代码

此时,抽象工厂就创建好了,我们只需要在登录的场景时,通过FactoryProducer获取LoginFactory,就能创建各种类型的登录对象的实例;在分享的场景时,通过FactoryProducer获取ShareFactory,就能创建各种类型的分享对象实例。

使用场景总结

下面来总结一下工厂方法和抽象工厂的使用场景

工厂方法
  1. 根据不同的条件,需要创建不同的实例时,并且创建的对象比较复杂时,就可以使用工厂方法,上面的分享例子就是这样的场景,但是上面的例子中创建对象相对简单,实际使用中,如果创建的对象很简单,只需要new就能创建,而没有复杂的初始化,我们也可以直接创建对象,使用工厂方法会增加代码量和复杂度。
抽象工厂
  1. 抽象工厂的使用场景需要满足两个场景,第一是需要满足工厂方法的使用场景,即需要根据不同的条件,创建不同的实例,同时又要满足第二个场景,即需要有多个产品族的产品要创建。

Android应用实例——LayoutInflater

下面来看一个Android系统中使用工厂方法的例子:LayoutInflater,我们都使用过LayoutInflater的inflate来创建View,他创建View的流程如下

/frameworks/base/core/java/android/view/LayoutInflater.java

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
复制代码

inflate方法中会获取XmlResourceParser,这个类是专门用来解析xml布局文件的,然后调用inflate重载方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    View result = root;

    try {
        int type;
        //循环读取xml文件,直到读取到结尾标志才跳出循环
        while ((type = parser.next()) != XmlPullParser.START_TAG &&
               type != XmlPullParser.END_DOCUMENT) {
            // Empty
        }


        final String name = parser.getName();

        //判断是否是merge标签,merge布局不许满足父view存在
        if (TAG_MERGE.equals(name)) {
            if (root == null || !attachToRoot) {
                throw new InflateException("<merge /> can be used only with a valid "
                                           + "ViewGroup root and attachToRoot=true");
            }

            //解析merge布局标签
            rInflate(parser, root, inflaterContext, attrs, false);
        } else {
            // 根据name,也就是解析出来的xml标签创建view
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);
            ……
        }

    } catch (XmlPullParserException e) {
        ……
    } catch (Exception e) {
        ……
    } finally {
        ……
    }
    return result;
}
复制代码

我们直接看createViewFromTag函数,也就是创建View的函数,我们知道View的子类非常多,如TextView,ListView,ImageView等等,那么这里面是否是用的工厂方法呢?我们来看一下里面的实现

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {
   ……

    try {
        View view;
        //调用factory来创建View
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        //没有factory创建view,则调用自身的createView方法
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {               
                    view = onCreateView(parent, name, attrs);
                } else {
                    //创建View
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
       ……
    } catch (ClassNotFoundException e) {
        ……
    } catch (Exception e) {
       ……
    }
}
复制代码

View的创建,实际不是通过工厂方法创建的,而是根据解析出来的view标签,调用反射创建的,创建函数在createView中实现,为什么不用工厂方法呢?因为View的子类太多了,用工厂方法代码会非常的庞大,所以用反射代码会更简洁一点,关于createView的实现就不再这里说了,这里主要讲讲在createViewFromTag函数中出现的工厂,那么这里的factory有什么用呢?其实这里的factory是提供给开发自定义创建View的,如果我们xml中自定义了一个标签,比如Div,Android是无法创建Div这个标签对应的View,这个时候,我们就可以开发一个能根据Div这个标签创建对应的View的factory,然后将工厂set到LayoutInflater,这样LayoutInflater就能扩展创建Div这个xml标签了视图了。

通过这个例子我们可以看到,工厂方法或者抽象工厂,不仅仅可以让对象创建更符合单一职责,实现对象创建和使用的解耦,我们同样可以利用这种设计模式,提供一种扩展的能力,比如上面的例子就通过factory提供了创建自定义标签的试图的扩展能力。

原型

笔者曾经开发过一个画板的功能,画板可以画长方形,正方形,圆形等等,在画板上画画时,选择不同的形状,就要创建对应形状的对象实例,创建实例时需要初始化很多数据,比如颜色,画笔大小等等,相当的麻烦,这样的场景,我们可以用上面的工厂方法来进行优化,但是这里我不讲工厂方法,而是另外一种设计模式:原型模式,原型模式可以用来快速创建对象,让对象创建更加高效,下面看一下具体的实现。

  1. 创建形状的抽象,这些抽象类都需要继承Cloneable接口
public abstract class Shape implements Cloneable {
   
   private String id;
   protected  String type;
   private int color;
   private int paintSize;
   
   abstract void draw();
   
   public String getType(){
      return type;
   }
    
   public String setType(String type){
       this.type = type;
   }
   
   public String getId() {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
    
   //省略其他属性初始化方法
   ……
       
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}
复制代码
  1. 扩展抽象类
public class Rectangle extends Shape {
 
   public Rectangle(){
     type = "Rectangle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square extends Shape {
 
   public Square(){
     type = "Square";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public class Circle extends Shape {
 
   public Circle(){
     type = "Circle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}
复制代码
  1. 将扩展的实体类存储在集合容器中
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();
 
   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }
 
   public static void initShapeCache() {
      Circle circle = new Circle();
      circle.setId("1");
      shapeMap.put(circle.getId(),circle);
 
      Square square = new Square();
      square.setId("2");
      shapeMap.put(square.getId(),square);
 
      Rectangle rectangle = new Rectangle();
      rectangle.setId("3");
      shapeMap.put(rectangle.getId(),rectangle);
   }
}
复制代码
  1. 此时,我们就可以在ShapeCache中获取我们图形的原型,快速的创建对应图形的对象了
public class PrototypePatternDemo {
   public static void main(String[] args) {
      ShapeCache.loadCache();
 
      Shape clonedShape = (Shape) ShapeCache.getShape("1");
      System.out.println("Shape : " + clonedShape.getType());        
 
      Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
      System.out.println("Shape : " + clonedShape2.getType());        
 
      Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
      System.out.println("Shape : " + clonedShape3.getType());        
   }
}
复制代码

同样是创建对象,上面的例子中,通过原型创建对象和通过工厂方法创建对象有什么区别呢?区别主要在于性能,原型创建对象,是在已有的对象上进行拷贝进行的,这样方式比通过new的方式新建一个对象,性能上更加高效。

使用场景

下面来总结一下原型模式的使用场景

  1. 创建对象很复杂,需要初始化很多数据和资源时,可以使用原型模式,上面提到的例子就是这样的应用场景

  2. 当对创建对象有性能要求时,可以使用原型模式,通过clone创建的对象比通过new创建的对象在性能消耗上更低

  3. 有安全性的要求时,即当一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以使用原型模式,在这种情况下,可以修改拷贝后的对象的值,而不会对原有的对象产生改变。

Android应用实例——Transition

Android中很对象都提供了clone方法,以供使用原型模式进行拷贝,这里以Transition举例子,Transition动画的初始化是比较麻烦的一件事情,需要设置动画的方式,时间,类型,插值器等等,对于这样复杂对象,Android一般都实现了clone函数,来让我们可以通过原型进行对象的创建,我们看下transition实现的clone方法。

/frameworks/support/transition/src/android/support/transition/Transition.java

@Override
public Transition clone() {
    try {
        Transition clone = (Transition) super.clone();
        clone.mAnimators = new ArrayList<>();
        clone.mStartValues = new TransitionValuesMaps();
        clone.mEndValues = new TransitionValuesMaps();
        clone.mStartValuesList = null;
        clone.mEndValuesList = null;
        return clone;
    } catch (CloneNotSupportedException e) {
        return null;
    }
}
复制代码

可以看到,transition的clone中,会初始化mAnimators,mStartValues,mEndValues等数据。我们接着看看PopupWindow通过原型,创建退出动画的例子。

public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    mContext = context;
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final TypedArray a = context.obtainStyledAttributes(
        attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
    final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
    mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
    mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);

   
    if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
        final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
        if (animStyle == R.style.Animation_PopupWindow) {
            mAnimationStyle = ANIMATION_STYLE_DEFAULT;
        } else {
            mAnimationStyle = animStyle;
        }
    } else {
        mAnimationStyle = ANIMATION_STYLE_DEFAULT;
    }

    //初始化进入动画
    final Transition enterTransition = getTransition(a.getResourceId(
        R.styleable.PopupWindow_popupEnterTransition, 0));
    final Transition exitTransition;
    if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
        exitTransition = getTransition(a.getResourceId(
            R.styleable.PopupWindow_popupExitTransition, 0));
    } else {
        //通过拷贝创建退出动画
        exitTransition = enterTransition == null ? null : enterTransition.clone();
    }

    a.recycle();

    setEnterTransition(enterTransition);
    setExitTransition(exitTransition);
    setBackgroundDrawable(bg);
}
复制代码

从上面的例子可以看到,当PopupWindow没有手动设置退出动画时,就会拷贝进入动画当作默认的退出动画。

建造者

还是以分享这个功能做例子,分享的类型非常多,可以带图片,可以带文字,可以带url连接,可以带视频,根据不同的分享场景,有不同的类型组合,当我们创建分享实例时,可以通过构造函数来初始化数据,如下所示

public class Share {
    private final String pic;
    private final String text;
    private final String url;
    private final String video;
    
    public Share(String pic){
        this.Share(pic,"","","");
    }
    
    public Share(String text){
        this.Share("",text,"","");
    }
    
    public Share(String url){
        this.Share("","",url,"");
    }
    
    public Share(String video){
        this.Share("","","",video);
    }
    
    public Share(String pic,String text){
        this.Share(pic,text,"","");
    }
    
    //省略剩余的构造函数
    ……
        
   public Share(String pic,String text,String url,String video){
        this.pic = pic;
        this.text = text;
        this.url = url;
        this.video = video;
    }
    
}
复制代码

通过上面的例子,我们会发现,如果通过构造函数初始化的数据太多,就会导致有大量重载的构造函数,而且也不方便阅读,这样的代码显然不优雅,这时我们就可以使用建造者模式来进行优化了。建造者模式的实现也比较简答,在该对象创建一个建造者的内部类就可以了。

public class Share {
    private final String pic;
    private final String text;
    private final String url;
    private final String video;

    public Share(Builder builder){
        this.pic=builder.pic;
        this.text=builder.text;
        this.url=builder.url;
        this.video=builder.video;
    }
    public static class Builder{
        private String pic;
        private String text;
        private String url;
        private String video;

        public Builder setPic(String pic) {
            this.pic = pic;
            return this;
        }
        public Builder setText(String text) {
            this.text = text;
            return this;
        }
        public Builder setUrl(String url) {
            this.url = url;
            return this;
        }        
        public Computer build(){
            return new Computer(this);
        }
    }
    
  //省略getter方法
    ……
        
}
复制代码

使用场景总结

  1. 一个对象由于场景的变化,内部有不同的组合时,可以使用建造者,比如上面的例子,根据场景的不同,分享的类型有各种各种的组合。
  2. 需要确保这个对象内部组合要按照一定的顺序初始化时,也可以使用建造者,这也是为什么很多时候一定需要使用构造函数来进行对象的初始化,而不是通过set数据的方法进行初始化,是因为调用set方法没法保证调用的顺序,有了建造模式,我们就可以即可以通过set函数来初始化数据,又要保证数据初始化的顺序。

Android应用实例——AlertDialog

我们常用的AlertDialog就是一个建造者模式,下面来看看Dialog是如何使用建造者模式的,先看AlertDialog的内部类:Builder的实现

/frameworks/base/core/java/android/app/AlertDialog.java

public static class Builder {
    private final AlertController.AlertParams P;

    public Builder(Context context) {
        this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
    }

    public Builder(Context context, int themeResId) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
            context, resolveDialogTheme(context, themeResId)));
    }

    public Context getContext() {
        return P.mContext;
    }

    public Builder setTitle(@StringRes int titleId) {
        P.mTitle = P.mContext.getText(titleId);
        return this;
    }


    public Builder setTitle(CharSequence title) {
        P.mTitle = title;
        return this;
    }

    public Builder setMessage(@StringRes int messageId) {
        P.mMessage = P.mContext.getText(messageId);
        return this;
    }


    public Builder setMessage(CharSequence message) {
        P.mMessage = message;
        return this;
    }


    public Builder setIcon(@DrawableRes int iconId) {
        P.mIconId = iconId;
        return this;
    }


    public Builder setIcon(Drawable icon) {
        P.mIcon = icon;
        return this;
    }

    //省略剩余的set方法
	……


    public Builder setView(int layoutResId) {
        P.mView = null;
        P.mViewLayoutResId = layoutResId;
        P.mViewSpacingSpecified = false;
        return this;
    }


    public Builder setView(View view) {
        P.mView = view;
        P.mViewLayoutResId = 0;
        P.mViewSpacingSpecified = false;
        return this;
    }

    public AlertDialog create() {
        // Context has already been wrapped with the appropriate theme.
        final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
        P.apply(dialog.mAlert);
        dialog.setCancelable(P.mCancelable);
        if (P.mCancelable) {
            dialog.setCanceledOnTouchOutside(true);
        }
        dialog.setOnCancelListener(P.mOnCancelListener);
        dialog.setOnDismissListener(P.mOnDismissListener);
        if (P.mOnKeyListener != null) {
            dialog.setOnKeyListener(P.mOnKeyListener);
        }
        return dialog;
    }

    public AlertDialog show() {
        final AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }
}
复制代码

可以看到这个Builder的内部有大量的set方法,用来设置这个Dialog的属性,每个set方法,返回值都是this,但是和前面的例子不一样的是,AlertDialog通过Builder设置的属性,全部都封装在了AlertController.AlertParams中,这样做一是为了满足单一职责原则,而是AlertController.AlertParams可以让其他Dialog的Builder复用。当通过Builder的set初始化完成AlertDialog后,再接着调用show()函数,这时,一个dialog就会出现了。

我们接着看看create()函数中调用AlertController.AlertParams的apply函数进行Dialog初始化的流程。

/frameworks/base/core/java/com/android/internal/app/AlertController.java

public void apply(AlertController dialog) {
    if (mCustomTitleView != null) {
        dialog.setCustomTitle(mCustomTitleView);
    } else {
        if (mTitle != null) {
            dialog.setTitle(mTitle);
        }
        if (mIcon != null) {
            dialog.setIcon(mIcon);
        }
        if (mIconId != 0) {
            dialog.setIcon(mIconId);
        }
        if (mIconAttrId != 0) {
            dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
        }
    }
    if (mMessage != null) {
        dialog.setMessage(mMessage);
    }
    if (mPositiveButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                         mPositiveButtonListener, null);
    }
    if (mNegativeButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                         mNegativeButtonListener, null);
    }
    if (mNeutralButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                         mNeutralButtonListener, null);
    }
    if (mForceInverseBackground) {
        dialog.setInverseBackgroundForced(true);
    }

    if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
        createListView(dialog);
    }
    if (mView != null) {
        if (mViewSpacingSpecified) {
            dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                           mViewSpacingBottom);
        } else {
            dialog.setView(mView);
        }
    } else if (mViewLayoutResId != 0) {
        dialog.setView(mViewLayoutResId);
    }

}
复制代码

可以看到,一个复杂的AlertDialog对象此时就通过Builder创建出来了。

单例

单例放在建造型设计模式最后一个讲,是因为这个设计模式我们使用的太频繁了,但凡一个开发者,都应该对这个单例模式有了解,所以这里就不讲他如何使用了,我这要介绍一下在多线程或者单线程中单例的使用。

单例模式主要分为懒汉式和饿汉式,懒汉式单例模式就是用的时候才进行实例初始化,饿汉式就是在申明对象时或者在静态代码块中提前进行实例初始化。

懒汉式

先看看懒汉式单例在非线程安全下的写法

public class SingletonLazy {
    private static SingletonLazy mInstance;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstanceUnLocked() {
        if (mInstance == null) {
            mInstance = new SingletonLazy();
        }
        return mInstance;
    }
}
复制代码

在看看懒汉式要保证线程安全的写法

public class SingletonLazy {
    private volatile static SingletonLazy mInstance;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        if (mInstance == null) {//第一次检查
            synchronized (SingletonLazy.class) {//加锁
                if (mInstance == null) {//第二次次检查
                    mInstance = new SingletonLazy();//new 一个对象
                }
            }
        }
        return mInstance;
    }
 }
复制代码

懒汉式单例要保证单例,需要进行双重检查,并且mInstance的声明要用voliate申明,为什么需要这么做,涉及到了多线程方面的知识,这一块需要很大的篇幅才能讲完,可以参考我的这篇文章《掌握Android和Java线程原理》

饿汉式

懒汉式单例的写法如下:

public class SingletonHungry {

    private static SingletonHungry mInstance = new SingletonHungry();

    private SingletonHungry() {
    }

    public static SingletonHungry getInstance() {
        return mInstance;
    }
 }
复制代码

懒汉式是能够保证线程安全的。懒汉式和饿汉式,并没有哪一种更好,而是要根据业务场景,选择最合适的,如果使用单例的场景并没有多线程,使用懒汉式的非线程安全就可以了,这种写法代码简洁易懂,如果需要保证线程安全,那么判断这个对象是不是一定有使用到,如果一定使用到,那么用饿汉式就可以了,如果不一定,那么就使用懒汉式的线程安全写法。

Android实例场景——ProcessState

Android系统中的进程,都是通过Zygote fork出来,fork完进程后,都会执行onZygoteInit的回调,在回调中,会调用ProcessState为这个进程打开和初始化Binder,ProcessState就是一个单例的。

先看看onZygoteInit函数的实现。

/frameworks/base/cmds/app_process/app_main.cpp

virtual void onZygoteInit() {
    sp<ProcessState> proc = ProcessState::self();
    proc->startThreadPool(); 
}
复制代码

我们在看看 ProcessState::self()函数中做的事情

/frameworks/native/libs/binder/ProcessState.cpp

sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }
    gProcess = new ProcessState("/dev/binder");
    return gProcess;
}
复制代码

可以看到,ProcessState是一个非线程安全的懒汉式单例,关于Binder的创建和通信原理,在这里就不详细说了,有兴趣的可以看看我的这篇文章《深入理解Binder通信原理》

结构型

了解了创建型的设计模式,是不是对设计模式有了一点感觉了,创建型的设计模式还是比较简单的,只需要我们在new一个对象的时候,考虑当前场景是否可以用创建型设计模式来进行改进和优化就行了,相比而言,结构型的设计模式就是属于进阶的了,他需要我们将多个对象进行组合,组织成更大的结构,并能获得新的功能,结构性的设计模式主要有代理、适配器、桥接、装饰、外观、享元、组合这7种,下面就详细了解一下。

代理和装饰器

将代理和装饰器放在一起讲,是因为这两种模式比较类似,刚接触这两种设计模式可能都会弄混。

先说说代理模式,什么是代理模式呢?Client对象想要访问A对象,如果使用代理,那么Client便不能直接访问A对象,而是访问A对象的代理P对象,此时Client直接访问代理B对象,B对象再访问A对象,所以Client是完全不知道A对象的存在的,只需要访问B对象就能访问到A对象的全部功能。

在Android应用开发中,经常会使用到代理模式,使用代理有什么好处呢?先举个例子,笔者曾对一个项目做性能分析,需要知道某些方法的耗时,虽然我可以直接在这些方法中进行统计,但是违反了单一职责原则,并且有些方法是第三方sdk的对象提供的方法,我无法直接修改源码,这个时候,就可以使用代理模式,代理模式的一个最主要的作用就是扩展被代理对象的功能。这里我继续以分享为例子讲解如何使用代理模式统计分享对象中的方法耗时。

  1. 创建被访问对象的接口
public interface Share{
    void shareImg();
    void shareText();
    void shareUrl();
}
复制代码
  1. 创建接口的实体类
public class QQShare implements Share{
    
    @Override
    public void shareImg(){
        //do something
    };
    
    @Override
    public void shareText(){
        //do something
    };
    
    @Override
    public void shareUrl(){
        //do something
    };
}
复制代码
  1. 如果我想统计每个分享方法的耗时,虽然可以直接在QQShare这个对象的方法里插入耗时统计代码,但是这样并不规范,统计功能并不属于分享能力的职责,这些写对代码侵入性高,如果QQShare类是封装在sdk里面的,我们也没办法在里面插入代码,这个时候,代理模式就能派上用场了。接下来创建一个代理对象,代理对象需要实现被代理的对象接口
public class QQShareProxy implements Share{
   private Share qqShare;
 
   public ShareProxy(){
       //代理对象内部创建被代理对象的实例
      qqShare = new QQShare();
   }
 
    @Override
    public void shareImg(){
        long startTime = System.currentTime;
        qqShare.shareImg();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareText(){
        long startTime = System.currentTime;
        qqShare.shareText();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareUrl(){
        long startTime = System.currentTime;
        qqShare.shareUrl();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
}
复制代码

此时,我们直接使用QQShareProxy,就可以统计每个方法的耗时的,当我们使用QQShareProxy时,我们对QQShare是无感知的,因为在Proxy的内部会创建对应的QQShare实例。

装饰器模式的作用也是扩展对象的功能,所以我们同样可以使用装饰器来为QQShare这个对象扩展耗时统计,那么他和代理模式的区别是什么呢?他们最大的区别在于代理模式对代理的对象是无感知的,我们不需要知道被代理的对象的存在,但是装饰器对被装饰的对象是感知的。下面通过代码来看看装饰器模式的实现。

public class ShareDecorate implements Share{
   private Share share;
 
   public ShareDecorate(Share share){
      this.share = share;
   }
 
    @Override
    public void shareImg(){
        long startTime = System.currentTime;
        qqShare.shareImg();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareText(){
        long startTime = System.currentTime;
        qqShare.shareText();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
    
    @Override
    public void shareUrl(){
        long startTime = System.currentTime;
        qqShare.shareUrl();
        Log.i(TAG,"COST:"+(System.currentTime - startTime));
    };
}
复制代码

可以看到,装饰器模式的构造函数中,需要我们传入被装饰的对象,这样的好处是,我们不仅可以为QQShare实例扩展耗时统计的能力,还能传入其他的分享对象,为WXShare,WBShare等所有其他的分享对象扩展统计能力,但是如果使用代理模式就没办法了,因为一个代理模式,只能唯一代理一个对象。

使用场景总结

看完了上面的例子,我们再来总结一下这两种模式的使用场景。

代理模式
  1. 和上面例子的作用一样,代理模式可以扩展对象的功能
  2. 当需要对访问做鉴权操作时,也可以使用代理模式
装饰器模式
  1. 装饰器的主要作用,就是扩展对象的能力

Android实例场景——binder

当我们调用startActivity启动一个Activity时,我们实际调用的是AMS的代理,通过AMS的Proxy代理的startActivity方法,我们就是启动一个activity,完全不需要知道AMS的存在,下面看一下通过代理启动一个Activiyt的流程。

startActivity函数最终都会调用startActivityForResult函数

/frameworks/base/core/java/android/app/Activity.java

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
                                   @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        //启动activity
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            mStartedActivity = true;
        }

        cancelInputsAndStartExitTransition(options);
    } else {
       ……
    }
}
复制代码

startActivityForResult函数中实际是调用了Instrumentation对象的execStartActivity方法。

/frameworks/base/core/java/android/app/Instrumentation.java

public ActivityResult execStartActivity(
    Context who, IBinder contextThread, IBinder token, Activity target,
    Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    Uri referrer = target != null ? target.onProvideReferrer() : null;
    if (referrer != null) {
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);
    }
    ……
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                           intent.resolveTypeIfNeeded(who.getContentResolver()),
                           token, target != null ? target.mEmbeddedID : null,
                           requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}
复制代码

execStartActivity函数关键的代码就是ActivityManager.getService() .startActivity,通过这一行代码,就能调用AMS启动activity,这一行代码由三部分组成。

  1. 获取ServiceManager的Binder地址,并根据获取到的Binder地址创建ServiceManager在应用进程的Proxy
  2. 获取ActivityManagerService的Binder地址,更根据AMS的Binder地址创建AMS在应用进程的Proxy
  3. 通知ActivityManagerService启动activity。

由于上面的流程设计到了Binder的通信过程,要详细讲解Binder是一个很长的流程,想要对Binder有更深入理解的可以看看我的这篇文章《深入理解Binder原理》,这里我直接讲AMS在用户进程的Proxy,他是由IActivityManager这个AIDL文件编译时自动生成的

/frameworks/base/core/java/android/app/IActivityManager.aidl

interface IActivityManager {
    ……
    int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent,
                      in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
                          int flags, in ProfilerInfo profilerInfo, in Bundle options);
    ……
}
复制代码

编译后生成的proxy文件如下

public interface IActivityManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements android.app.IActivityManager {
        private static final java.lang.String DESCRIPTOR = "android.app.IActivityManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an android.app.IActivityManager interface,
         * generating a proxy if needed.
         */
        public static android.app.IActivityManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof android.app.IActivityManager))) {
                return ((android.app.IActivityManager) iin);
            }
            return new android.app.IActivityManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: ……
                case TRANSACTION_openContentUri: ……
                case TRANSACTION_handleApplicationCrash: ……
                case TRANSACTION_startActivity: {
                    data.enforceInterface(DESCRIPTOR);
                    android.app.IApplicationThread _arg0;
                    _arg0 = android.app.IApplicationThread.Stub.asInterface(data.readStrongBinder());
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    android.content.Intent _arg2;
                    if ((0 != data.readInt())) {
                        _arg2 = android.content.Intent.CREATOR.createFromParcel(data);
                    } else {
                        _arg2 = null;
                    }
                    java.lang.String _arg3;
                    _arg3 = data.readString();
                    android.os.IBinder _arg4;
                    _arg4 = data.readStrongBinder();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    int _arg6;
                    _arg6 = data.readInt();
                    int _arg7;
                    _arg7 = data.readInt();
                    android.app.ProfilerInfo _arg8;
                    if ((0 != data.readInt())) {
                        _arg8 = android.app.ProfilerInfo.CREATOR.createFromParcel(data);
                    } else {
                        _arg8 = null;
                    }
                    android.os.Bundle _arg9;
                    if ((0 != data.readInt())) {
                        _arg9 = android.os.Bundle.CREATOR.createFromParcel(data);
                    } else {
                        _arg9 = null;
                    }
                    int _result = this.startActivity(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8, _arg9);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }

                private static class Proxy implements android.app.IActivityManager {
                    private android.os.IBinder mRemote;

                    Proxy(android.os.IBinder remote) {
                        mRemote = remote;
                    }

                    @Override
                    public int startActivity(android.app.IApplicationThread caller, java.lang.String callingPackage, android.content.Intent intent, java.lang.String resolvedType, android.os.IBinder resultTo, java.lang.String resultWho, int requestCode, int flags, android.app.ProfilerInfo profilerInfo, android.os.Bundle options) throws android.os.RemoteException {
                        android.os.Parcel _data = android.os.Parcel.obtain();
                        android.os.Parcel _reply = android.os.Parcel.obtain();
                        int _result;
                        try {
                            _data.writeInterfaceToken(DESCRIPTOR);
                            _data.writeStrongBinder((((caller != null)) ? (caller.asBinder()) : (null)));
                            _data.writeString(callingPackage);
                            if ((intent != null)) {
                                _data.writeInt(1);
                                intent.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            _data.writeString(resolvedType);
                            _data.writeStrongBinder(resultTo);
                            _data.writeString(resultWho);
                            _data.writeInt(requestCode);
                            _data.writeInt(flags);
                            if ((profilerInfo != null)) {
                                _data.writeInt(1);
                                profilerInfo.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            if ((options != null)) {
                                _data.writeInt(1);
                                options.writeToParcel(_data, 0);
                            } else {
                                _data.writeInt(0);
                            }
                            mRemote.transact(Stub.TRANSACTION_startActivity, _data, _reply, 0);
                            _reply.readException();
                            _result = _reply.readInt();
                        } finally {
                            _reply.recycle();
                            _data.recycle();
                        }
                        return _result;
                    }
                }
            }
        }
    }
}
复制代码

可以看到,里面的内部类Proxy就是AMS的代理对象

private static class Proxy implements android.app.IActivityManager {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int startActivity(android.app.IApplicationThread caller, java.lang.String callingPackage, android.content.Intent intent, java.lang.String resolvedType, android.os.IBinder resultTo, java.lang.String resultWho, int requestCode, int flags, android.app.ProfilerInfo profilerInfo, android.os.Bundle options) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeStrongBinder((((caller != null)) ? (caller.asBinder()) : (null)));
            _data.writeString(callingPackage);
            if ((intent != null)) {
                _data.writeInt(1);
                intent.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            _data.writeString(resolvedType);
            _data.writeStrongBinder(resultTo);
            _data.writeString(resultWho);
            _data.writeInt(requestCode);
            _data.writeInt(flags);
            if ((profilerInfo != null)) {
                _data.writeInt(1);
                profilerInfo.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            if ((options != null)) {
                _data.writeInt(1);
                options.writeToParcel(_data, 0);
            } else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_startActivity, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}
复制代码

调用这个Proxy的startActivty方法,就相当于调用了AMS的startActivity方法,因为这个Proxy的内部,最终会通过Binder去调用AMS的startActivity方法。

外观

外观模式比较简单,即当我们要和多个对象打交道时,可以将多个类封装到一个类里面,这个类就被称为外观,这时我们就只需要和这一个类打交道就行了。

还是以前面一直提到的分享为例子,在分享场景中,我们需要和多个分享的对象打交道,如QQ分享,微信分享,微博分享等等,比较繁琐,使用外观模式,可以将所以的分享封装到一个类里面,这样我们只需要使用一个对象,就是调用所有驱动的分享能力,伪代码如下。

  1. 创建接口
public interface Share{
    void shareImg();
    void shareText();
    void shareUrl();
}
复制代码
  1. 创建接口的实体类
public class QQShare implements Share{
    
    @Override
    public void shareImg(){
        //do something
    };
    
    @Override
    public void shareText(){
        //do something
    };
    
    @Override
    public void shareUrl(){
        //do something
    };
}

public class WXShare implements Share{
    
    @Override
    public void shareImg(){
        //do something
    };
    
    @Override
    public void shareText(){
        //do something
    };
    
    @Override
    public void shareUrl(){
        //do something
    };
}

public class WBShare implements Share{
    
    @Override
    public void shareImg(){
        //do something
    };
    
    @Override
    public void shareText(){
        //do something
    };
    
    @Override
    public void shareUrl(){
        //do something
    };
}
复制代码
  1. 创建外观类
public class ShareFacade{
    private Share qqShare;
    private Share wxShare;
    private Share wbShare;
    
    public ShareFacade(){
        qqShare = new QQShare();
        wxShare = new WXShare();
        wbShare = new WBShare();
    }

    public void shareQQImg(){
        qqShare.shareImg();
    };
    
    public void shareQQText(){
        qqShare.shareText();
    };
    
    public void shareQQUrl(){
        qqShare.shareUrl();
    };
    
    public void shareWXImg(){
        wxShare.shareImg();
    };
    
    public void shareWXText(){
        wxShare.shareText();
    };   

    public void shareWXUrl(){
        wxShare.shareUrl();
    };
    
    public void shareWBImg(){
        wbShare.shareImg();
    };

    public void shareWBText(){
        wbShare.shareText();
    };   

    public void shareWBUrl(){
       wbShare.shareUrl();
    };    
}
复制代码

Android应用实例——Context

在Android中用到Context的地方非常的多,为什么呢?因为Context里面封装了很多Framework层的Service,可以供我们直接调用,比如调用startActivity就能启动activity,调用startService就能启动service,Context里面封装了ActivityManagerService,PackageManager,AssetManager等等各种各样的对象实例,简直就是一个大杂烩,而这也就是外观模式的精髓所在。

在这里插入图片描述

我们直接看一下Context的源码,它是一个抽象类

/frameworks/base/core/java/android/content/Context.java

public abstract class Context {
    public abstract AssetManager getAssets();
    public abstract Resources getResources();
    public abstract PackageManager getPackageManager();
    public abstract ContentResolver getContentResolver();
    public abstract Looper getMainLooper();
    ……
}
复制代码

可以看到,Context中封装了各种各样的获取某个对象方法,并且Context的子类ContextWrapper中实现所有的get方法,我们直接调用get方法就能获取到我们想要打交道的对象。

使用场景总结

  1. 当某个系统非常的复杂,对像非常多的时候,外观模式就可以派上用场了,外观模式主要作用是为复杂的模块或子系统提供外界访问的模块。

适配器

从适配器这个名字,我们就可以知道,它的作用是适配两个并不兼容的东西,最熟悉的例子就是我们的插头,如果我们去外国旅游,国内的充电插头是没法直接使用的,所以我们就需要一个插头转换器,将我们的插头转换成兼容国外的插头。同样的道理,适配器模式就是将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的两个类可以一起工作

下面是举例时刻,笔者曾经开发过一个导航的功能,输入两个地点,获取经纬度,然后寻找最佳路线,最开始,我使用的是手机自带的默认的地图接口,伪代码如下。

  1. 创建默认的地图接口
public interface IMap{
    //获取经度
    public float getLatitude(String location);
    //获取纬度
    public float getLongitude(String location);
}
复制代码
  1. 系统自带的原生的地图实现了该接口
public class DefaultMap implements IMap{
    @Override
    public float getLatitude(String location){
		//根据地名获取经度  
        ……
    }
    
    @Override
    public float getLongitude(String location){
        //根据地名获取纬度
        ……
    }
}
复制代码

3.创建调用地图能力实现路线规划的对象

public class MapUtil{
    private IMap map;
    public MapUtil(IMap map){
        this.map = map;
    }
    
    public void linePlan(String locationA,String locationB){
        flong latA = map.getLatitude();
        float lngA = map.getLongitude();
        flong latB = map.getLatitude();
        float lngB = map.getLongitude();
        //拿到了两个地方的经纬度,接下来就是路线规划的逻辑,这里就省略了
        ……
    }
}
复制代码

此时,Client就可以通过MapUtil,来实现路线规划的功能了

public class Client {
   public static void main(String[] args) {
       MapUtil mapUtil = new MapUtil(new DefaultMap());
       mapUtil.linePlan("家","公司");
   }
}
复制代码

过了一段时间,因为手机自带的地图经常会出现定位不准的情况,所以需要改成百度地图,这个时候问题就来,因为百度地图并不是继承自IMap,而MapUtil的构造函数需要传入IMap,所以上面这套代码就不可用了,由于百度地图是第三方的SDK,不能修改源码,所以就只能修改MapUtil,但是我们知道代码设计有一个原则,就是对修改关闭,对扩展开放,直接修改MapUtil,显然不满足该原则,这个时候,适配器模式就派上用场了。

此时我们只需要对百度地图设计一个适配器即可

public class MapAdapter implements IMap {
 
   BaiduMap map;
 
   public MediaAdapter(BaiduMap map){
      this.map = map;
   }
 
   @Override
    public float getLatitude(String location){
		//根据地名获取经度 
        return map.getLat();
    }
    
    @Override
    public float getLongitude(String location){
        //根据地名获取纬度
        return map.getLng();
    }
}
复制代码

有了这个适配器后,我们就可以在不修改MapUtil的情况下,使用百度的地图了,同样,如果想要继续使用其他的第三方地图,如高德,也只需要扩展适配器就行了。

public class Client {
   public static void main(String[] args) {
       MapUtil mapUtil = new MapUtil(new MapAdapter(new BaiduMap());
       mapUtil.linePlan("家","公司");
   }
}
复制代码

使用场景总结

  1. 适配器的主要使用场景,就是兼容两个接口不匹配的对象,在代码的开发之初,我们是使用不上适配的,只有代码开发完成后,或者使用第三方的SDK时,发现接口不兼容,我们就可以使用适配器模式了。

Android应用实例——Adapter

Android中使用适配器模式的地方挺多的,这里以我们最熟悉的ListView的Adapter为例,这里为什么需要使用适配器呢?我们使用的ListView组件是Android系统设计之初就已经开发好了的,就像国外的三孔排插一样,设计之初就只能使用指定的三孔的插头,ListView也是一样,设计之初它有固定的使用方法,比如必须设置每一个Item的样式,设置总Item的数量,显示的效果也有固定的使用方法。

作为在Android系统上进行开发的人员,想要将我们自己的View在ListView这个控件上显示出来,我们是没有办法,或者很复杂的,就像我们拿着国内的二孔插头,是没法直接使用国外的三孔插头,Adapter就是将我们自己的View能够在ListView上显示的适配器。

Adapter中定义好了使用方法,我们只需要按照Adapter中定义好的方法,填充我们自己的View以及实现响应的方法,然后将Adpater设置到ListView,就能完美的将我们自己的View适配到ListView中了。

public class MyAdapter extends BaseAdapter{

    private LayoutInflater mInflater;
    List<String> mDatas ; 

    public MyAdapter(Context context, List<String> datas){
        this.mInflater = LayoutInflater.from(context);
        mDatas = datas ;
    }
    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public String getItem(int pos) {
        return mDatas.get(pos);
    }

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

    // 解析、设置、缓存convertView以及相关内容
    @Override
    public View getView(int position, View convertView, ViewGroup parent) { 
        ViewHolder holder = null;
        // Item View的复用
        if (convertView == null) {
            holder = new ViewHolder();  
            convertView = mInflater.inflate(R.layout.my_listview_item, null);
            // 获取title
            holder.title = (TextView)convertView.findViewById(R.id.title);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.title.setText(mDatas.get(position));
        return convertView;
    }

}
复制代码

实际上,如果我们要开发SDK或者开发提供给其他开发者使用的平台,适配器是最常见的,因为我们并不能预见使用我们这套SDK或者系统的开发人员需要传入什么样的对象,所以我们就需要设计好适配器,这样不管他们想传什么样的对象,都能用用适配器适配到我们自己的系统中。

桥接

桥接模式的作用是将对象接口的抽象与实现解耦,这个概念有点不好懂,通俗的讲,就是我们定义好的某个对象的接口抽象,但是实现这个接口却是另外的对象,这样做有什么好处呢?

这里以在前面讲原型时提到的画板这个功能为例子来讲解桥接模式。画板可以画各种形状,圆形,矩形,菱形等等,这些形状又有各种各样的颜色,白色,黑色,红色等等。如图所示。

在这里插入图片描述

如果我们按照一种特性的形状创建一个对象,会扩展出发出非常多的类,这个时候,桥接模式就排上用场,下面看看如何使用。

  1. 创建Shape的抽象类
public abstract class Shape {
   protected Color color;
   protected Shape(Color color){
      this.color = color;
   }
   public abstract void draw();  
}
复制代码
  1. 创建颜色的接口
public interface ColorDraw {
   public void draw(String type);
}
复制代码
  1. 实现Shape抽象类实体
public class Circle extends Shape {
   private ColorDraw colorDraw;
 
   public Circle(ColorDraw colorDraw) {
      super(colorDraw);
      this.colorDraw = colorDraw;
   }
 
   @Override
   public void draw() {
      colorDraw.draw("Circle");
   }
}

public class Rectangle extends Shape {
   private ColorDraw colorDraw;
 
   public Rectangle(ColorDraw colorDraw) {
      super(colorDraw);
      this.colorDraw = colorDraw;
   }
 
   @Override
   public void draw() {
      colorDraw.draw("Rectangle");
   }
}
复制代码
  1. 创建ColorDraw接口的实体桥接实现类
public class RedDraw implements ColorDraw {
    
   @Override
   public void draw(String type) {
      System.out.println("Drawing Green "+type);
   }
}

public class BlackDraw implements ColorDraw {
    
   @Override
   public void draw(String type) {
      System.out.println("Drawing black "+type);
   }
}
复制代码

这个时候,桥接模式就涉及好了,此时,客户端只需要选择一个形状,再配合一种颜色,得到想要绘制的形状,而不用直接将绘制颜色的功能和形状都整合在一个对象里面。

Shape shape = new Circle(new RedDraw())
shape.darw();
复制代码

通过上面的例子,我们在看看桥接模式的概念:桥接模式就是将抽象部分与实现部分分离,使它们都可以独立的变化,是不是就更容易理解了呢?

使用场景总结

下面说一下桥接模式的使用场景

  • 一个类存在两个或以上的独立维度的变化,且这些维度都需要进行拓展的,可以通过桥接模式,来达到解耦和组合的目的。
  • 不希望使用继承或因为多层次继承导致类的个数急剧增加时。
  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,可以通过桥接模式使他们在抽象层建立一个关联关系。

Android应用实例——View

View的子类非常多,TextView,Button,ListView……,这些View也能用多种方式绘制出来,如Skia,OpenGl,Vulkan等,如果我们不讲View的绘制的实现和定义分离,就会像上面的例子一样,扩展出非常多的对象。

在这里插入图片描述

View在绘制中,实际上是将它的实现封装到了Canvas对象里面,根据Skia绘制有Skia的Canvas,OpenGl绘制有OpenGl对应的Canvas,由于绘制这一块太复杂了,就不再这里详细展开了,有兴趣的可以看看我的这篇文章《掌握Android图像显示原理》

组合

组合模式的使用场景非常有限,一般只有在构建Dom树,View树时才使用组合模式,所以我在这里就直接讲解Android中使用的组合模式的场景:ViewGroup,让大家了解一下这个模式即可,因为需要使用到组合模式的时候,一般都自然能想到组合模式,不然的话就可能无法完成改场景的功能开发。

Android应用实例——ViewGroup

下面看看一个组合模式的使用方法

  1. 创建基本元素对象
public class View{
	……
}
复制代码
  1. 创建容器对象,这个容器对象继承自基本元素
public abstract class ViewGroup extends View{
    
    //添加一个试图
    public void addView( View view){
        ……
    }
    
    //删除一个视图
    public void removeView( View view) {
        
    }
    
    //获取某个子View
    public View getChildAt(int index) {
        ……
    }
    
}
复制代码

这两步就是组合模式的使用方法,组合模式主要用在具有树形结构的系统上,通过使用组合模式,我们可以自由增加该系统的节点,并且简化高层模块对低层模块的调用,比如ViewGroup就是一个树型结构,ViewGroup有子View,子View也可以有子View,并且我们可以通过getChildAt访问某个子View,也就是简化了高层模块对低层模块的调用。

享元

享元模式是一个非常简单的模式,通俗的讲,就是将对象进行缓存,用的时候直接使用,不用重新创建,提高性能,比如我们最常用的LruCache,也可以理解成享元,因为这个设计模式太简单也太常见,所以在这儿就不展开说了。

结尾

到这里,创建型和结构型总共十二种设计模式就讲完了,剩下的就是行为型的十一种设计模式,在下一篇文章会接着讲。

看完这篇文章,也许会有人会说我讲的很多模式和其他地方讲的实现方式不一样,我只能说,确实是会有一些区别,比如很多地方讲建造者模式,都会有一个导演类,比如文章中Android实例场景中的例子,看起来好像都没有严格按照它对应的设计模式使用,实际上,设计模式的使用,并没有说一定要严格按照GOF的规范去实现,我们也可以从Android的源码中看到,它里面各种设计模式的使用,也都没严格按照GOF的UML中的规范去使用,设计模式的具体实现并不是绝对的,关键还是理解模式里面包含的解决问题的思想,我们不需要记住设计模式的UML类图,不需要记住使用某种设计模式的步骤一,步骤二,但是我们需要理解为什么要这样使用,这样使用的好处是什么,以及什么样的场景,用什么样的方式解决问题最优雅。

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改