面向对象 (⭐⭐⭐)
谈谈对 java 多态的理解?
多态是指父类的某个方法被子类重写时,可以产生自己的功能行为,同一个操作作用于不同对象,可以有不同的解释,产生不同的执行结果。
多态的三个必要条件:
- 继承父类。
- 重写父类的方法。
- 父类的引用指向子类对象。
什么是多态
面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态的技术称为:动态绑定(dynamic binding
),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用:
消除类型之间的耦合关系。
现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
多态的好处:
可替换性(substitutability)
。多态对已存在代码具有可替换性。例如,多态对圆Circle
类工作,对其他任何圆形几何体,如圆环,也同样工作。可扩充性(extensibility)
。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。接口性(interface-ability)
。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。灵活性(flexibility)
。它在应用中体现了灵活多样的操作,提高了使用效率。简化性(simplicity)
。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
Java
中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。
你所知道的设计模式有哪些?
答:Java
中一般认为有 23 种设计模式,我们不需要所有的都会,但是其中常用的种设计模式应该去掌握。下面列出了所有的设计模式。要掌握的设计模式我单独列出来了,当然能掌握的越多越好。
总体来说设计模式分为三大类:
创建型模式,共五种:
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:
策略模式、模板方法模式、观者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
通过静态内部类实现单例模式有哪些优点?
不用
synchronized
,节省时间。调用
getInstance()
的时候才会创建对象,不调用不创建,节省空间,这有点像传说中的懒汉式。
静态代理和动态代理的区别,什么场景使用?
静态代理与动态代理的区别在于代理类生成的时间不同,即根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。如果需要对多个类进行代理,并且代理的功能都是一样的,用静态代理重复编写代理类就非常的麻烦,可以用动态代理动态的生成代理类。
1 | // 为目标对象生成代理对象 |
- 静态代理使用场景:四大组件同
AIDL
与AMS
进行跨进程通信 - 动态代理使用场景:
Retrofit
使用了动态代理极大地提升了扩展性和可维护性
简单工厂、工厂方法、抽象工厂、Builder 模式的区别?
- 简单工厂模式:一个工厂方法创建不同类型的对象。
- 工厂方法模式:一个具体的工厂类负责创建一个具体对象类型。
- 抽象工厂模式:一个具体的工厂类负责创建一系列相关的对象。
Builder
模式:对象的构建与表示分离,它更注重对象的创建过程。
装饰模式和代理模式有哪些区别 ?与桥接模式相比呢?
装饰模式是以客户端透明的方式扩展对象的功能,是继承关系的一个替代方案;
而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用。
装饰模式应该为所装饰的对象增强功能;
代理模式对代理的对象施加控制,但不对对象本身的功能进行增加。
桥接模式的作用与代理、装饰截然不同,它主要是为了应对某个类族有多个变化维度导致子类类型急剧增多的场景。通过桥接模式将多个变化维度隔离开,使得它们可以独立地变化,最后通过组合使它们应对多维变化,减少子类的数量和复杂度。
外观模式和中介模式的区别?
外观模式重点是对外封装统一的高层接口,便于用户使用;
而中介模式则是避免多个互相协作的对象直接引用,它们之间的交互通过一个中介对象进行,从而使得它们耦合松散,能够易于应对变化。
策略模式和状态模式的区别?
虽然两者的类型结构是一致的,但是它们的本质却是不一样的。策略模式重在整个算法的替换,也就是策略的替换,而状态模式则是通过状态来改变行为。
适配器模式,装饰者模式,外观模式的异同?
这三个模式的相同之处是,它们都作用于用户与真实被使用的类或系统之间,作一个中间层,起到了让用户间接地调用真实的类的作用。它们的不同之外在于,如上所述的应用场合不同和本质的思想不同。
代理与外观的主要区别在于,代理对象代表一个单一对象,而外观对象代表一个子系统,代理的客户对象无法直接访问对象,由代理提供单独的目标对象的访问,而通常外观对象提供对子系统各元件功能的简化的共同层次的调用接口。代理是一种原来对象的代表,其它需要与这个对象打交道的操作都是和这个代表交涉的。而适配器则不需要虚构出一个代表者,只需要为应付特定使用目的,将原来的类进行一些组合。
外观与适配器都是对现存系统的封装。外观定义的新的接口,而适配器则是复用一个原有的接口,适配器是使两个已有的接口协同工作,而外观则是为现存系统提供一个更为方便的访问接口。如果硬要说外观是适配,那么适配器有用来适配对象的,而外观是用来适配整个子系统的。也就是说,外观所针对的对象的粒度更大。
代理模式提供与真实的类一致的接口,意在用代理类来处理真实的类,实现一些特定的服务或真实类的部分功能,Facade
(外观)模式注重简化接口,Adapter
(适配器)模式注重转换接口。
代码的坏味道:
代码重复:
代码重复几乎是最常见的异味了。他也是
Refactoring
的主要目标之一。代码重复往往来自于copy-and-paste
的编程风格。方法过长:
一个方法应当具有自我独立的意图,不要把几个意图放在一起。
类提供的功能太多:
把太多的责任交给了一个类,一个类应该仅提供一个单一的功能。
数据泥团:
某些数据通常像孩子一样成群玩耍:一起出现在很多类的成员变量中,一起出现在许多方法的参数中…..,这些数据或许应该自己独立形成对象。 比如以单例的形式对外提供自己的实例。
冗赘类:
一个干活不多的类。类的维护需要额外的开销,如果一个类承担了太少的责任,应当消除它。
需要太多注释:
经常觉得要写很多注释表示你的代码难以理解。如果这种感觉太多,表示你需要
Refactoring
。
是否能从 Android
中举几个例子说说用到了什么设计模式 ?
AlertDialog
、Notification
源码中使用了 Bulider
(建造者)模式完成参数的初始化:
在 AlertDialog
的 Builder
模式中并没有看到 Direcotr
角色的出现,其实在很多场景中,Android
并没有完全按照 GOF
的经典设计模式来实现,而是做了一些修改,使得这个模式更易于使用。这个的 AlertDialog.Builder
同时扮演了上下文中提到的 builder
、ConcreteBuilder
、Director
的角色,简化了 Builder
模式的设计。
当模块比较稳定,不存在一些变化时,可以在经典模式实现的基础上做出一些精简,而不是照搬 GOF
上的经典实现,更不要生搬硬套,使程序失去架构之美。
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。即将配置从目标类中隔离出来,避免过多的 setter
方法。
优点:
- 良好的封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 建造者独立,容易扩展。
缺点:
- 会产生多余的
Builder
对象以及Director
对象,消耗内存。
日常开发的 BaseActivity
抽象工厂模式:
定义:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类。
主题切换的应用:比如我们的应用中有两套主题,分别为亮色主题 LightTheme
和暗色主题DarkTheme
,这两种主题我们可以通过一个抽象的类或接口来定义,而在对应主题下我们又有各类不同的 UI
元素,比如 Button
、TextView
、Dialog
、ActionBar
等,这些 UI 元素都会分别对应不同的主题,这些 UI
元素我们也可以通过抽象的类或接口定义,抽象的主题、具体的主题、抽象的 UI
元素和具体的 UI
元素之间的关系就是抽象工厂模式最好的体现。
优点:
- 分离接口与实现,面向接口编程,使其从具体的产品实现中解耦,同时基于接口与实现的分离,使抽象该工厂方法模式在切换产品类时更加灵活、容易。
缺点:
- 类文件的爆炸性增加。
- 新的产品类不易扩展。
Okhttp
内部使用了责任链模式来完成每个 Interceptor
拦截器的调用:
定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
ViewGroup
事件传递的递归调用就类似一条责任链,一旦其寻找到责任者,那么将由责任者持有并消费掉该次事件,具体体现在 View
的 onTouchEvent
方法中返回值的设置,如果 onTouchEvent
返回 false
,那么意味着当前 View
不会是该次事件的责任人,将不会对其持有;如果为 true
则相反,此时 View
会持有该事件并不再向下传递。
优点:
- 将请求者和处理者关系解耦,提供代码的灵活性。
缺点:
- 对链中请求处理者的遍历中,如果处理者太多,那么遍历必定会影响性能,特别是在一些递归调用中,要慎重。
RxJava
的观察者模式:
定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
ListView/RecyclerView
的 Adapter
的 notifyDataSetChanged
方法、广播、事件总线机制。
观察者模式主要的作用就是对象解耦,将观察者与被观察者完全隔离,只依赖于 Observer
和 Observable
抽象。
优点:
- 观察者和被观察者之间是抽象耦合,应对业务变化。
- 增强系统灵活性、可扩展性。
缺点:
- 在
Java
中消息的通知默认是顺序执行,一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般考虑采用异步的方式。
AIDL
代理模式:
定义:为其他对象提供一种代理以控制对这个对象的访问。
静态代理:代码运行前代理类的 class
编译文件就已经存在。
动态代理:通过反射动态地生成代理者的对象。代理谁将会在执行阶段决定。将原来代理类所做的工作由 InvocationHandler
来处理。
使用场景:
- 当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。
缺点:
- 对类的增加。
ListView
/RecyclerView
/GridView
的适配器模式:
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
使用场景:
- 接口不兼容。
- 想要建立一个可以重复使用的类。
- 需要一个统一的输出接口,而输入端的类型不可预知。
优点:
- 更好的复用性:复用现有的功能。
- 更好的扩展性:扩展现有的功能。
缺点:
- 过多地使用适配器,会让系统非常零乱,不易于整体把握。例如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果出现太多这种情况,无异于一场灾难。
Context
/ContextImpl
外观模式:
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,门面模式提供一个高层次的接口,使得子系统更易于使用。
使用场景:
- 为一个复杂子系统提供一个简单接口。
优点:
- 对客户程序隐藏子系统细节,因而减少了客户对于子系统的耦合,能够拥抱变化。
- 外观类对子系统的接口封装,使得系统更易用使用。
缺点:
- 外观类接口膨胀。
- 外观类没有遵循开闭原则,当业务出现变更时,可能需要直接修改外观类。