网络科技

    今日:462| 主题:245747
收藏本版
互联网、科技极客的综合动态。

[其他] 启航 - 设计模式与 Android 源码 (篇一)

[复制链接]
华丽的不敢触碰 发表于 2016-11-28 07:25:11
51 9

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
试图看懂 --> 试图模仿 --> 试图记住 --> 试图熟练
  对于设计模式这是牛人们对代码中的一定场景而进行提炼的结果, 对于一个进阶的开发人员这是一个必不可少的技能. 当代码越写越好, 更易扩展更加灵活. 这对于Coder来说是最酷的事情.
   通过 设计模式 和 Android源码 中的关系, 可以更加清楚的记住各个模式的特点, 和源码中的实现方式. 多练多分析之间的关系 这是必不可少的一步!
   本篇就是 一书的缩减版. 这本书挺不错的. 片中类图讲解出自 <大话设计模式>
  
       
  • 灵活之路 - 面向对象六大原则   
  • 启航之路 - UML类图说明   
  •   发现之路 - 23种设计模式
           
    • 单例模式 Singleton
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
            
    • 建造者模式 Builder
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
      • 实战场景      
            
    • 原型模式 Prototype
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
      • 实战场景      
            
    • 工厂方法模式 Factory
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
      • 实战场景      
            
    • 抽象工厂模式 Abstract Factory
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
            
    • 策略模式 Strategy
              
      • 模式介绍      
      • 模式范例      
      • Android源码模式实现      
      • 实战场景      
           
    灵活之路 - 面向对象六大原则

       
  • 单一职责原则SRP(Single Responsibility Principle)
           
    • 定义 : 就一个类而言, 应该仅有一个引起它的变化的原因. 通俗点 就是一个类应该是相关性很高数据封装     
    • 举例 : 现在有一个图片加载类. 但是这个类内部包含了 图片下载的逻辑 , 图片缓存的逻辑 这样就使得这个类的职责过多, 随着功能的不断完善, 这个类的代码和逻辑也变得纵横交错混合在了一起. 对于后续的修改维护扩展都是不利的. 所以让两个类组合起来, 一个类内部只负责 图片下载 ,另一个类内部负责 图片缓存 . 保持每个类的 单一职责   
       
  • 开闭原则OCP(Open Close Principle)
           
    • 定义 : 软件中的对象应该对于 扩展开放 的 但是对于 修改封闭 的. 通俗点 : 尽量通过 扩展的方式 来实现变化, 而不是通过修改已有的代码来实现.     
    • 举例 : 此时我们实现了一个 双缓存类单缓存类 . 在 图片加载类 中进行这两个缓存类的实例. 并对外暴露一个布尔值让用户设置是否使用双缓存来决定内部缓存的逻辑. ok. 目前看可能没有问题. 但是如果有一个更好的缓存算法类, 这时候每次都需要在 图片加载类中修改代码 . 这就违反了 OCP 原则, 利用 继承,接口 的特性可以让此类问题得以解决. 比如: 我们可以定义一个 缓存接口 , 在 加载类 中使用的个这个接口中的方法. 而这个接口的具体实现通过暴露一个方法让外部调用的时候传入, 以后如果有新的缓存类只需要调用方法传入接口的子类就可以. 这样就对于原始代码修改进行了关闭, 而对于扩展是开放的.   
       
  • 里氏替换原则LSP(Liskov Substitution Principle)
           
    • 定义 : 所有引用基类的地方必须能透明地使用其子类. 通俗点 :是基于继承,多态两大特性. 再简单点 抽象     
    • 举例 : Window#show(View view) 这个方法接收一个 View , 但是我们可以 Button , TextView 等等. 其实很简单. 我们常用只不过不知道这个名字而已. 所以 LSP 的原则的核心就是 抽象 . 抽象又依赖于继承这个特性. 通常开闭原则和里氏替换是不离不弃的 例如上面 OCP 中举得例子. 在外部调用就是利用了继承的特性, 也就是 里氏替换   
       
  • 依赖倒置原则DIP(Dependence Inversion Principle)
           
    • 定义 : 指代了一种特定的解耦形式, 使得高层次的模块不依赖于低层次的模块的实现细节的目的, 依赖模块被颠倒了. 通俗点 : 在Java中依赖抽象(接口,抽象类), 而不依赖具体实现类. 模块之间的依赖通过 抽象 发生, 实现类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象类产生.     
    • 举例 : 还是在 OCP 中的例子, 内部加载类依赖于也就是成员变量是 缓存接口 , 而不是具体的某一个 单缓存 或者 双缓存 的实现类.   
       
  • 接口隔离原则ISP(Interface Segregation Principles)
           
    • 定义 : 接口的依赖关系应该建立在最小的接口上. 通俗点 :接口隔离原则的目的是系统解开耦合, 从而容易重构, 更改和重新部署.     
    • 举例 : 在操作一些 IO文件,网络 的时候我们总是伴随着 try...catch...finally . 在最终调用块中调用 close() 确保资源可以正确的释放. 但这样这样的代码不仅可读性差可以每次都是写一些冗余的模板代码. 其实可以提供一个静态方法, 而根据java中的的特性,之上操作的对象都会实现一个 标识接口Closeable ,这个接口标识了一个可关闭的对象有一个 close() . 所以这个静态方法的形参接收一个 Closeable 接口,并在方法内调用 close() 即可. 仔细想想: 这个方法的形参在调用的时候传入的实参是 里氏替换原则 , 而方法内部调用的是一个接口的 close() 方法,但传入的可能是某一个实现类,那么这不就是 依赖导致原则 ,并且建立在最小化的依赖基础上, 只要知道这个对象是可关闭的, 别的一概不关心, 这就是 接口隔离原则 .   
       
  • 迪米特原则LOD(Law of Demeter)
           
    • 定义 : 一个对象应该对其他对象有 最少 的了解. 通俗点 : 一个类应该对自己需要耦合或调用的类知道的最少, 类的内部如果实现与调用者或者依赖者没有关系, 调用者或者依赖者只需要知道他需要的方法即可, 其他一概不管.     
    • 举例 : 房间类, 中介类, 上班族类. 可以 上班族 应该只关心 中介类 , 而不需要关注 房间类 . 只需要 中介类 返回房子的地址即可. 而不需要通过调用 中介类 返回一个 房间类 . 这也就是代码中需要注意的. 不要过度耦合, 要降低类之间的关系.   
    启航之路 - UML类图说明

          
   对于许多类组成的庞大关系网, 最好的办法是通过图来表示出其关系. 可以直观的看出组合的元素, 元素直接是如何存在的, 元素与哪些元素直接存在着联系等. 表示出来的图就是 UML类图 .
   可以看如下一个稍微完整的一个 UML类图
   
