Android面试题(七)设计模式

24 篇文章 0 订阅

九、架构设计

 

9.1 MVC模式

MVC模式介绍:

MVC是Model-View-Controller的简称

Model:模型层,负责处理数据的加载或者存储

View:视图层,负责界面数据的展示,与用户进行交互

Controller:控制器层,负责逻辑业务的处理

MVC模式的特点:

1.耦合性低;

2.可扩展性好;

3.模块职责划分明确

MVC模式的不足:

XML文件作为视图层,所做的事情比较有限,所以Activity作为Controller同时也承担了一部分的View视图显示工作,导致Activity代码庞大,维护困难

9.2 MVP模式

  • View 对应于Activity,负责View的绘制以及与用户交互
  • Model 依然是业务逻辑和实体模型
  • Presenter 负责完成View于Model间的交互

mvp模式简化的Activity的代码逻辑,将复杂的逻辑代码提取到了Presenter中进行处理。对应的耦合度降低。

 

一张图总结Mvc和Mvp的区别

 

其实最明显的区别就是,MVC中中Model和View直接交互的,而MVP中很明显,Model与View之间的交互由Presenter完成,Presenter与View之间的交互是通过接口的。

9.3 MVVM模式

MVVM模式不是四层,任然是3层,分别是Model、View、ViewModel

Model :负责数据实现和逻辑处理,类似MVP。

View : 对应于Activity和XML,负责View的绘制以及与用户交互,类似MVP。

ViewModel : 创建关联,将model和view绑定起来,如此之后,我们model的更改,通过viewmodel反馈给view,从而自动刷新界面。

通常情况下,数据的流向是单方面的,只能从代码流向UI,也就是单向绑定;而双向绑定的数据流向是双向的,当业务代码中的数据改变时,UI上的数据能够得到刷新;当用户通过UI交互编辑了数据时,数据的变化也能自动的更新到业务代码中的数据上。而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个关键的工具,它是支持双向绑定的。

 

下面就一起来看看他们的职责和协同工作的原理。

  • Model

Model层就是职责数据的存储、读取网络数据、操作数据库数据以及I/O,一般会有一个ViewModel对象来调用获取这一部分的数据。

  • View

我感觉这里的View才是真正的View,为什么这么说?View层做的仅仅和UI相关的工作,我们只在XML、Activity、Fragment写View层的代码,View层不做和业务相关的事,也就是我们的Activity 不写和业务逻辑相关代码,一般Activity不写更新UI的代码,如果非得要写,那更新的UI必须和业务逻辑和数据是没有关系的,只是单纯UI逻辑来更新UI,比如:滑动时头部颜色渐变、editttext根据输入内容显示隐藏等,简单的说:View层不做任何业务逻辑、不涉及操作数据、不处理数据、UI和数据严格的分开。

  • ViewModel

ViewModel 只做和业务逻辑和业务数据相关的事,不做任何和UI、控件相关的事,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,操作的也都是对数据进行操作,这些个数据源绑定在相应的控件上会自动去更改UI,开发者不需要关心更新UI的事情。

总结一下:View层的Activity通过DataBinding生成Binding实例,把这个实例传递给ViewModel,ViewModel层通过把自身与Binding实例绑定,从而实现View中layout与ViewModel的双向绑定。mvvm的缺点数据绑定使得 Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。

9.4 谈谈你对Android设计模式的理解

 设计模式的六大原则:

1.单一职责原则:对于一个类而言,应该仅有一个引起它变化的原因。通俗地理解是不要在Activity中写Bean文件、网络请求处理和Adapter等。

2.开放封闭原则:类、模块、函数等应该是可以拓展,但是不可以修改。在开发中,需求是变化的,如果有新的需求,我们就要重新把类改一遍显然是很爆炸的,所有进来通过扩展的方式实现

3.迪米特原则:一个软件应当尽可能少地和其他实体发生相互作用。

4.接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上,接口的负责化要适度

5.依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖于抽象。抽象不应该依赖细节,细节应该依赖与抽象。读起来很拗口,在Java中,抽象是指接口和抽象类;细节是实现类;高层模块就是调用者,底层模块就是具体的实现类,我的理解是类跟类之间的依赖通过抽象产生的。

