转眼不知不觉来到新公司近三个月了,好久没有静下心写博客了
最近看欢乐颂安迪说的有句话很好,每一个管理者并不在乎你因为什么客观原因导致没完成任务,他更在乎交给你什么任务。你完成的结果如何。是啊,我们在职场与其抱怨这个任务是因为什么原因导致流产,不如多想想解决办法。作为员工我们更应该想想对方有什么需求,在实际工作中多从这些方面思考问题。
1欢乐颂安迪的话引起设计模式的思考
安迪说:“每一个管理者并不在乎你因为什么客观原因导致没完成任务,他更在乎交给你什么任务。你完成的结果如何”,是的,试想让你做一名企业的leader,项目经理,在每天众多繁杂的事情中,出于对上层的交代,每个人都希望招一个有能力的下属帮他完成工作而不是整天跟他汇报“xxx问题导致”,“xxx请假拖延进度”。他更在乎你处理问题的结果是什么。
同理,在我们软件开发中,当我们在一家几个人,十几人的公司里出于节省人力成本考虑,一个leader可能会做接口文档数据,数据库建表,甚至前台,后台部门业务逻辑开发,尽管你是个经验丰富的技术专家,情商沟通能力超群,但是随着企业的扩大一个人能力精力都是有限的,因此他得学会分配任务,把自己的类中的不同功能抽出去,一方面减轻了自己的压力,另一方面每个人都能各尽其能,灵活性,拓展性提高了,这就是下面要说的单一职责原则
1.1单一职责原则
简单的来说单一职责的关键在于单一二字,至于如何做到单一,这得根据具体的业务和功能来拆分,不同的功能封装到不同的类中,这样提高了代码的复用性,灵活性。正如一个leader如何去拆分自己的功能模块划分给手下去做,这也是资源合理安排的良性循环,分不好,自己累,手下没活干,资源没有最大程度被开发。分的好,自己轻松,下属也能为公司创造更多的价值也得到锻炼,这个没什么好说的,主要是为了引入下面要讲的内容。
1.2开闭原则
开闭原则简称OCP原则,它能够很好的为我们的软件开发建立一个稳定的,灵活的系统。为什么这么说,开闭原则的核心定义就是对于扩展是开放的,但对于修改却是封闭的(如果理解起来有点晦涩,待会我们上代码理解),在软件的开发周期中,我们的产品需求不可能在当初设计的一成不变,随着版本的更新,迭代,升级,以及维护等对原有的代码进行修改时,我们可能会将错误的代码引入原有的系统中,破坏原来已经测试成熟的系统,譬如,现在我们对标题栏自定义的view,后来有一点,我觉得toolbar不错,想换个试试,又或者今天我是用Glide加载图片,后来我发现一个更好的加载图片框架,想换个试试,这时候软件需求发生变化了,我们应该尽量的通告扩展的方式来实现变化,而不是整个项目的找代码替换。这就需要引入一个很重要的原则,开闭原则。
少废话,上代码。
好,下面我们看看曾经刚开始我在项目中对标题栏实现,在每一个layout中都include一个头布局。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_pdam">
<include
layout="@layout/public_header" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp" >
然后在基类里面拿到进行设置文本之类的方法并暴露给子类,这会导致一个问题,在对头布局定制化需求比较少的时候是完全hold住的,假如随着项目推进对标题的定制的复杂度越来愈高,整个基类都是关于标题的业务逻辑,这跟刚刚说的软件设计职责单一原则相悖。后来小伙伴集思广益决定重构代码。
于是诞生了新的模式。把标题栏单独抽成一个管理类,在这个类中负责对标题的文字的显示,右边按钮的监听,图标的修改。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/bg_main">
<com.vcredit.cp.view.TitleBar
android:id="@+id/life_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleMiddle="信用生活" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
这样以后的每个activity布局中标题栏的功能被单独划分出去,在TitleBar这个类中完成对标题栏一连串的操作。
/**
* 设置文本标题
* @param text
* @return
*/
public TitleBar setMiddleTitleText(String text) {
tvMiddle.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
tvMiddle.setText(text);
return this;
}
/**
* 设置左侧图标
* @param resId
* @return
*/
public TitleBar setLeftIcon(int resId) {
if (resId > 0){
ivLeft.setImageResource(resId);
}
ivLeft.setVisibility(resId <= 0 ? View.GONE : View.VISIBLE);
return this;
}
/**
* 设置右侧图标
* @param resId
* @return
*/
public TitleBar setRightIcon(int resId){
if (resId > 0){
ivRight.setImageResource(resId);
}
ivRight.setVisibility(resId <= 0 ? View.GONE : View.VISIBLE);
return this;
}
这时候团队的成员对自己很满意,于是就沾沾自喜的觉得自己代码封装的很好并跟主管汇报,大家觉得是不是很好?但我要是主管我肯定觉得这种思路很好,能想到职责单一原则,把不同的功能模块抽出去。但是不是还有明显的问题?
这就是我要说的开闭原则。
下面小伙伴们纷纷想把这个结果用于自己项目中,后来有一天,主管提出一个要求,所有的标题栏使用toolbar。这种需求看似苛刻,但是随着产品发展,需求变更的同时我们可能会因为 技术的升级对原有的技术进行替换,这个并不是什么罕见的事情,那我们如何在产品设计的时候就考虑进去呢?如果按照刚刚的方法,现在要做的就是整个项目每个activity进行修改把Titlebar更换掉。那我们能不能设置一个类可以动态的自己去选择自己想用的标题呢?只需要手动的设置一下就可以替换自己是自定义的TitleBar或者Toolbar?
当然可以,首先我们自定义实现的接口,让它们都实现我们自定义的接口。具体的实现方法由类中具体实现类去完成,这就是刚刚说的开闭原则“软件中的对象(类,模块)应该对于扩展是开放的,但是对于修改是封闭的”,而遵循开闭原则的重要一环就是抽象,接口或者抽象类。
我们让BaseTitleBarStrategy实现TitleBarInterface接口,然后在初始化或者基类里面制定不同的策略,就可以动态的修改使用Toolbr还是TitleBar,这种动态的更换而不是修改可以很好的维护以前的生态系统不被破坏的前提下,灵活多变让自己程序立于不败之地。
private BaseTitleBarStrategy mTitleBarStrategy;
private BaseToolbarStrategy mToolbarStrategy;
/**
* 指定标题栏策略
*/
public void init(BaseTitleBarStrategy strategy) {
setTitleBarStrategy(strategy);
}
开闭原则能够顺利的实现这里面有个核心的地方,就是抽象,这就是要引出我们下一个要讲的里氏替换原则
1.3里矢替换原则
这个设计思路要说清楚如果大家有兴趣看google源码的话,在view类以及对应的子类中就是通过里矢替换的方式让代码简洁,灵活,软件开发这种设计模式是很常用的,通俗点说就是所有基类的地方必须能透明的使用子类的对象。
譬如我定一个一个接口
/**
* 所有的标题栏都实现该接口
*
* Created by zew on 17/6/11.
*/
public interface TitleBarInterface {
TitleBar setLeftIcon(int resId);//设置左边的图标
TitleBar setRightIcon(int resId);//设置右边的图标
TitleBar setLeftIconListener(View.OnClickListener listener);//设置左边图标点击事件
TitleBar setRightIconListener(View.OnClickListener listener);//设置右边图标点击事件
TitleBar setRightIconVisibility(int visibility);//设置右边图标是否显示
TitleBar setMiddleTitleText(String text);//设置标题文字
TitleBar setRightText(String text);//设置右边文字
TitleBar setLeftLayoutHint();//设置左边文字隐藏
TitleBar setRightCricleNumber(int number);//设置小圆点文字
TitleBar setRightTextListener(View.OnClickListener listener);//设置右侧文字点击处理函数
void setVisibility(int visibility);//标题栏设置显示状态
}
我的Titlebar实现这个接口
public class TitleBar extends FrameLayout implements TitleBarInterface
我的Toolbar也实现
public class Toolbar implements TitleBarInterface
假如将来有第三个,第四个bar我们只需要实现该接口并实现方法,通过里氏替换,我们就可以自定义想怎么玩标题栏就怎么玩,各种样式,千变万化的改变,只要实现该接口并实现方法就可以灵活的完成不同需求的替换。
其实这里面一个关键点就是抽象,建立一个抽象的规范,具体的实现在你在运行的时候替换掉抽象,保证需痛的扩展性,灵活性,开闭原则和里氏替换以及策略模式往往是生死相依,不离不弃的三个设计实现,用好了可以让自己的软件扩展开放,修改方便的效果。
2.1为什么要有https
最近公司开始广泛推进适用https,我也觉得科普一下https如何保证信息安全的传输的。
先科普几个概念:
对称加密采用对称密码编码技术,也就是编码和解码采用相同描述字符,即加密和解密使用相同的密钥,实现这种加密技术的算法称对称加密算法。对称加密使用简单,密钥较短,加密和解密过程较快,耗时短,常见的对称加密算法有DES,3DES,lDEA,AES,RC4等。
非对称加密与对称加密不同,其加密算法需要两个密钥:公开密钥(publickey)和私有密钥(private),两者是一对的。如果用公钥加密,只能用私钥才能解密。非对称加密保密性好,但加密和解密花费的时间较长,不适合对大文件加密而只适合对少量的数据加密。常见的非对称加密算法有RSA,ECC,DSA(数字签名)等。
Hash算法是一种单向算法,通过Hash算法可以对目标数据生成一段特定长度、唯一的hash值,但是不能通过这个hash值重新计算出原始的数据,因此也称之为摘要算法,经常被用在不需要数据还原的密码加密以及数据完整性校验上,常用的算法有MD2,MD4,MD5,SHA等。
2.2几种加密方式的优缺点
对称密钥:使用同一个密钥进行加密和解密,速度快,但不安全
非对称密钥:使用不同的密钥进行加密和解密,通常被称为公钥(public key)和私钥(private key)。 公钥可以广泛传播,但是私钥只有其所有者知道。在一个安全的非对称密钥加密方案中,当信息用公钥加密后,只有用私钥才能解密。所以,即使一个黑客拿到你公钥加密过后的信息,也无法解密它,因为它没有配对的私钥。这样,传输的消息就是安全的。速度慢
2.3没有最好的解决方案,都是妥协的产物
https的好处在于让数据传输安全。
如何安全通信呢?对称加密是我们最先想到的方案:将数据进行加密,然后将加密过的数据和密钥同时传到服务器,服务器使用这个密钥解密加密过后的数据。
现在,我们来看看这种可能的场景:黑客截获了该通信,这意味着黑客拥有了密钥和密文。一旦黑客有了密钥,那么解密密文就是很简单的事情了,我们的数据就这样泄漏了。
上面的解决方案非常不安全。我们继续往下看。使用非对称加密怎么样?
这个想法非常棒:服务端发送给你公钥,你使用这个公钥加密数据。因为服务端是唯一拥有私钥的,
这意味着只有服务端能够解密密文。即使黑客截获了该通讯,但因为没有私钥也就无法解密密文。
但是,非对称加密比对称加密更加耗时。为了用户体验,不建议使用非对称加密这种方式来加密/解密大量的数据
。
最终方案
前两种方案都无法解决我们安全通信,我们怎么结合上面的两种方案呢?来看看最终方案:
HTTPS工作的流程。
1.[Server]生成一对密钥:公钥和私钥,我们称之为“KeyPub”,“KeyPri”
2.[Server]服务端将公钥(KeyPub)发送到客户端
3.[Client]生成一个对称密钥(姑且称之为key2),然后用key2加密数据。
4.[Client]使用公钥(KeyPub)加密key2.这时,key2是安全的,因为只有服务度有私钥KeyPri
5.[Client]发送用key2加密后的信息及用KeyPub加密过的key2到服务端
6.[Server]服务端使用KeyPri解密得到加密过的key2,得到真正的key2
7.[Server]使用key2解密消息正文。这样,数据就被安全的传输到了服务端。
结论
由于对称加密比非对称加密快,https决定使用对称加密来加密数据,使用非对称加密对称加密生成的密钥,以确保安全。没有最好的,只有妥协的,其实架构师在设计整个框架的时候都是根据业务跟技术做一个权衡妥协拿出最优的方案,这对于我们这种完美主义者是个很值得借鉴的地方,不要总想着完美,人生不总是那么完美,稍微有点缺憾也有缺憾的美。
3感悟
今天titleBar例子只是为了说明设计模式举的一个例子,这只是冰山一角,开闭原则和里氏替换以及策略模式是一个程序员贯穿一生都得学会的思想,可以应用在不同地方,也是区分一个初中高开发的重要一环。
技术分享就到这,本来计划周末白天去同济看看书,晚上做几个好菜犒劳下自己,结果赶上大学考试周,没看成,郁闷,看了很多android项目,也看过一些javaEE项目,总的来说javaEE这么些年的沉淀留下来很多思想上的东西是很值得我们每一个android程序员学习的,想着好久没有写博客了,加上超哥说我不懂分享,只喜欢埋头写代码,好吧,花了周末写这篇博客一方面纪录下自己开发一点心得,另一方面为了辟谣我不是一个不懂得分享的程序员,哈哈