启航 - 设计模式与 Android 源码 (篇一)-1 (Android,启航,技能,开发)

  组成元素

  
       
  • 类和接口 : 通过黄色的矩形框来表示一个类, 例如上面鸟就是一个 普通类 , 如果类名是斜体那么就是 抽象类 , 如果和 飞翔 或者 唐老鸭 的表示法那么就是接口.   
  • 访问权限 : 通过 + 公共权限 , - 私有权限 , # 保护权限   
  • 变量和方法 : 分别在第二行, 和第三行表示,抽象方法同样斜体表示, 静态属性的用下划线表示.  
  关系结构

  
       
  • 继承关系 : 类与类之间的关系, 通过 空心三角+实线 表示, 通过 箭头的方向指向父类 表述关系.   
  • 实现关系 : 类与接口直接的关系, 通过 空心三角+虚线 表示, 通过 箭头的方向指向接口 表述关系.   
  • 关联关系 : 当一个类知道另一个类的时候,可以使用 关联 , 比如企鹅和气候两个类中, 企鹅类的变量有气候类的引用 , 这个时候就如上图之间的关系. 实线箭头 表示, 箭头指向被知道的类   
  • 依赖关系 : 例如 动物 是依赖 氧气和水的 , 就如 动物类中的方法形参类型依赖这两个类型 . 如上图动物和水之间关系. 使用 虚线箭头 , 箭头指向被依赖的类   
  • 聚合关系 : 表示一种弱拥用, A可以包含B, 但B不可以包含A. 如大雁和雁群两个类. 雁群类中会有一个数组,数组的元素是大雁类型. 这之间就是 聚合 . 使用 空心菱形+实线箭头   
  • 合成关系 : 也可以认为是 组合 . 是一种强拥有关系. 例如鸟类和翅膀类, 鸟类是整体, 翅膀类是部分. 并且其生命周期相同, 对应着就是 在鸟类初始化的时候,翅膀类也会随之初始化 . 并且, 上图中的鸟到翅膀还有 1..2 的字样. 这称为 基数 . 表明一段会有几个实例, 例如一个鸟会有两个翅膀. 如果一个类有无数个实例那就用 n 表示. 关联关系 , 聚合关系 也是可以有 基数 的. 使用 实心菱形+实线箭头 表示.  
  编程是门技术, 更加是一门艺术, 不能只满足代码结果运行正确就完事, 时常考虑如果让代码更加简练, 更加容易维护, 更易扩展和复用, 这样才可以真正提高.
  发现之路 - 23种设计模式

  单例模式 Singleton

  模式介绍

  
       
  • 定义 : 确保某个类只有一个实例, 而且自行实例化并向整个系统提供这个实例.   
  •   场景 : 确保一个类只会有一个对象实例, 避免产生多个对象消耗过多的资源, 或者某种类型的对象只应该有且只有一个. 如创建一个对象需要消耗的资源过多, 访问IO和数据库等资源时就可以考虑单例.
    模式范例     
  单例模式的实现有5种.
  
          
  • 饿汉式单例 --> 实现代码   
  • 懒汉式单例 -->实现代码   
  • 静态内部类单例 -->实现代码   
  • 枚举单例 -->实现代码   
  • 容器实现单例 -->实现代码 这种方式在 android 源码中存在.  
  知识扩展

   枚举实现法 最大的优点就是实现简单, 但是在 android 却比较消耗内存. 有一点与其他单例模式不同的是: 默认枚举实例的创建 是线程安全的 . 为什么? 因为其他的单例在一种特定的场合下会重新创建对象,那就是 反序列化 .
   反序列化 是从磁盘读回数据并创建一个新的对象. 即使构造函数是私有的, 反序列化依然可以通过特殊的途径去创建一个实例, 相当于调用了构造函数. 反序列化提供了一个很特别的 钩子函数 , 类中具有一个私有的, 被实例化的方法 readResolver() , 这个方法可以让开发人员控制对象的反序列化. 例如上面的几个单例模式, 如果想杜绝单例对象在被反序列化时重新生成对象, 那么必须加入如下方法:
  [code]private Object readResolve() throws ObjectStreamException(){
    return sInstent;        // 返回单例中的实例对象
}[/code]  这样在反序列化的时候就不是默认的重新生成一个新对象. 而对于枚举,并不存在这个问题. 因为即使反序列化它也不会重新生成新的实例.
  Android源码对应模式

   我们经常会在 Activity 中通过 getSystemService(String name) 这个函数来获取系统的服务, 比如说 WMS , AMS , LayoutInflater 等等. 这些服务都会在某一时刻以 容器单例 的形式保存在应用中.
   以 Adapter#getView() 中使用布局加载器 LayoutInflate.from(context).inflate(layoutId,null) 为例
   会调用 ContextImpl#getSystemService(String) 方法获取服务, 而方法内部只是从一个 SYSTEM_SERVICE_MAP 名字的集合中获取了一个 ServiceFetcher 对象, 并从其中获取具体的服务返回.
  那么我们可以缕一下应用的启动, 并定位到何时保存的这些服务到这个集合的.
  
       
  • 首先应用的入口为 ActivityThread#main() ,在这个函数里面会创建 ActivityThread 对象, 并启动消息循环(UI)线程, 调用 attach(boolean) 函数   
  • 在 attach(boolean) 中通过 Binder 机制与 ActivityManagerService 通信, 最终回调本类的 handlelaunchActivity() 函数.   
  • 然后执行 PerformLaunchActivity() 函数, 开始创建 Application , Context , Activity , 并把上下文关联到 Activity 中, 最终调用 Activity#onCreate()  
   ok刚才大概流程是这样的, 通过之前的分析我们知道, 各个系统服务是保存在 ContextImpl类中的 , 这个类是在上面的第3步中被初始化的. 看如下代码, 就是服务被注册的代码, 时机也就是第一个 Context 被创建的时候.
  [code]class ContextImpl extends Context {
    // 存储所有系统服务的集合
    private static final HashMap SYSTEM_SERVICE_MAP =new HashMap();

    // 一个注册服务的并添加到结合的方法
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
       if (!(fetcher instanceof StaticServiceFetcher)) {
           fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
       }
       SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }

    // 静态语句块, 只在类第一次被加载的时候调用, 保证了服务只被添加一次.
    static {
        // 注册了LayoutInflate服务
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});

        registerService(INPUT_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    return InputManager.getInstance();
                }});

        /**
         *  后面省略一大坨的注册的服务代码
        **/
    }

}[/code]  建造者模式 Builder

  模式介绍

   一个复杂的对象有很多组成成分, 如汽车, 车轮, 方向盘, 发动机,等等. 为了在构建过程中对外部隐藏实现细节, 就可以使用 Builder 模式将部件和组装过程分离, 使得构建过程和部件都可以自由扩展, 两者之间的耦合也将到了最低.
  
       
  • 定义 : 将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示.   
  •   场景 :
           
    • 当初始化一个队形特别复杂, 参数特别多, 且有很多参数都具有默认值时.     
    • 相同的方法, 不同的执行顺序, 产生不同的事件结果时     
    • 多个部件或零件, 都可以装配到一个对象中, 但是产生的运行结果又不相同.   
    模式范例

       范例代码
    范例的UML类图

    启航 - 设计模式与 Android 源码 (篇一)-2 (Android,启航,技能,开发)

      上例中通过具体 MacbookBuilder 类构建 Macbook 对象, 而 Director 封装了构建复杂产品对象的过程, 对外隐藏了构建的细节. Builder 于 Director 一起将一个复杂对象的构建与它的表示分离, 是的同样的构建过程可以创建不同的对象.
      可能你会觉得 唉? 怎么和我见过的Builder模式不一样呢? ,这是因为 Director 这个角色经常会被忽略. 而直接使用一个 Builder 来进行对象的封装, 并且这个 Builder 通常为 链式调用 , 它的每个 setter 方法都会返回 this 自身, 比如我们常用的 AlertDialog . 下节介绍.
       
  Android源码模式实现

   在Android中最经典的 Builder 实现就是 AlertDialog . 看一下开发中的使用:
  [code]// 一个粗略的创建dialog