6.里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。

  • 手写一个生产者消费者模式

要是如何使用wait notify/notifyAll

//仓库代码 主要处理同步问题 public class Storage { private final int MAX_SIZE = 100;//仓库最大容量 private List list = new LinkedList();//产品存储在这里 public void produce(int num) {//生产num个产品 synchronized (list) { //一定是while,因为wait被唤醒后需要判断是不是满足生产条件 while(list.size()+num > MAX_SIZE) { System.out.println("暂时不能执行生产任务"); try{ list.wait(); } catch ( InterruptedException e) { e.printStackTrace(); } } //满足生产条件开始生产 for(int i = 0; i < num; i++) { list.add(new Object()); } System.out.println("已生产产品数"+num+" 仓库容量"+list.size()); list.notifyAll(); } } public void consume(int num) {//消费num个产品 synchronized (list) { while(list.size() < num) { System.out.println("暂时不能执行消费任务"); try{ list.wait(); } catch ( InterruptedException e) { e.printStackTrace(); } } //满足消费条件开始消费 for(int i = 0; i < num; i++) { list.remove(); } System.out.println("已消费产品数"+num+" 仓库容量"+list.size()); list.notifyAll(); } } }

之后定义生产者消费者线程

//生产者 public class Producer extends Thread { private int num;//生产的数量 public Storage storage;//仓库 public Producer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage. produce(num); } } //消费者 public class Consumer extends Thread { private int num;//消费的数量 public Storage storage;//仓库 public Consumer(Storage storage) { this.storage = storage; } public void setNum(int num) { this.num = num; } public void run() { storage. consume(num); } }

主程序 用来开启多个生产者消费者线程执行操作

public class Test{ public static void main(String[] args) { Storage storage = new Storage(); Producer p1 = new Producer(storage); Producer p2 = new Producer(storage); Producer p3 = new Producer(storage); Producer p4 = new Producer(storage); Producer p5 = new Producer(storage); Consumer c1 = new Consumer(storage); Consumer c2 = new Consumer(storage); Consumer c3 = new Consumer(storage); p1.setNum(10); p2.setNum(20); p3.setNum(10); p4.setNum(80); p5.setNum(10); c1.setNum(50); c2.setNum(20); c3.setNum(20); c1.start(); c2.start(); c3.start(); p1.start(); p2.start(); p3.start(); p4.start(); p5.start(); } }

核心的就是每个线程拿到锁之后检测是不是满足条件,不满足则wait释放锁及CPU资源,等待被唤醒之后判断条件满足的话执行生产/消费操作,然后唤醒别的等待的线程,此线程结束

  • 适配器模式,装饰者模式,外观模式的异同?

装饰者与适配者模式的区别

 

适配器模式将一个类的接口,转化成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。

装饰者模式:动态的将责任附加到对象上(因为利用组合而不是继承来实现,而组合是可以在运行时进行随机组合的)。若要扩展功能,装饰者提供了比继承更富有弹性的替代方案(同样地,通过组合可以很好的避免类暴涨,也规避了继承中的子类必须无条件继承父类所有属性的弊端)。

关于适配器模式和装饰者模式:个人理解为,适配器是横向的转换接口,而装饰是纵向的包装接口。

适配器模式将一个对象包装起来以改变其接口,装饰者模式将一个对象包装起来以增加新的行为和责任,外观模式将一群对象“包装”起来以简化其接口。

理解RxJava:https://www.cnblogs.com/JohnTsai/p/5695560.html

 

9.5 说说EventBus作用,实现方式,代替EventBus的方式

EventBus是什么:

  1. 简化组件之间的通信
  2. 分离事件发送者和接收者
  3. 对活动,片段和后台线程表现良好
  4. 避免复杂和容易出错的依赖关系和生命周期问题
  5. 使您的代码更简单
  6. 运行速度是快的
  7. 很小(约50k的jar)
  8. 在实践中证明了具有100,000,000+个安装的应用程序
  9. 具有传送线程,用户优先级等高级功能

替代:RxJava实现RxBus

