《Android源码设计模式解析与实战》读书笔记

面向对象的六大原则

单一职责原则

Single Responsibility Principle(SRP),定义是:就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该使一组相关性很高的函数、数据的封装。

例如一个ImageLoader实现图片加载,并要将图片缓存起来。

  • 有的人可能会这样实现:是直接在一个类里实现图片下载、LruCache、displayImage等。但这样耦合太严重,毫无扩展性。随着功能增加,类会越来越大,系统越来越脆弱。

  • 较好的实现是对ImageLoader进行拆分,添加一个类ImageCache类用于处理图片缓存。ImageLoader只负责图片加载的逻辑。

开闭原则

Open Close Principle(OCP),定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是,对于修改是封闭的。在软件的生命周期内,因为变化、升级和维护等原因需要对软件进行原有代码修改时,可能会将错误引入原本已经过测试的旧代码中,破坏原有系统。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

《面向对象软件构造》中提出了开闭原则。这一想法认为,程序一旦开发完成,程序中一个类的实现只应该因错误而被修改,新的或者改变的特性应该通过新建不同的类而实现,新建的类可通过继承的方式来重用原类的代码。

以ImageLoader为例,需要实现内存缓存、SD卡缓存以及双缓存:

  • 有的人可能会这样实现: ImageCache里持有内存缓存类ImageCache、SD卡缓存类DiskCache,再通过设定布尔值来使用if-else判断语句来确定使用哪种缓存。随着逻辑的引入,代码会变得越来越脆弱、复杂,如果一不小心写错某个if条件,就需要更多的时间来排除。最重要的是,用户不能自己实现缓存注入到ImageLoader中,可扩展性差,可扩展性是框架的最重要特性之一。

  • 较好的实现是:提取一个图片缓存的接口,用来抽象图片缓存的功能:

    public interface ImageCache {
        public Bitmap get(String url);
        public void put(String url, Bitmap bmp);
    }
    复制代码

    内存缓存、SD卡缓存、双缓存都实现了该接口。ImageLoader类中增加了一个setImageCache(ImageCache cache)函数,用户可以通过该函数设置缓存实现,也就是通常说的依赖注入。通过函数注入不同的缓存实现,这样不仅是ImageLoader更简单、健壮,也是可扩展性、灵活性更高。几种缓存的具体实现不一样,但是,它们有个特点是都实现了ImageCache接口。当用户需要自定义实现缓存策略时,只需要新建一个实现ImageLoader接口的类,然后改造该类的对象,并通过设置函数注入到ImageLoader中,这样就实现了千变万化的缓存策略。

里氏替换原则

全称是Liskov Substitution Principle(LSP)。定义是:如果对每一个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有的O1都代换为O2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

里氏替换原则第二种定义:所有引用基类的地方必须能透明地使用其之类的对象。通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行,有子类出现的地方,父类未必就能适应。最终总结就两个字:抽象。

在OOP中(面向对象编程,Object Oriented Programming,OOP),继承的优缺点都相当明显。

优点:

  1. 代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性;
  2. 子类与父类基本相似,但又与父类有所区别;
  3. 提高代码的可扩展性。

继承的缺点:

  1. 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;
  2. 可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的所有属性和方法。

里氏替换原则就是为这类问题提供指导原则,也就是建立抽象。通过抽象建立规范,具体的实现在与形式替换掉抽象,保证系统的扩展性、灵活性。开闭原则和里氏替换原则往往是生死相依的,通过里氏替换来达到对扩展开放,对修改关闭的效果。两个原则都同时强调了OOP的一个重要特性——抽象。

依赖倒置原则

Dependence Inversion Principle(DIP)。依赖倒置原则指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。其有几个关键点:

  1. 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
  2. 抽象不应该依赖细节;
  3. 细节应该依赖抽象。

JAVA中,抽象就是指接口或抽象类;细节就是实现类;高层模块就是调用端,低层模块就是具体实现类。依赖倒置在JAVA语言中的表现是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。一句话概括:面向接口(抽象)编程。

如果类与类直接依赖于细节,那么它们之间就有直接的耦合,当具体实现需要变化时,意味着要同时修改依赖者的代码,这限制了系统的可扩展性。例如之前的例子,ImageLoader直接依赖于MemoryCache,这个MemoryCache是一个具体实现而不是抽象,这导致了ImageLoader直接依赖了具体细节,当MemoryCache不能满足ImageLoader而需要被其他缓存实现替代时,就必须修改ImageLoader。

接口隔离原则

InterfaceSegregation Principles(ISP)。定义是:客户端不应该依赖它不需要的接口。接口隔离原则的目的是系统解开耦合,从而容易重构、更改和重新部署。说白了就是让客户端依赖的接口尽可能地小。

例如ImageLoader中的ImageCache就是接口隔离原则的运用,ImageLoader只需要知道该缓存对象有存、取缓存图片的接口即可,其他的一概不管,这就使得缓存功能的具体实现对ImageLoader隐藏,用最小化接口隔离了实现类的细节,也促使我们将庞大的接口拆分到更细粒度的接口当中,使系统具有更低的耦合性,更高的灵活性。

迪米特原则(最少只是原则)

Law of Demeter(LOD)。一个对象应该对其他对象有最少的了解。还有一种解释是:只与直接的朋友通信。

例如,租房子,中介拥有房源,客户对租金和面积有要求。

  • 有的人可能会这样实现:Room有属性area、price,Mediator(中介)有属性List,Tenant(客户)有属性myPrice、myArea,有方法isSuitable(Room room)来判断房源是否符合要求。

    Tenant需要Mediator遍历拥有的房源,然后再对Room进行比较是否符合条件。

  • 较好的实现:

将对于Room的判定操作移到Mediator类中,这本应该是中介的职责,根绝用户的条件查找防止,用户只需要我们的朋友——中介沟通就好了。“只与直接的朋友通信。”

单例模式

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

一般有如下几个关键点:

  1. 构造函数不对外开放,一般为Private;
  2. 通过一个静态方法或者枚举返回单例类对象;
  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下;
  4. 确保单例类对象在反序列化时不会重新构建对象。
public class Singleton {
        private static Singleton instance;

        private Singleton() {}

        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
复制代码

建造者模式

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程创建不同的表示

使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果
  • 多个部件或零件,都可以装配到一个对象中,但产生的运行结果又不相同
  • 产品类非常复杂,或者产品类中的调用顺序不同产生不同的作用
  • 初始化一个对象特别复杂,如参数多,且很多参数都有默认值

构成

你可能感兴趣的:(设计模式,移动开发,java)