// 创建构建者builder角色
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.sym_def_app_icon)
      .setTitle("标题")
      .setMessage("message")
      // 设置点击等..
      .setPositiveButton("确定", null);

// 构建
AlertDialog alertDialog = builder.create();

// 显示
alertDialog.show();[/code]   从类名就可以看出这是一个 Builder模式 , 通过 Builder 对象来组装 Dialog 的各个部分. 将 Dialog 的构造和表示进行了分离.
   接下来看一下 AlertDialog 的源码:
  [code]public class AlertDialog extends Dialog implements DialogInterface {
    // AlertController 这个对象会保存Builder对象中的各个参数
    private AlertController mAlert;

    // 实际上操作的是上面这个变量中的属性
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }

    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }
    // 省略一坨代码如各种setter等
    // Builder以内部类的形式存在
    public static class Builder {
        // 1.存储AlertDialog的各个参数 如title,icon等
        private final AlertController.AlertParams P;

        // 构造函数
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }

        // 2. 设置参数, 我们构建的Builder设置的参数就是这些方法
        public Builder setTitle(int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

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

        // ....

        // 3.构建AlertDialog, 传递参数
        public AlertDialog create() {
            // 4.因为已经通过builder设置了参数, 接下来就可以创建真正需要的AlertDialog对象
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);

            // 5.将Builder类中的成员变量P应用到AlertDialog类中
            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;
        }      
    }   
}[/code]   对, 最后还调用了 AlertDialog#show() 函数, 这个函数主要做了如下几件事情:
  
       
  • 通过 dispatchOnCreate() 函数来调用 AlertDialog#onCreate() 函数   
  • 然后调用 AlertDialog#onStart() 函数   
  •   最后将 Dialog 的 DecorView 添加到 WindowManager 中.
      那么在看一下 onCreate() 函数的源码及后续调用.
      
  [code]// AlertDialog类
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mAlert.installContent();
}

