第一章Android构成基石--四大组件
Activity
1.生命周期
onCreate() 第一次创建时调用,初始化工作
onStart() 不可见,可见之前调用
onResume() 可见。执行完onResume以后Activity会请求AMS渲染视图。此时Activity位于栈顶,并且处于运行状态
onPause() 准备去启动或者返回另一个Activity时调用。可见变为不可见。用于:保存数据+释放资源
onStop() 同onPause()区别是对话框式的Activity,不执行onStop
onDestroy()销毁前调用。
onRestart()停止状态变成运行状态时调用,也就是被Activity重启了。
2.构成
从ActivityThread.java类的performLaunchActivity()方法开始加载Activity(翻译performLaunch执行启动)。
在这个方法里
1.先建Context 根据createBaseContextForActivity
2.根据Context getClassLoader
3.根据ClassLoader 新建出一个Activity newActivity
4.建了一个Window,并将它与activity关联
5.将Window传入了activity.attach()
6.在activity.attach()里,其实是将window传入,new 了一个 PhoneWindow
7.在PhoneWindow的setContentView()里其实分为两步:①installDecor() 初始化
DecorView ②mLayoutInflater.inflate(layoutResID, mContentParent)(加载XML布局)
8.这里的DecorView才是整个页面的顶级视图,它从一些系统布局中加载(已经定义好的很多R.laout.xxx),并且在运行时将自己写的布局添加到系统布局的mContentPartent(ViewGroup)中,这样一来用户界面就被添加到系统布局上了。系统布局就是主题,标题栏等等
3.启动模式
Activty是通过栈来管理的,当前页面在栈顶,如果按了返回键,栈顶的Activty就会移出栈,所以本来第二位置的就到栈顶显示了
1.standard(标准)默认,可以被多次实例化
2.singleTop(栈顶复用)栈顶时复用,并且调用onNewIntent()函数将Intent对象传递到这个实例中
3.singleTask(栈内复用)会将Activity顶到栈顶,上面的也就全被顶(移)出栈了。并且onNewIntent()将会被调用
4.singleInstance(单例)单独开一个任务栈,将这个Activity装里面,下回调用时在这个任务栈里复用,并调onNewIntent()函数。并且(单例)整个系统只有这一个Activity。
Service
Service默认执行在UI线程中,所以得建一个线程运行Service再处理耗时操作。
当应用被杀掉时该应用的Service都会被杀掉,但是挂后台,Service就一直在运行
生命周期
onCreate() 首次创建时会调用,后面就不会
onStartCommand() 每次调用startService()方法,都会调用该方法
onDestroy() stopService()/stopSelf(),服务就停止
onBind() 当调用bindService()会绑定,多次调用不会重复绑定回调
onUnbind() 当调用unbindService()会解绑
IntentService
执行短期耗时任务,简化版普通Service
IntentService = Service + Thread
IntentService将请求在子线程中执行,用户只需要覆写onHandleIntent函数,任务执行完自动stopSelf()销毁。
前台Service
高优先级不会被杀,在通知栏会有一栏,如音乐播放器。
startForeground(自定义通知栏ID,notification);
public class MyServer extends Service{
public void onCreate(){
super.onCreate();
//设置通知栏--可不看
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
// 此处设置的图标仅用于显示新提醒时候出现在设备的通知栏
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setContentTitle(title);
mBuilder.setContentText(alert);
mBuilder.setTicker(alert);
Notification notification = mBuilder.build();
· · ·
//启动为前台服务 重点
startForeground(自定义通知栏ID,notification);
}
}
AIDL (接口描述类语言)---进程间通信
AIDL的作用实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
blog.csdn.net/luoyanglizi…
简单的来说AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。
AIDL是通过Binder机制来实现的。
支持的数据类型:8种基本类型+String+List+Map+CharSequence或者实现了Parcelable的类
服务器端
-
服务器端App,新建一个AIDL文件
-
在AIDL中定义需要使用的接口
-
创建一个Service去继承AIDL文件,然后实现name接口,并且定义Service隐式调用的action
客户端
- 新建一个APP,在同样的位置创建一样的AIDL文件
-
在新APP的Activity中去隐式调用,服务器端的那个Service,bindService()
-
直接get就能取到
Broadcast
广播是典型的发布—订阅模式(即观察者模式)。发送方并不关心接收方是否接收、处理消息。通过这样的方式来解耦
广播三要素:发送广播的Broadcast、接收广播的BroadcastReceiver、传递信息的Intent。
普通广播 - 无序广播
这里的有序,无序广播都是全局的,所有应用都收的到
广播是异步的,通过Context的sendBrodcast来发送消息。无接收顺序,无法终止。
注册广播:其实是注册的接收器,发随便发,接收器得注册
静态注册:在Manifest里注册,得把action填上
动态注册:registerReceiver()
发送消息
Context的sendBroadcast发送一个Intent
接收
继承BroadcastReceiver
有序广播
设置优先级IntentFilter.setPriority()
通过getResult()和setResult()来获取/传递,上一个接收器传下来的值
通过abortBroadcast()(翻译中止广播)
本地广播
只有自己应用收的到 LocalBroadcastManager
LocalBroadcastManager.getInstance().registerReceiver() 用法差不多
sticky广播(粘性)
发送时,使用sendStickyBroadcast()已经被弃用了,这个会保留最后一条广播,直到被注册接收。
可以用removeStickyBroadcast()来删除
ContentProvider
ContentProvider对外共享数据,其他应用可以通过ContentProvider对数据进行增删改查。他实际上是对SQLiteOpenHelper的封装,通过Uri映射,来判断具体是操作的那个数据库中的表,并进行操作
略
第二章 UI --- View与动画
用户界面的组成
在Activity中关联了一个PhoneWidow创建,在这个窗口下管理了一颗视图树。根节点正是DecorView(GroupView),而DecorView下面就是各个视图控件,这样就组成了android中的UI元素。
ListView与GridView
ListView负责加载和管理视图,Adapter负责提供数据。
ListView就是通过Adapter模式、RecycleBin缓存机制、Adapter的观察者模式实现了高效的列表显示
ListView的继承结构
Adapter模式
-
View getView(int position, View convertView, ViewGroup parent)
convertView表示缓存的ItemView。
parent表示ItemView的父试图。 -
public Object getItem(int position)
获取position位置的数据 -
public long getItemId(int position)
获取position位置的数据id -
public int getCount()
返回数据总数
使用代码
RecycleBin缓存机制
第一次onLayout()会new出布局,当一页布局铺满时,上下滑动超出的item会进入到RecycleBin缓存中,新建的时候会先去缓存中取,没有在新建。
getView里的convertView就是缓存的ItemView
所以当数据有100条,一页只能铺8个,那就通过复用机制只要产生8个ItemView就行
Adapter中的观察者模式
当数据源发生变化,更新ListView -- 观察者模式
- 在setAdapter中,new了一个AdapterDataSetObserver,并将它注册到了adapter上
- mAdapter.registerDataSetObserver(mDataSetObserver);最终会调到他的父类BaseAdapter的registerObserver方法
- registerObserver()方法其实是将Observer添加到了被观察者Observable.java中的观察者容器里
List<mObservers>,这就是标准的观察者模式
- 当调用notifyChanged时,其实是调到了Observable(被观察者)的实现类DataSetObservable中的notifyChanged,里面加锁,然后遍历调用了mObservers的onChanged方法。其实就是标准的观察者设计模式。
BaseAdapter.java的notifyDataSetChanged()
点下去,DataSetObservable.java的notifyChanged()
RecycleView
封装Adapter
RecycleView是ListView的升级版。RecycleView的设计与ListView大致类似。
不过RecycleView将ListView的Adapter封装成了静态内部类,该Adapter还有一个泛型参数VH,
VH extends ViewHolder,ViewHolder中有一个itemView代表了每项数据的根视图,需要在构造函数中传进来
这样设计其实就是将ListView的Adapter中的getView函数判断是否有缓存/复用的逻辑封装到了RecycleView里面。这样用户不用管这里的逻辑,只需要显示数据就行
布局管理器
RecycleView的另一大特点就是将布局方式抽象为LayoutManager。
默认提供LinearLayoutManager线性布局、GridLayoutManager网格布局、StaggeredGridLayoutManager交错布局。还可以自定义布局。
差不多就是只通过LayoutManager就将ListView和GridView等合二为一了(他两就布局方式不同,别的都一样)。
还可以通过ItemDecotation为item添加装饰、ItemAnimator添加动画等等
自定义View
自定义View重点:onMeasure()、onLayout()、onDraw()、onTouch()、动画
补充:2个参数用于反射的构造函数。AttributeSet代表的是xml中的系统属性
补充:Context.obtainStyledAttributes(attrs,R.styleable.SimpleImageView)获得自定义属性
TypedArray调用recycle():回收TypedArray,用于后续调用时可复用之。当调用该方法后,不能再操作该变量。
Canvas与Paint(画布、画笔)
Canvas:画布,整个View就是一个画布。
Canvas
drawRect(RectF , Paint)画矩形
drawBitmap(Bitmap,left,top , Paint)画Bitmap
drawText,drawPath,drawCircle...
● save()、restore() 保存到私有的栈,恢复之前save的图。restore调用如果没有save的图就闪退
Paint
setColor、setTextSize、setStyle空心实心、setShadowLayer设置阴影等
动画
动画分为帧动画、补间动画、属性动画、矢量动画VectorDrawable
帧动画
帧动画就是定义一堆图,然后按照顺序放映。(跟老式的动画片一样)
补间动画
补间动画是让一个控件,旋转、渐变、移动、缩放等。
AnimationSet管理一组动画。他有两个参数,Interpolator(插值器)与shareInterpolator(是否共享插值器)
Interpolator(插值器)系统提供了很多插值器:先加速在减速、加速、匀速(默认)、周期运动、先回退再加速、最后弹一下等等。如果不够用可以自定义。
透明度动画:AlphaAnimation。fromAlpha - toAlpha; 透明度在0.0-1.0
缩放动画:ScaleAnimation。float fromX, float toX, float fromY, float toY, float pivotX(中心的), float pivotY(中心的)。
平移动画:TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)
fromXType =Animation.ABSOLUTE,
Animation.RELATIVE_TO_SELF,
Animation.RELATIVE_TO_PARENT
绝对的,相对自己的,相对父亲的
旋转动画:RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
入参中心点xy值,旋转初始终止角度,参考Type
属性动画
在一定时间内,修改某个对象的某个属性的动画
ValueAnimator就是属性动画的核心类。作用就是在一定时间内不断的修改某属性的值
使用很简单,这是一个将Float(0~1)的动画取值,设置一个监听,在监听里能取到现在的变化的值,然后自己设置到控件的某属性上。如透明度
ObjectAnimator
ValueAnimator有个缺点,他只是将一个值平滑的过度到终点。而ObjectAnimator是将任意属性任意对象操作的类
ObjectAnimator很强大,能修改任意对象的任意属性,通过运行时对象属性setter函数计算并更新值,如果没有就通过反射来更新值
ObjectAnimator继承自ValueAnimator,机制也差不多
使用方式
AnimatorSet
AnimatorSet将多个动画组合起来执行,一起放还是谁前谁后,随意组合
TypeEvaluator估值器与TimeInterpolator时间插值器
TypeEvaluator估值器作用根据当前动画已经执行的时间占总时间的百分比来计算新属性值。
TimeInterpolator时间插值器系统自带匀速、加速、减速、加速减速插值器。
Android中的多线程
消息处理机制---Handler、looper、MessageQueue
构造图大概是这样的。
Handler的使用:
handler.sendMessage(message);或者handler.post(Runnable)子线程发送消息
handleMessage()主线程接收消息
Handler是一个消息处理器,Looper是循环,MessageQueue消息队列。
总结:启动主线程的时候,会新建一个Looper(设置成不可退出的)然后将它设置给ThreadLocal。在这个Looper里有一个MessageQueue。然后调用Looper.loop()创建一个死循环去Queue取message,取到就通过Handle.dispatchMessage发送到主线程的handleMessage
源码浅析
- ActivityThread.java的main()里,Android程序的入口。会先创建Looper,Looper.prepareMainLooper();和启动消息循环Looper.loop();
- Looper.prepareMainLooper()创建Looper。① sThreadLocal.set(new Looper(quitAllowed)) new Looper设置为不允许退出,然后ThreadLocal.set() ②加锁在ThreadLocal里sThreadLocal.get()取到当前线程的Looper并返回
所以Looper其实是存储在ThreadLocal里的
- Looper.loop();执行死循环
loop()方法先myLooper().mQueue取到消息,然后一个死循环不断的queue.next();
取到消息通过handle msg.target.dispatchMessage(msg);发出去
最后清缓存
-
根据不同的提交方式跳到不同的处理方法上。
handler.sendMessage(message);handler.post(Runnable)
-
反正殊途同归都会调到handleMessage,自己定义的
Android中的多线程
Callable、Future、FutureTask只能用在线程池
Callable是有返回值的Runnable,call()
Future为线程池制定了一些规则,其中有是否完成,是否取消,获取结果接口
FutureTask是Future的实现类
ScheduledThreadPoolExecutor
执行定时任务用这个,继承自ThreadPoolExecutor,就是把任务队列重写了,写成延时队列DelayedWorkQueue
显示锁 -- ReentrantLock与Condition
显示锁可以提供轮训锁和定时锁,同时也可以实现公平锁和非公平锁
使用:
lock() --- tryLock()尝试拿锁,防止死锁的 --- unLock() --- newCondition()条件
unLock必须放在finally里释放,要不然没释放会死锁。
Condition(条件)与Lock是绑定的,Condition用于实现线程间的通信,他是为了解决wait、nitify难用的问题,其实差不多 await()、singnalAll()等待与唤醒
AsyncTask
AsyncTask 创建异步任务更简单。
使用:
入参分别为参数,进度,返回值
在doInBackground里执行子线程代码,其余方法为开始、进度条、完成等
调用与取消execute()、cancel()
源码解析
- 当AsyncTask.execute()的时候,会先调用自定义的onPreExecute(),然后调用ThreadPoolExecutor.execute来执行FutureTask任务。
往下点,记住参数sDefaultExecutor
往下点
sDefaultExecutor一直往下点就是下图,也就是就是一个线程池ThreadPoolExecutor
- 这里的FutureTask找到代码,会执行自定义的doInBackground方法
- 别的方法都是通过Handle来实现的
Http网络请求
HTTP是一种应用层的协议,他通过TCP实现了可靠的数据传输,能够保证数据的完整性、正确性。而TCP对于数据传输的有点也能体现在HTTP上,使得HTTP的数据传输吞吐量、效率都能得到保证。
详细的交互流程:
(1)客户端执行网络请求,解析URL地址,得到服务器的主机名、端口号
(2)将服务器的主机名转成服务器地址
(3)建立一条客户端、Web服务器的TCP连接
(4)客户端通过输出流向服务器端发送一条HTTP请求
(5)服务器向客户端回应一条HTTP响应报文
(6)客户端从输入流获取报文
(7)客户端解析报文,关闭连接
Http的请求方式
PUT增/DELETE删/POST改/GET查/HEAD/TRACE/OPTIONS
HTTP请求报文格式:
起始行
第一行为起始行,写了请求方式和预期执行结果
请求头
0~n个首部字段。也就是头部字段名:值
请求体
分为请求主体和响应主体。数据结构是任意的二进制数据、文本等。
请求主体:发给Web服务器的数据。
响应主体:返回给客户端的数据。
HTTP响应报文格式
状态行
协议版本,状态码,状态码描述,之间由空格分隔
GET、DELETE
delete请求服务器删除url里指定的资源,但是不一定会被执行。服务器可以撤销该请求。
URL的最大长度是1024字节,所以GET/DELETE最大就1KB
http://www.devtf.cn/?p=909
GET /?p=909 HTTP/1.1 这是起始行,GET方式,参数值是p=909 HTTP版本1.1
Host:www.devtf.cn 请求头:Host=xxx
Cache-Control:no-cache 请求头:Cache-Control=xxx
POST、PUT
报文格式一般是表单,请求参数在请求体上。
//todo 待补充,跳过吧
Android中的网络请求
Android中提供了两种网络请求方式:Apache的HttpClient 和 Java的HttpURLConnection
Apache的HttpClient
Android 6.0以后被移除了。暂不学习
(1)创建一个HttpClient对象
(2)创建HttpGet、HttpPost对象,将URL信息填到HttpGet里
(3)调用execute发送HttpGet、HttpPost请求,并返回HttpResponse对象
(4)通过HttpResponse对象的getEntity获得处理数据
Java的HttpURLConnection
因为Apache的HttpClient在android6.0被移除,所以这个也就成了唯一的选择。
优点:API简单,体积小。压缩和缓存机制可以有效的减小网络流量,还能提升速度和省电。
使用GET方式:
InputStream inputStream = null;
try {
String url = "https://www.baidu.com/";
URL url = new URL(url);
//得到connection对象。
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTime(1000);
//设置请求方式
connection.setRequestMethod("GET");
//连接
connection.connect();
//得到响应码
int responseCode = connection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
//得到响应流
inputStream = connection.getInputStream();
//将响应流转换成字符串
String result = is2String(inputStream);//将流转换为字符串。
Log.d("kwwl","result============="+result);
}
} finally{
inputStream.close();
}
使用POST方式:
try {
URL url = new URL(getUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);/启动输出流
connection.setDoInput(true);//接收输入流
connection.setUseCaches(false);//不用缓存
String body = "userName=zhangsan&password=123456";//Str不太行,用IO合理一点
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
writer.write(body);
writer.close();
connection.connect(); //发起请求
//获取结果
int responseCode = connection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
InputStream inputStream = connection.getInputStream();
String result = is2String(inputStream);//将流转换为字符串。
Log.d("kwwl","result============="+result);
}
} catch (Exception e) {
e.printStackTrace();
}
如果在输出流里写了参数,不管设置了GET/post 最后都会用POST来请求的。setRequestMethod("GET")
SQLite
SQLite是一个遵守了ACID的数据库。(原子性、一致性、隔离性、永久性)
SQLite库很小,被集成在用户程序里,是应用的一部分。这个库可以被动态链接。
优点:
1.零配置
2.小,250K
3.操作快
4.开源
5.数据库文件可共享
SQLite的结构:前端解析接口(词法分析器、语法分析器、代码生成器)、后端引擎(虚拟机、B/B+树、页面调整程序)
SQL语句
创建数据库
sqlite3 XXX.db
创建表
create table 表名 (字段1 数据类型 约束,字段2 数据类型 约束 ... );
最后 ; 结尾必须的。 每一列:字段名+数据类型+约束 (name TEXT notNull)
数据类型:Null、INTEGER(整型)、real(浮点)、text(字符)、blob(块)
约束:not null、unique(唯一)、Primary key(主键)、Foreign key(外键)、check(条件检查)、default(默认值)
主键:每个表必须的自增长的id
外键:另一个表的某值必须从这个表中查的到。设备消息表中的设备id,必须在设备表里能查得到。
插入数据 - insert
insert into 表名 (字段列表) values (值)
insert into dev_info (dev_id,dev_name) values (111,烟感)
insert into dev_info values (111,烟感,...)
删除数据 - delete
delete from student where id=1
查询数据 - select
select XXX from student where id=1
修改数据 - update
update student set name=李,age=10 where id=3
修改表 - alter
alter只能修改表名和添加字段
alter table student rename to stu //将student表名改为stu
alter table student add column age integer default 0 //在student表添加age字段,int 类型 默认值0
创建索引 - create index
create index 索引名 on student(name) //studentd的name创建索引
创建视图 - create view
create view 视图名 as SQL语句
drop
drop用于删除表、视图、索引、触发器等
drop student
性能优化
布局优化
去除多余的层级
include
将公用的布局用include抽取出来。一来代码的层次更分明,二来修改的时候减少出错
merge
当子布局的根布局和父布局是一样的时候,可以使用merge。merge可以少一个根布局。
①merge必须用在根布局上
②merge不是View也不是ViewGroup
③merge在inflat时必须声明父布局类型
ViewStub
惰性加载,初始化时不加载,不占用空间,就占一个位置(宽高都为0的View)。当需要使用的时候,调用inflate()或者setVisibility(VISIBLE)时。他才被初始化,加载。
只能加载一次,加载完了自身就会被移除。调用两次就空指针了
使用:平时不调用的,偶尔调用的控件页面,如网络请求失败页。
布局优化总结
(1)减少视图层级
(2)多用RelativeLayout 少用 AbsoluteLayout
(3)ListView等组件,不要用LinearLayout的weight(因为weight属性会让子View绘制2次)
(4)活用include、merge、ViewStub
内存优化
-
珍惜Services资源
当services任务执行完事了以后,要及时的释放资源。不要贪婪的保留这个Service。除非他被触发执行一个任务,要不然都应该是非运行状态。
推荐用IntentService,执行完任务以后,尽快的结束自己 -
当UI隐藏时释放内存
当UI隐藏时,及时的释放资源。UI隐藏包括Activity的切换 onStop()和切换到其他应用 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)(所有UI都被隐藏时触发)
在onStop()里释放 网络连接、广播接收者、unregister
在onTrimMemory(TRIM_MEMORY_UI_HIDDEN)里释放UI资源
- 内存紧张时释放内存
onTrimMemory()回调可以收到内存紧张的级别。根据收到的内存级别来确定释放哪一步的内存。
-
检查内存情况
getMemoryClass()来查看堆空间大小
getLargeMemoryClass()获取更大的堆空间大小 -
避免bitmap浪费
当原图像素高于屏幕像素的时候,可以缩小图片XY。尽量使用Glide等框架加载bitmap。
- 使用优化的数据容器
使用Framework里优化过的容器:SparseArray/LongSparseArray。因为HashMap需要一个额外的实例来记录Mapping操作。而且比如存int时得自动装箱,拆箱
-
枚举的内存>2倍的静态常量
-
注意抽象
抽象用的不好,代码量变多,内存变大
- 避免使用依赖注入框架
在初始化时,会因为注释而消耗大量的工作在扫码代码上,而且反射的效率不高。
-
谨慎使用外部库
-
使用ProGuard来剔除不必要的代码
ProGuard 能通过移除不必要的代码,类,方法对代码进行 压缩、优化、混淆。
- 对APK进行zipalign
Android SDK中包含了一个用于优化APK的新工具zipalign。它提高了优化后的Applications与Android系统的交互效率,从而可以使整个系统的运行速度有了较大的提升。
内存泄漏
GC只会回收不可达的对象。如果本应该被销毁的对象,被错误的持有,那么该对象就不会被释放,也就是内存泄漏了。
内存泄漏了以后,可使用的内存就会减少,如果继续泄漏,那么因为内存不足会频繁的触发GC,而导致UI越来越卡,最终内存溢出了,闪退。
使用Memory Monitor来监控内存情况
当看到频繁的GC或者内存快速增长的时候就要看看是不是内存泄漏了。
LeakCanary
监测内存泄漏的第三方库
性能优化
防止过度绘制
手机上可以看过度绘制,开发者选项 -- GPU过渡绘制,然后就能根据颜色的不同来看过渡绘制的情况。
移除没必要的View,减少视图层级 RelativeLayout和LinearLayout的weight都会对子视图绘制两次
可以用Tools -> Layout Inspector(布局检查器) 来查看视图的层级
用TraceView -- 采集数据和分析 -- 各个函数的调用、占用CPU情况
六大原则与设计模式
单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则、开闭原则
面向对象的三大特性:封装、继承、多态
单一职责原则
一个类只做一件事。也就是一个类里的函数做的工作高度相关,也就是高内聚、低耦合。
优点:复杂性↓,可读性↑,可维护性↑,扩展性↑ --- 高内聚,低耦合
要修改的时候,只要该这个类就行,不会对其他类产生影响。
里氏替换原则
(讲的是继承)
始于继承、多态
所有引用基类、接口的地方能透明的使用其子类的对象。
= 任何父类出现的地方,替换为子类都不会有异常。
= 子类能替换父类,但是父类不能替换子类
= 子类可以扩展父类的功能,但是不能改变父类原有的功能。子类中前置条件(入参)可以放宽,但是后置条件(返回值)得更严格。
优点 :开放性↑,可扩展性↑(继承父亲就能扩展接口等),代码重用性、共享↑(只要继承父类,就拥有了父类的方法和属性,代码的可以复用)
缺点 : 只要继承就拥有了父亲的所有属性、方法,灵活度↓,耦合度↑(父亲一改,儿子全得改)
依赖倒置原则
(讲的是抽象abstract)
(1)高层模块不应该依赖于底层模块,两者都应该依赖于其抽象。
(2)抽象不应该依赖于细节。
(3)细节应该依赖于抽象。
抽象 = 接口、抽象类。 细节 = 实现类
类之间、模块之间不能直接发生依赖关系,而是得通过抽象类或者接口产生
总结:面向接口(抽象)编程。
原来设计:
依赖倒置原则后的设计:
开闭原则
是其他5个原则的指导思想
一个软件实体(类、模块、函数) 对扩展开发,对修改封闭
当软件需要变化的时候,我们应该通过扩展的方式来实现变化,而不是通过修改已有代码来实现。
开闭原则实际上是对抽象的约束。
通过抽象类去约束扩展,不允许出现抽象中没有的public
抽象类得慎重,一但确定,不能更改
参数类型,尽量用抽象类,不用实现类(感觉适用依赖倒置原则)
优点:稳定性、可扩展性
接口隔离原则
客户端不应该依赖他不需要的接口
类间依赖关系应该建立在最小接口上
迪米特原则
一个对象对其他对象应该有最少的了解。
只与直接的朋友通信。
A ---> B ---> C 不能 A又调B又调C (AC没关系,得通过B的话)
被依赖者:只暴露应该暴露的方法熟悉,别的都用private
依赖者:只依赖应该依赖的对象
总结
单一职责原则告诉我们类的职责要单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向抽象编程;
开闭原则告诉我们对扩展开发,对修改封闭;
接口隔离原则告诉我们接口要精简、单一;
迪米特原则告诉我们要降低耦合性。
解耦、单一、高内聚、低耦合