RXBus(RxBus的核心功能是基于Rxjava的)

RxJava写一个RxBus,来实现EventBus的发布 / 订阅的事件总线功能。

或者按照evenbus原理来写:储存反射

9.6 从0设计一款App整体架构,如何去做?

想要设计App的整体框架,首先要清楚我们做的是什么

一般我们与网络交互数据的方式有两种:主动请求(http),长连接推送

结合网络交互数据的方式来说一下我们开发的App的类型和特点:

  • 数据展示类型的App:特点是页面多,需要频繁调用后端接口进行数据交互,以http请求为主;推送模块,IM类型App的IM核心功能以长连接为主,比较看重电量、流量消耗。
  • 手机助手类App:主要着眼于系统API的调用,达到辅助管理系统的目的,网络调用的方式以http为主。
  • 游戏:一般分为游戏引擎和业务逻辑,业务脚本化编写,网络以长连接为主,http为辅。
  • 一般我们做的App都是类型1,简要来说这类app的主要工作就是
  1. 把服务端的数据拉下来给用户展示
  2. 把用户在客户端修改的数据上传给服务端处理

所以这类App的网络调用相当频繁,而且需要考虑到网络差,没网络等情况下,App的运行,成熟的商业应用的网络调用一般是如下流程:

UI发起请求 - 检查缓存 - 调用网络模块 - 解析返回JSON / 统一处理异常 - JSON对象映射为Java对象 - 缓存 - UI获取数据并展示

这之中可以看到很明显职责划分,即:数据获取;数据管理;数据展示

 

9.7 谈谈对java状态机理解

一、有限状态机(FSM)

有限状态机(Finite State Machine)是表示有限个状态(State)以及在这些状态(State)之间的转移(Transition)和动作(Action)等行为的数据模型。

总的来说,有限状态机系统,是指在不同阶段呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。

这样的系统在某一时刻一定会处于其所有状态中的一个状态,此时它接收一部分允许的输入,产生一部分可能的响应,并迁移到一部分可能的状态。

有限状态机的要素:

(1)State(状态)

状态(State),就是一个系统在其生命周期中某一个时刻的运行情况,此时,系统会执行一些操作,或者等待一些外部输入。并且,在当前形态下,可能会有不同的行为和属性。

(2)Guard(条件)

状态机对外部消息进行响应时,除了需要判断当前的状态,还需要判断跟这个状态相关的一些条件是否成立。这种判断称为 Guard(条件)。Guard 通过允许或者禁止某些操作来影响状态机的行为。

(3)Event(事件)

事件(Event),就是在一定的时间和空间上发生的对系统有意义的事情,事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。

(4)Action(动作)

当一个事件(Event)被状态机系统分发的时候,状态机用 动作(Action)来进行响应,比如修改一下变量的值、进行输入输出、产生另外一个 Event 或者迁移到另外一个状态等

(5)Transition(迁移)

从一个状态切换到另一个状态被称为 Transition(迁移)。引起状态迁移的事件被称为触发事件(triggering event),或者被简称为触发(trigger)。

二、分层状态机(HFSM)

在有限状态机中,虽说状态是有限的,但是当状态太多的时候却不是那么好维护的,这个时候就需要将一些具有公共属性的状态分类,抽离出来,将同类型的状态作为一个状态机,然后再做一个大的状态机,来维护这些子状态。

例如,在有限状态机中,我们的状态图是这样的:

但在分层状态机中,我们的状态是这样的:

这样一来,我们就不需要考量所有的状态之间的关系,定义所有状态之间的跳转链接。

只需要使用层次化的状态机,将所有的行为分类,把几个小的状态归并到一个大的状态里面,然后再定义高层状态和高层状态中内部小状态的跳转链接。

分层状态机从某种程度上就是限制了状态机的跳转,而且高层状态内部的状态是不需要关心外部状态的跳转的,这也做到了无关状态间的隔离,在每个状态内部只需要关心自己的小状态的跳转就可以了,这样就大大的降低了状态机的复杂度。

9.8 Fragment如果在Adapter中使用应该如何解耦?