// AlertController类
public void installContent() {
   // 设置窗口, 没有title类型
   mWindow.requestFeature(Window.FEATURE_NO_TITLE);
   int contentView = selectContentView();
   // 设置窗口的内容视图
   mWindow.setContentView(contentView);
   // 初始化AlertDialog其他子视图的内容
   setupView();
   setupDecor();
}[/code]   这部分比较重要, 通过 Window#setContentView() 和Activity是一样的过程, 设置了内容布局, 通过 AlertController 的构造函数可以发现加载布局资源就是 com.android.internal.R.layout.alert_dialog 这个文件, 之前的Builder中的各种 setter 方法就是把设置的内容传入到这个布局当中.
   可以看到 Android源码中的AlertDialog 并没有遵循 GOF设计模式 中经典的实现方式, 而是进行了变种, 但却使其使用更加的方便. 这里 AlertDialog.Builder 这个类同时扮演了范例中的 builder , 具体实现builder , Director 的角色. 简化了 Builder 设计模式, 因为模块比较稳定不会存在变化, 根据具体场景简化模式, 正是体现了灵活运用设计模式的实例.
  实战场景

   就如 Picasso , Glide 等链式的调用, 你可以通过链式设置很多配置属性, 也可以仅调用两三此传入必要参数即可. 是的调用实现更加灵活.
  原型模式 Prototype

  模式介绍

   创建性模式, 从一个样板对象中复制出一个内部属性一致的对象, 其实就是 克隆 . 而被复制的对象就叫做 原型 , 多用于创建复杂的或者构造耗时的实例
  
       
  • 定义 : 用原型实例指定创建对象的种类, 并通过拷贝这些原型创建新的对象.   
  • 场景 :
           
    • 类初始化需要消耗非常多的资源, 这个资源包括数据,硬件资源等, 可通过原型拷贝避免这些消耗     
    • 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限, 同样可以使用原型模式     
    • 一个对象需要提供给其他对象访问, 并且会能会对其修改属性, 可以用原型拷贝多个对象提供使用   
       
   其实这个模式很简单, 就是利用 Object#clone() 方法可以复制一份提供使用(clone是一个 native 方法). 但是需要注意, 通过实现 Cloneable 接口的原型模式在调用 clone 函数构造并不一定就比通过 new 方式的快, 只有当通过 new 构造对象较为耗时或者说成本较高时, 通过 clone 方法才能获得效率提升.
  UML类图
1234下一页
友荐云推荐




上一篇:AR|也许AR增强现实技术才是距离设计师最近的未来
下一篇:Control Anything with a Universal Wireless Remote
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

kurcb 发表于 2016-11-28 09:04:13
不错 支持一个了
回复 支持 反对

使用道具 举报

陈晨 发表于 2016-11-28 09:07:13
我来一个充满激情的回复
回复 支持 反对

使用道具 举报

数学不好万岁。 发表于 2016-11-28 10:16:59
我知道在一个没有人的角落,楼主一边紧盯着屏幕,一边用颤抖的小手按着刷新,一遍一遍,零回复!又是零回复吗?楼主内心涌现出一种失望,眼神中透露着凄凉。是的,楼主,我理解你,这种冷落的感觉在这个地方太常见了,在这里该火的贴不会火,骗回复的帖子却永远被回复着。楼主的无奈我很懂,所以我来了,带来了经验,带来了人气,带来了零点几秒的首页显示。而我问楼主讨要的,仅仅是一层楼而已,是的,不用谢我,但请记住我,记住我来过。
回复 支持 反对

使用道具 举报

jy01668522 发表于 2016-11-29 02:52:31
做人必备的100项技能,看帖回帖是一项!
回复 支持 反对

使用道具 举报

lzmuyu 发表于 2016-11-29 03:08:05
楼下的小伙伴,速度跟上!
回复 支持 反对

使用道具 举报

rfedsfd 发表于 2016-11-29 03:13:12
好帖必须得顶起
回复 支持 反对

使用道具 举报

陈建华 发表于 2016-11-29 03:13:16
将薪比薪想一下,算了,不想活了。
回复 支持 反对

使用道具 举报

刘远军 发表于 2016-11-30 02:52:35
天塌下来你顶着,我垫着!  
回复 支持 反对

使用道具 举报

1263481627 发表于 5 天前
我是火华哥,抢沙发专业户。。。。
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表