startActivityForResult是Activity类里的方法,在原Activity里通过Intent跳转到其他类再跳回到原Activity里时候,回传数据所用在Adapter以及其他非Activity类使用的时候,可以将由原Activity类传入的Context强转为Activity类,再在原Activity里重写onActivityResult方法接受到返回值。

9.9 Binder机制及底层实现

Binder在C/S中的流程如下:

 

Binder通信机制流程(整体框架)

 

上图即是Binder的通信模型。我们可以发现:

  1. Client和Server是存在于用户空间
  2. Client与Server通信的实现,是由Binder驱动在内核空间实现
  3. SM作为守护进程,处理客户端请求,管理所有服务项。

为了方便理解,我们可以把SM理解成DNS服务器; 那么Binder Driver 就相当于路由的功能。

步骤一:Server向SM注册服务

 

  1. 首先,XXXServer(XXX代表某个)在自己的进程中向Binder驱动申请创建一个XXXService的Binder的实体(可以理解成具有真实空间的Object)

 

  1. Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用(没有真实空间,可以理解成实体的 一个链接,操作引用就会操作对应链接上的实体),注意,是将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Service。

 

  1. SM收到数据包后,从中取出XXXService名字和引用,填入一张查找表中。
  2. 此时,如果有Client向SM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client。

 

注:引用和实体。这里,对于一个用于通信的实体,可以有多个该实体的引用。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引用。

有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。

步骤二:Client从SM获得Service的远程接口

 

Server向SM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了 (BpBinder)。

  1. Client也利用保留的handle值为0的引用向SM请求访问某个Service:我申请访问XXXService的引用。
  2. SM就会从请求数据包中获得XXXService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。

之 后,Client就可以利用XXXService的引用使用XXXService的服务了。如果有更多的Client请求该Service,系统中就会有更多的Client获得这个引用。

建立C/S通路后

首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:

  1. 从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用
  2. 从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用。

也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。

 

  1. 建立CS通路后的流程:(当接收方获得Binder的实体,发送方获得Binder的引用后)
  2. 发送方会通过Binder实体请求发送操作。Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binder_write_read.write_buffer) (对于接收方为读缓冲区),并把read_size(接收方读数据)置为数据大小(对于具体的实现后面会介绍);
  3. 接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作
  4. 接收方执行完后,会把返回结果同样用binder_transaction_data结构体封装,写入写缓冲区(对于发送方,为读缓冲区)

9.10 对于应用更新这块是如何做的?(解答:灰度,强制更新,分区域更新)?

灰度:(能够平滑过渡的一种发布方式)

(1)找单一渠道投放特别版本。

(2)做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。

(3)开放单独的下载入口。

还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版

增量更新:bsdiff:二进制差分工具bspatch是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 不足:要区分版本,内置及版本相同破解版apk无法增量更新,最好进行sha1sum校验,保证基础包的一致性。

 

 

1. 什么是设计模式? 答:设计模式是在软件开发中,经过多次实践证明,被广泛接受的、可重用的、可靠的解决特定题的最佳实践或解决方案。 2. 设计模式的分类有哪些? 答:设计模式可以分为创建型模式、结构型模式、行为型模式三类。 3. 简述单例模式的实现方式及其优缺点。 答:单例模式是一种创建型模式,它保证一个类只有一个实例,并提供一个全局访点。单例模式的实现方式可以是饿汉式、懒汉式、双重检查锁等。优点是可以减少资源的消耗,提高系统性能,缺点是可能造成资源的浪费和线程安全题。 4. 解释工厂模式的概念及其应用场景。 答:工厂模式是一种创建型模式,它提供了一种创建对象的最佳实践,通过抽象工厂类和具体工厂类的组合,可以实现客户端与具体产品类的解耦。工厂模式的应用场景包括简单工厂模式、工厂方法模式和抽象工厂模式。 5. 请说明观察者模式的实现方式及其应用场景。 答:观察者模式是一种行为型模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都能够得到通知并自动更新。观察者模式的实现方式包括使用Java提供的Observer和Observable接口、自定义事件监听器等。应用场景包括GUI界面、事件驱动系统、消息发布和订阅等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值