【小木箱生长营】规划形式系列文章(排期中):
Android架构演进 规划形式 Android常见的4种创立型规划形式(上)
Android架构演进 规划形式 Android常见的4种创立型规划形式(下)
Android架构演进 规划形式 Android常见的6种结构型规划形式(上)
Android架构演进 规划形式 Android常见的6种结构型规划形式(中)
Android架构演进 规划形式 Android常见的6种结构型规划形式(下)
Android架构演进 规划形式 Android常见的8种行为型规划形式(上)
Android架构演进 规划形式 Android常见的8种行为型规划形式(中)
Android架构演进 规划形式 Android常见的8种行为型规划形式(下)
Android架构演进 规划形式 规划形式在Android实时监控体系的实践
Tips:
重视群众号小木箱生长营,回复“规划形式”可免费取得规划形式在线思维导图
一、导言
Hello,我是小木箱,欢迎来到小木箱生长营Android架构演进系列教程,今日将共享Android架构演进 规划形式 为什么主张你一定要学透规划形式?
今日共享的内容首要分为四部分内容,榜首部分是规划形式5W2H,第二部分是7大规划准则,第三部分是3大规划形式,终究一部分是总结与展望。
其间,7大规划准则首要包括开闭准则、里氏替换准则、依靠倒置准则、单一责任准则、接口阻隔准则、最小常识准则和组成复用准则。3大规划形式首要包括创立型、结构型和行为型。
拿破仑之前说过Every French soldier carries a marshal’s baton in his knapsack
,意思是“每个战士背包里都应该装有元帅的权杖”,本意是激励每一名上战场的战士都要有大局观,有元帅的思维。
编程的海洋里也是如此,不想当架构师的程序员不是好程序员,我觉得架构师或许是大部分程序员最理想的归宿。而规划形式是每一个架构师所必备的技能之一,只要学透了规划形式,才敢说真实了解了软件工程。
期望每个有技能寻求的Android开发,能够从代码中去寻找归于自己的那份高兴,经过代码构建编程生计的架构思维。
假设学完小木箱Android架构演进规划形式系列文章,那么任何人都能为社区或企业贡献优质的SDK规划计划。
二、规划形式5W2H
5W2H又名七何分析法,5W2H是二战的时分,美国陆军兵器修理部创造的思维办法论,便于启发性的了解深水区常识。5W2H是What、Who、Why、Where、When、How much、How首字母缩写之和,广泛用于企业技能管理、脑筋风暴等。小木箱今日尝试用5W2H分析法分析一定要学透规划形式底层逻辑。
2.1 What: 什么是规划形式?
首要聊聊规划形式5W2H的What, 什么是规划形式?sourcemaking从条件过,在软件工程中,规划形式是软件规划中,常见问题的可重复处理计划。规划形式尽管不能够直接转换为代码,然后完结规划。可是规划形式是处理不同状况特定共性问题的通用模板。
不论北京的四合院、广州的小蛮腰仍是上海的东方明珠,都有好的修建地基。规划形式也是如此,规划形式是高效能工程建设柱石,假设事务代码看作钢筋水泥,那么规划形式能够看作是修建地基。只要地基满意结实,项目工程才不会因为质量问题烂尾。
假设想高度重用代码,那么主张你一定要学透规划形式。
假设想让代码更简略被了解,那么主张你一定要学透规划形式。
假设想保证代码可靠性、可保护性,那么主张你一定要学透规划形式。
简而言之,规划形式不只是被多数人知晓、被重复验证的代码规划经验总结,并且规划形式是特定场景和事务痛点,针对同类问题通用处理计划。
2.2 Who: 谁应该学透规划形式?
聊完规划形式5W2H的What,再聊聊规划形式5W2H的Who,谁应该学透规划形式? 规划形式能够用于一切项目开发作业的一种高价值规划理念,并且在写源代码或许阅览源代码中常常需求用到。
假设你的作业是偏架构方向,那么运用规划形式或许像一日三餐相同频频。规划形式已然链接着每个根底模块的方方面面,就看你想不想让你的编程生计走的更远,假设想,就接着往下面看。
规划形式不是代码! 规划形式不是代码! 规划形式不是代码! 重要的作业说三遍,规划形式是编程思维。假设参加外企面试,那么根本不会像国内考各种八股文,有两个重点考查项目,一方面是算法,另一方面是体系规划。而体系规划和规划形式休戚相关。
假设面试国内中大厂架构组也是,规划形式关于架构组程序员而言,根本随意拿捏,规划形式是差异中级程序员和高档程序员的要害。当国内Android程序员疲于事务,导致规划形式在事务方面缺少实践,假设你把握的比他们更好,是不是相比之下会更有竞争力。学透规划形式是一般开发逆袭架构师的捷径,凡是有一定作业年限的Android开发,都必须学透规划形式。
2.3 Why: 为什么要学透规划形式?
聊完规划形式5W2H的Who,再聊聊规划形式5W2H的Why,为什么要学透规划形式? 关于为什么要学透规划形式的原因一共有三点。
榜首,学透规划形式,为职场提升翻开绿色通道。 代码是程序员的一个门面。有些互联网大厂技能岗提升,会随机抽取对方代码提交节点,依据对方的代码片段,进行review并给予提升打分。这是平时为什么要重视代码整齐性很重要原因。学透了规划形式并运用于项目开发,等同你有了职场提升直升飞机。
第二,学透规划形式,编码大局观更强。 正确运用规划形式,无异于站在伟人的肩膀上看世界。前辈们在 Java 、C++等言语范畴上,花费几十年时刻,并经过分类、提炼、总结、沉积和验证等各个方面的尽力,才整理了一套精品架构思维,假设想成为一名Senior Android Dev,那么有什么理由不去研究呢?
第三,学透规划形式,笼统建模才能更强。 关于特定场景和特定事务,假设正确的运用规划形式,那么代码质量和事务拓宽性会有质的腾跃。
2.4 When: 什么时分运用规划形式?
说完规划形式5W2H的Who,再聊聊规划形式5W2H的When,关于运用规划形式的机遇一共有两点。
榜首点,场景要契合
第二点,保证原有事务安稳根底上,套用或灵敏运用规划形式,能够处理未来或许呈现的拓宽性和保护性问题。
2.5 Where: 哪些当地需求运用到规划形式?
说完规划形式5W2H的When,再聊聊规划形式5W2H的Where,哪些当地需求运用到规划形式?多数状况下,假设程序员做技能需求要考虑其灵敏性的当地,就能够运用规划形式。22种规划形式都有自己的策阅,22种规划形式的策阅也适合不同的场景。
咱们不或许从策阅规划形式的事务布景套用状况规划形式的事务布景,比方女朋友,世界上没有性情一模相同的女生,一个女生只能处理其时状况的情感需求。咱们的前任和现任带给的体感都不彻底相同。因而,每一种规划形式中的策阅和女朋友相同,每一个都是原创。
规划形式和女朋友有一个一起特征便是供给了其时布景下能够处理问题(心情价值)的结构。在处理实践问题时,必须考虑该问题处理计划的改动,假设事务产生大的改动,那么需求考虑规划形式是否通用。比方女朋友,假设当下你习气内卷,但女朋友忽然躺平了,那么后边话题或许越来越少。
运用规划形式切勿生搬硬套,正确的运用规划形式能够很好地将技能需求映射事务模型上。可是假设过度运用不适宜的规划形式会形成程序可读性更高,保护本钱变得更高。比方女朋友,假设为了女朋友过度忍让,那么终究或许因为联络不平等不欢而散。
那么,怎样挑选适宜的规划形式呢?运用规划形式的准则是在树立对规划形式有很好认知条件,并习气这种办法模型,看到一种特定的技能布景,就立马能够联想到详细对应的模型。这种当地运用规划形式是最适宜不过的。比方追女生,假设能对方兴趣爱好和性情特征能彼此招引,各方面布景匹配度高,会更适宜一点。
综上所述,假设你发现特定事务痛点,刚好契合特定规划准则,或能匹配特定规划形式办法模型,那么主张你将这种事务笼统成通用模板映射到实践事务里。
2.6 How much: 学透规划形式的价值点是什么?
说完规划形式5W2H的Where,再聊聊规划形式5W2H的How much,学透规划形式的价值点是什么?关于运用规划形式的价值一共有三点,榜首点是针对个人,第二点是针对工程质量,终究一点是针对团队。
对个人而言,正确运用规划形式能够进步程序代码规划才能和职场受欢迎度。
对工程质量而言,假设想要代码高可用、高复用、可读性强和扩展性强,那么需求规划形式做支撑。
对团队而言,在现有工业化和商业化的代码规划维度上,规划形式不只更标准和更工程化,并且规划形式能够进步编码开发功率,节约处理问题时刻。
2.7 How: 怎样学透规划形式?
说完规划形式5W2H的How much,再聊聊规划形式5W2H的How,怎样学透规划形式?学透规划形式有四种途径,别离是网课、文章、书本、源码和项目实战。网课方面,小木箱引荐咱们在B站看一下马战士教育和图灵讲堂视频。这两门课程能够带咱们很轻松的入门规划形式。
文章方面,小木箱引荐咱们看一下百度工程师教你玩转规划形式(观察者形式)、 提升代码质量的办法:范畴模型、规划准则、规划形式、洞悉规划形式的底层逻辑、规划形式二三事、规划形式在事务体系中的运用、Android中居然包含这么多规划形式,一起来学一波!、当规划形式遇上 Hooks、谈谈我作业中的23个规划形式和规划形式之美等文章。
书本方面,小木箱引荐咱们看一下 《head first》 、《重学Java规划形式 RPC中心件规划运用程序规划编程实战分布式范畴驱动规划和规划形式结合》 和 《代码整齐之道》 。
源码方面,Glog日志结构能够值得一学。
项目实战方面,学有余力的同学能够动手用规划形式完结一下定位组件、实时日志组件和启动监控组件。
终究,听说有个叫小木箱这个家伙规划形式的文章写的还挺不错的,能够重视一下~
三、7大规划准则
产生代码差的原因,有两方面,榜首方面是外部原因,第二方面是内部原因。外部原因首要有:项目排期急,没有多少时刻去规划;资源短缺,人手不行,只能怎样快怎样来;紧迫问题修复,暂时计划快速处理……。内部原因首要有:自我要求不高;无反馈通道
而处理代码差的根因首要是办法有三个:范畴建模、规划准则、规划形式
分析阶段:当拿到一个需求时,先不要着急想着怎样把这个功用完结,这种很简略陷入事务脚本的形式。
- 分析什么呢?需求分析需求的意图是什么、完结该功用需求哪些实体承当,这一步中心是找实体。
举个上面进店Tab展现的比方,它有两个要害的实体:导航栏、Tab,其间导航栏里边包含了若干个Tab。
规划阶段:分析完了有哪些实体后,再分析责任怎样分配到详细的实体上,这就要运用一些规划准则去辅导
回到上面的比方上,Tab的责任首要有两个:一个是Tab能否展现,这是它自己的责任,如上新Tab展现的逻辑是店肆30天内有上架新产品;
另一个责任便是Tab标准信息的构建,也是它自己要负责的。
导航栏的责任有两个:一个是承受Tab注册;另一个是展现。责任分配不合理,也就不满意高内聚、低耦合的特征。
打磨阶段:这个阶段挑选适宜的形式去完结,咱们一看到形式都会了解它是做什么的,比方看到模板类,就会知道处理通用的事务流程,详细改动的部分放在子类中处理。
上面的这个比方,用到了2个规划形式:一个是订阅者形式,Tab主动注册的进程;另一个是模板形式,先判断Tab能否展现,然后再构建Tab标准信息,流程尽管简略,也能够笼统出来通用的流程出来,子类只用简略地重写2个办法。
范畴模型首要是和产品和运营整理事务模型,进行流程化优化,然后判断需求是否合理可行。
提升代码质量还有一个捷径,那便是要遵循七大准则,七大准则比方毛泽东乡村包围城市辅导方针。首要确认一致我国方针,然后是在统治力量薄弱的乡村树立革命依据地,等革命队伍变大,树立乡村包围城市的矩阵,终究采取不同摧毁策阅对国民政府不同城市政权进行各个击破。
假设体系工程事务代码混乱,咱们首要保证底层代码功用不变,然后以点成线,以线成面,以面成网,以网建模。依据规划准则,针对不同的事务痛点,制定单一准则或组合准则技能计划。接着小步快跑,安稳安全地施行软件工程质量改造规划,终究到达下降事务冗余或许下降未来大幅度代码改动带来的危险意图。规划准则的底层逻辑便是让软件能够较好地应对改动,降本增效。
而规划准则又分为七个部分,别离是开闭准则、里式替换准则、依靠倒置准则、接口阻隔准则、最小常识准则、单一责任准则和组成复用准则。
3.1 开闭准则
榜首个规划准则是开闭准则,开闭准则简称OCP,正如英文界说的那样the open–closed principle, Entities should be open for extension, but closed for modification , 对扩展开放,对修正封闭。
这样做的意图是保护已有代码的安稳性、复用性、灵敏性、保护性和可扩展性,一起又让体系更具有弹性。
Android需求产生改动的时分,不发起直接修正Android基类源代码,尽量扩展模块或许扩展原有的功用,去完结新需求改动。
关于开闭准则,一般选用承继或完结的办法,比方: 假设涉及到非通用功用,不要把事务逻辑加到BaseActvity,而是单独运用ChildActvity类承继abstract BaseActvity,并让ChildActvity去拓宽abstract BaseActvity的笼统办法。
翻一翻开源库源码,面向笼统类或面向接口去完结的功用场景十分常见。
那么,为什么要运用开闭准则呢?
榜首,开闭准则能够下降功用规划本钱
第二,开闭准则能够进步代码安稳性
第三,开闭准则能够进步代码可保护性
第四,开闭准则能够下降测验的本钱
因为无论是大佬仍是小白改动工程陈腐代码块,都无法保证改完后代码是0危险的。因而,假设遵守开闭准则,那么能够极大限度的下降改动引发的前史功用性缺失、逻辑漏洞等危险。
3.1.1 UML图例
老爸帮小明去买书,书有许多特征,一种特征是书是有姓名的,一种特征是书是有价格的,那假设依照开闭准则的话,首要要界说一个IBook接口,描绘书的两种特征:称号、价格。
然后用一个类NovelBook去完结这个接口,方便读取和修正书的称号和价格。
依据开闭准则,运用者假设要对书进行比方打折降价活动是不能直接在NovelBook操作的,需求用DisNovelBook承继NovelBook去拓宽NovelBook的getName和getPrice办法。
3.1.2 Bad Code
//----------------------------代码片段一----------------------------
/**
* 功用描绘: 界说小说类NovelBook-完结类
*/
public class NovelBook implements IBook {
public String name;
public int price;
public NovelBook(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getPrice() {
if (this.price > 50) {
return (int) (this.price * 0.9);
} else {
return this.price;
}
}
}
//----------------------------代码片段二----------------------------
/**
*
* 功用描绘: 现在有个书店售书的场景,首要界说一个IBook类,里边有两个特点:称号、价格。
*/
public interface IBook{
public String getName();
public int getPrice();
}
//----------------------------代码片段三----------------------------
public class Client {
public static void main(String[] args) {
NovelBook novel = new NovelBook("笑傲江湖", 100);
System.out.println("书本姓名:" + novel.getName() + "书本价格:" + novel.getPrice());
}
}
3.1.3 Good Code
因为假设未来需求改动,如小明要买数学书和化学书,其间化学书价格不能超过15元,数学不能高于30元,且数学书能够运用人教版,而化学书既能够运用湘教版也能够运用人教版。
//----------------------------代码片段一----------------------------
/**
* 功用描绘: 界说小说类NovelBook-完结类
*/
public class NovelBook implements IBook {
public String name;
public int price;
public NovelBook(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getPrice() {
return this.price;
}
}
//----------------------------代码片段二----------------------------
public class DisNovelBook extends NovelBook {
public DisNovelBook(String name, int price) {
super(name, price);
}
// 复写价格办法,当价格大于50,就打9析
@Override
public int getPrice() {
if (this.price > 50) {
return (int) (this.price * 0.9);
} else {
return this.price;
}
}
}
//----------------------------代码片段三----------------------------
/**
*
* 功用描绘: 现在有个书店售书的场景,首要界说一个IBook类,里边有两个特点:称号、价格。
*/
public interface IBook{
public String getName();
public int getPrice();
}
//----------------------------代码片段四----------------------------
public class Client{
public static void main(String[] args){
IBook disnovel = new DisNovelBook ("小木箱生长营",100000);
System.out.println("群众号姓名:"+disnovel .getName()+"群众号粉丝数量:"+disnovel .getPrice());
} }
这些逻辑加在一块的话,因为购买条件不相同,需求将不变的逻辑笼统成接口完结类NovelBook,但假设不运用拓荒准则,直接更改接口完结类NovelBook,跟着需求不断胀大,凡是多添加一些操控项,在多人协同开发进程中代码保护危险度会越来越高。
3.1.4 运用准则
拓荒准则运用准则有2个点,榜首个点是笼统束缚;第二个点是封装改动
首要来说一说笼统束缚,笼统束缚一共有三个方面,榜首个方面是接口或笼统类的办法悉数要public,方便去运用。
第二个方面是参数类型、引证方针尽量运用接口或许笼统类,而不是完结类;因为运用接口和笼统类能够防止以为更改起始数据;
第三点是笼统层尽量保持安稳,一旦确认即不允许修正。假设笼统层常常改动,会导致一切完结类报错。
接着来说一说封装改动,封装改动一共有两个方面,榜首个方面是相同的逻辑要笼统到一个接口或笼统类中。
第二个方面是将不同的改动封装到不同的接口或笼统类中,不应该有两个不同的改动呈现在同一个接口或笼统类中。
比方上文说的,假设老爸买完书了,预备买菜,那么要单独立一个IVegetable的接口。而不是改造本来的IBook。
3.2 里氏替换准则
第二个规划准则是里氏替换准则,里氏替换准则简称LSP,正如英文界说的那样The Liskov Substitution Principle,Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it 。
子类能够替换父类,子类方针能够替换程序中父类方针呈现的任何当地,并且保证本来程序的逻辑行为不变以及正确性不会被损坏。
相当于子类能够扩展父类功用。承继是里氏替换准则的重要表现办法。里氏替换准则用来辅导承继联络中子类该怎样规划的。
里氏替换准则,注意事项是尽量不要重写父类的办法,也是开闭准则的重要办法之一,为什么不主张重写父类的办法呢?
因为重写会掩盖父类的功用,导致运用者对类预期功用被修正后得到就不是对方想要的功用。
提出问题
下面有个关于Bird鸟类位移时刻的技能需求:
已知Bird(基类)的子类Swallow(小燕子)和Ostrich(鸵鸟)位移了300米,Ostrich(鸵鸟)的跑步速度为120米/秒。
Swallow(小燕子)和Ostrich(鸵鸟)的翱翔速度为120米/秒和0米/秒,求Swallow(小燕子)和Ostrich(鸵鸟)的位移时刻。
分析问题
位移时刻,算的是位移间隔/跑步速度仍是位移间隔/翱翔速度呢?
Ostrich(鸵鸟)能飞吗?
Swallow(小燕子)翱翔速度能否单独笼统成一个办法呢?
处理问题
能够参阅UML图例、Good Code、Bad Code和里氏替换运用准则。
3.2.1 UML图例
3.2.2 Bad Code
惯例办法❌: 界说鸟的基类Bird,Bird(基类)有一个setFlySpeed(翱翔速度)。依据distance(间隔)去算出它翱翔的getFlyTime(翱翔时刻)。
//----------------------------代码片段一----------------------------
public class Bird {
private double flySpeed;
public void setFlySpeed(double speed) {
this.flySpeed = speed;
}
public double getFlyTime(double distance) {
return distance / flySpeed;
}
}
//----------------------------代码片段二----------------------------
public class Swallow extends Bird {}
//----------------------------代码片段三----------------------------
public class Ostrich extends Bird{
@Override
public void setFlySpeed(double speed) {
speed = 0;
}
}
//----------------------------代码片段四----------------------------
public class Main {
public static void main(String[] args) {
Bird swallow = new Swallow();
Bird ostrich = new Ostrich();
swallow.setFlySpeed(120);
ostrich.setFlySpeed(120);
System.out.println("小木箱说,假设翱翔300公里:");
try {
System.out.println("燕子将翱翔: " + swallow.getFlyTime(300) + "小时。"); // 燕子翱翔2.5小时。
System.out.println("鸵鸟将翱翔: " + ostrich.getFlyTime(300) + "小时。"); // 鸵鸟将翱翔Infinity小时。
} catch (Exception err) {
System.out.println("产生错误了!");
}
}
}
Bird(基类)有两个子类,一个是Swallow(小燕子),一个是Ostrich(鸵鸟)。
小燕子只要设置正确的setFlySpeed(速度)和distance(间隔)即可。
但Ostrich(鸵鸟)不太相同,Ostrich(鸵鸟)是不会飞的,Ostrich(鸵鸟)只会地上跑。
因为Ostrich(鸵鸟)没有flySpeed(翱翔速度)。那在构造Ostrich(鸵鸟),去承继完结这 Bird(基类), Ostrich(鸵鸟)的重写办法setFlySpeed(设置翱翔速度)传0.0。
在Bird(基类) 傍边去核算getFlyTime(翱翔时刻),依照惯例的应该distance(间隔) / setFlySpeed(设置翱翔速度),就得到了getFlyTime(翱翔时刻)。
去调用getFlyTime(翱翔时刻) 时刻的时分,因为对Ostrich(鸵鸟) 的getFlyTime(翱翔时刻)的子类的参数speed,重写了setFlySpeed(设置翱翔速度)办法,并设置该办法speed参数为0,数学里边0不能作为分母,所以会得到一个无效成果Infinity,重写进程,违反了里氏替换准则。
成果:
3.2.3 Good Code
正确的办法是✔️:打断Ostrich(鸵鸟)和Bird(基类)承继联络,界说Bird(基类)和Ostrich(鸵鸟)的超级父类Animal(动物),让Animal(动物)有奔驰才能。Ostrich(鸵鸟)的翱翔速度尽管为 0,但奔驰速度不为 0,能够核算出其奔驰 300 千米所要花费的时刻。
那么,尽管不能将Ostrich(鸵鸟)的getRunTime(位移时刻)笼统成 Bird(基类)的 getFlyTime(翱翔时刻)。
但能够利用超级父类Animal(动物)的getRunTime(位移时刻),即花费时长,这时Ostrich(鸵鸟)的setRunSpeed(跑步速度)就不为0,因为Ostrich(鸵鸟)复用了超级父类Animal(动物) getRunTime(位移时刻)功用。
超级父类Animal(动物)有一个 getRunSpeed(跑步速度) ,而不是Bird(基类)的setFlySpeed那个翱翔速度。
去设置setRunSpeed(跑步速度) 之后。因为位移是动物的天分。鸟类和鸵鸟都具有位移才能。
所以能够在超级父类Animal(动物) 的根底上,界说Bird(基类) 子类,去承继 Animal(动物) ,把Animal(动物)的一些才能转化成Bird(基类) 相关一些才能,这样就和预期需求是一致的了。
//----------------------------代码片段一----------------------------
public class Bird extends Animal {
private double flySpeed;
public void setFlySpeed(double speed) {
this.flySpeed = speed;
}
public double getFlyTime(double distance) {
return distance / flySpeed;
}
}
//----------------------------代码片段二----------------------------
public class Animal {
private double runSpeed;
public double getRunTime(double distance) {
return distance / speed;
}
public void setRunSpeed(double speed) {
this.runSpeed = speed;
}
}
//----------------------------代码片段三----------------------------
public class Swallow extends Bird {}
//----------------------------代码片段四----------------------------
public class Ostrich extends Animal{}
//----------------------------代码片段五----------------------------
public class Main {
public static void main(String[] args) {
Bird swallow = new Swallow();
Animal ostrich = new Ostrich();
swallow.setFlySpeed(120);
ostrich.setRunSpeed(120);
System.out.println("假设翱翔300公里:");
try {
System.out.println("燕子将位移: " + swallow.getFlyTime(300) + "小时。");
System.out.println("鸵鸟将位移: " + ostrich.getRunTime(300) + "小时。");
} catch (Exception err) {
System.out.println("产生错误了!");
}
}
}
成果:
3.2.4 运用准则
Java中,多态是不是违反了里氏替换准则?
那么,JAVA中,多态是不是违反了里氏替换准则呢?假设extends的意图是为了多态,而多态的条件便是Swallow(子类)掩盖偏从头界说Bird(基类)的getFlySpeed()。
为了契合LSP,应该将Bird(基类)界说为abstract,并界说getFlySpeed()(笼统办法),让Swallow(子类)从头界说getFlySpeed()。
当父类是abstract时,Bird(基类)便是不能实例化,所以也不存在可实例化的Bird(基类)方针在程序里。
//----------------------------代码片段一----------------------------
public abstract class Bird{
protected abstract double getFlySpeed();
public double getFlyTime(double distance){
return distance / getFlySpeed();
}
}
//----------------------------代码片段二----------------------------
public class Swallow extends Bird
{
protected double getFlySpeed()
{
return 100.0;
}
}
里氏替换准则和开闭准则的差异有哪些?
里氏替换准则和开闭准则的差异在于: 开闭准则大部分是面向接口编程,少部分是针对承继的,而里氏替换准则首要针对承继的,下降承继带来的杂乱度
什么时分运用里氏替换准则?
运用里氏替换准则的机遇有两个,榜首个是从头提取公共部分的办法,第二个是改动承继联络.
首要,从头提取公共部分的办法首要是把公共部分提取出来作为一个笼统基类.
而提取公共部分的机遇是代码不是许多的时分运用,提取得部分能够作为一个规划工具.
然后,改动承继联络首要是从父子联络变为派遣联络或兄弟联络,能够把它们的一些公有特性提取到一个笼统接口,再别离完结.详细能够看 #3.2.1 UML图例
3.3 依靠倒置准则
第三个规划准则是里氏替换准则,里氏替换准则简称DIP,正如英文界说的那样Dependence Inversion Principle,Abstractions should not depend on details. Details should depend on abstractions,笼统不依靠于细节,而细节依靠于笼统。高层模块不能直接依靠低层模块,而是经过接口或笼统的办法去完结。
从界说也就能够看出来,依靠倒置准则是为了下降类或模块的耦合性,发起面向接口编程,能下降工程保护本钱,下降因为类或完结产生改动带来的修正本钱,进步代码安稳性。
比方小木箱在组件化规划傍边,会员模块、订单模块和用户模块不应该直接依靠根底渠道组件数据库、网络和核算组件等。
而应该从会员模块、订单模块和用户模块抽取BaseModule和中心件等模块,横向依靠根底渠道组件BaseModule和中心件,去完结模块与模块之间的一些拜访与跳转,这样层级才会更明晰。
依靠倒置准则中心思维是面向接口编程,因为假设面向完结类,完结类假设产生改动,那么依靠完结类的完结办法和功用都会产生蝴蝶效应。
提出问题
小木箱刚拿到驾照,预备在电动车、新动力、汽油车三类型进行购车,于是拿沃尔沃、宝马、特斯拉进行测验,请用代码让这三辆轿车主动跑起来?
分析问题
假设小木箱想把跑起来的主动驾驭代码,复用给其他驾驭者,代码的健壮性怎样?
处理问题
能够参阅UML图例、Good Code、Bad Code和考虑复盘。
3.3.1 UML图例
3.3.2 Bad Code
下面代码比较劣质的原因在于主动驾驭才能与驾驭者高耦合度,假设想让其他驾驭者运用主动驾驭系列的车,那么驾驭者必须将车型实例从头传给其他驾驭者,没有做到真实意义上的插拔式注册,换个驾驭者就不成立了。
//----------------------------代码片段一----------------------------
public class BMW {
public void autoRun() {
System.out.println("BMW is running!");
}
}
//----------------------------代码片段二----------------------------
public class Tesla {
public void autoRun() {
System.out.println("Tesla is running!");
}
}
//----------------------------代码片段三----------------------------
public class Volvo {
public void autoRun() {
System.out.println("Volvo is running!");
}
}
//----------------------------代码片段四----------------------------
public class AutoDriver {
public void autoDrive(Tesla tesla) {
tesla.autoRun();
}
public void autoDrive(BMW bm) {
bm.autoRun();
}
public void autoDrive(Volvo volvo) {
volvo.autoRun();
}
}
//----------------------------代码片段四----------------------------
public class Main {
public static void main(String[] args) {
Tesla tesla = new Tesla();
BMW bm = new BMW();
Volvo volvo = new Volvo();
AutoDriver driver = new AutoDriver();
driver.autoDrive(tesla);
driver.autoDrive(bm);
driver.autoDrive(volvo);
}
}
成果:
3.3.3 Good Code
那么,正确完结办法是怎样的呢? 首要要界说一个主动驾驭接口IAutoDriver。因为主动驾驭,新动力比方说像宝马、特斯拉、沃尔沃都有完结主动驾驭才能。
可是比方说像红旗、长城不是一个主动驾驭的完结者。
那对主动驾驭接口IAutoDriver,假设你有主动驾驭才能,那么你就去完结IAutoDriver,去重写autoDrive(主动驾驭)的才能。不然,就不完结主动驾驭IAutoDriver接口。
对 AutoDriver 的话,驾驭者是去经过依靠倒置准则,把宝马、特斯拉、沃尔沃主动驾驭形式接口IAutoCar传进来,经过autoRun开启主动驾驭形式。
autoRun是差异了主动驾驭仍是一般驾驭形式。详细的代码办法很简略,首要 new一个宝马实例,然后去完结主动驾驭接口 IAutoCar,终究把宝马实例传给 AutoDriver,完结主动驾驭的办法,特斯拉、沃尔沃也是这样的。
关于主动驾驭技能,不关怀驾驭的什么车,宝马、特斯拉、沃尔沃仍是群众,只关怀你是完结了IAutoDriver接口。只关怀你是否有autoDrive(主动驾驭)才能。
假设有主动驾驭才能,运用者就直接调用autoDrive(主动驾驭)才能。详细的怎样完结呢?是AutoDriver的完结类IAutoDriver决定的,这便是依靠倒置准则,不依靠详细的完结,只调IAutoCar接口办法挑选主动驾驭形式autoRun即可.
//----------------------------代码片段一----------------------------
public interface IAutoCar {
public void autoRun();
}
//----------------------------代码片段二----------------------------
public class BMW implements IAutoCar{
@Override
public void autoRun() {
System.out.println("BMW is running!");
}
}
//----------------------------代码片段三----------------------------
public class Tesla implements IAutoCar {
@Override
public void autoRun() {
System.out.println("Tesla is running!");
}
}
//----------------------------代码片段四----------------------------
public class Volvo implements IAutoCar{
@Override
public void autoRun() {
System.out.println("Volvo is running!");
}
}
//----------------------------代码片段五----------------------------
public interface IAutoDriver {
public void autoDrive(IAutoCar car);
}
//----------------------------代码片段六----------------------------
public class AutoDriver implements IAutoDriver{
@Override
public void autoDrive(IAutoCar car) {
car.autoRun();
}
}
//----------------------------代码片段六----------------------------
public class Main {
public static void main(String[] args) {
IAutoDriver driver = new AutoDriver();
driver.autoDrive(new Tesla());
driver.autoDrive(new BMW());
driver.autoDrive(new Volvo());
}
}
成果:
3.3.4 运用准则
在简略工厂规划形式和战略规划形式,都是运用依靠倒置准则进行注入,不过简略工厂规划形式, 运用的是接口办法注入, 而战略规划形式运用的是构造函数注入,这一块后文详细介绍。
3.4 单一责任准则
第四个规划准则是单一责任准则,单一责任准则简称SRP, 正如英文The Single Responsibility Principle界说的那样,A class should have one, and only one, reason to change。
单一责任指的是一个类只能因为一个理由被修正,一个类只做一件事。不要规划大而全的类,要规划粒度小、功用单一的类。
类的功用要有界限。单一准则要求类要高内聚,低耦合。意思是为了规避代码冗余,无关责任、无关功用的办法和方针不要引进类里边。
因为假设一个类承当的责任过多,就等于把这些责任耦合在一起,一个责任的改动或许会削弱或许抑制这个类完结其他责任的才能。
这种耦合会导致软弱他的规划,当改动产生时,规划会遭受到意想不到的损坏;软件规划真实要做的许多内容便是发现责任并把那些责任彼此别离。
比方去银行取钱,取钱的类不应该包含打印发票,取钱的类只管取钱动作,打印发票功用,需求新建类完结。意图是下降类的杂乱度,进步阅览性,下降代码改动形成的危险。
再比方Android里边Activity过于臃肿会让感觉很头大,MVP、MVVM、MVP和MVI等架构都是为了让Activity变得责任单一。
提出问题:
教师去网上收购“ 三国演义 ”、“ 红楼梦 ”、“ 三国演义 ”、“ 西游记 ”各一本。
已知“ 红楼梦 ”50元/本,“ 三国演义 ”40元/本,“ 西游记 ”30元/本,“ 水浒传 ”20元/本。
假设“ 红楼梦 ” 8 折促销,“ 西游记 ”6 折促销,依据书的价格,求一切图书的总价格。
分析问题:
假设收购1000本书本,单品扣头策阅或许不相同,假设单品价格跟着单品购买数量改动,那么购物车价格条件一旦改动,购物车代码会因而胀大,然后影响代码可保护性,怎样处理这种问题?
3.4.1 UML图例
3.4.2 Bad Code
这段坏滋味的代码问题就在于: 购物车掺杂了价格核算功用,购物车正常只关怀对产品的CRUD才能,假设有一天,价格核算办法改动,那这儿就需求动购物车代码,购物车改动会引起办法改动,然后带来危险。
//----------------------------代码片段一----------------------------
public class WoodBook {
private String name;
private double price;
public WoodBook(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
//----------------------------代码片段二----------------------------
public class ShoppingCart {
private List<WoodBook> list = new ArrayList<>();
public void addBook(WoodBook book) {
list.add(book);
}
public double checkOut() {
double total = 0;
for (WoodBook book : list) {
if ("红楼梦".equals(book.getName())) {
total = total + book.getPrice() * 0.8;
} else if ("西游记".equals(book.getName())) {
total = total + book.getPrice() * 0.6;
} else {
total = total + book.getPrice();
}
}
return total;
}
}
//----------------------------代码片段三----------------------------
public class Main {
public static void main(String[] args) {
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.addBook(new WoodBook("红楼梦",50));
shoppingCart.addBook(new WoodBook("三国演义",40));
shoppingCart.addBook(new WoodBook("西游记",30));
shoppingCart.addBook(new WoodBook("水浒传",20));
double total = shoppingCart.checkOut();
System.out.println("一切图书价格为:"+total);
}
}
3.4.3 Good Code
正确的办法: 首要核算价格的逻辑,交给接口完结,购物车只关怀价格核算的成果,并将成果返回即可。然后核算价格接口交给调用方完结,运用者不关怀红楼梦和西游记价格扣头策阅仍是0扣头策阅,终究需求假设产生改动,那么只需求更改调用方完结逻辑即可。
//----------------------------代码片段一----------------------------
public class WoodBook {
private String name;
private double price;
public WoodBook(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
//----------------------------代码片段二----------------------------
public class DefaultDiscountStrategy implements DiscountStrategy {
@Override
public double discount(List<WoodBook> list) {
double total = 0;
for (WoodBook book : list) {
total = total + book.getPrice();
}
return total;
}
}
//----------------------------代码片段三----------------------------
public class SingleDiscountStrategy implements DiscountStrategy {
@Override
public double discount(List<WoodBook> list) {
double total = 0;
for (WoodBook book : list) {
if ("西游记".equals(book.getName())) {
total = total + book.getPrice() * 0.6;
} else if ("红楼梦".equals(book.getName().toString())) {
total = total + book.getPrice() * 0.8;
}else {
total = total + book.getPrice() ;
}
}
return total;
}
}
//----------------------------代码片段四----------------------------
public class ShoppingCart {
private List<WoodBook> list = new ArrayList<>();
private DiscountStrategy discountStrategy;
public void addBook(WoodBook book) {
list.add(book);
}
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double checkOut() {
if (discountStrategy == null) {
discountStrategy = new DefaultDiscountStrategy();
}
return discountStrategy.discount(list);
}
}
//----------------------------代码片段五----------------------------
public interface DiscountStrategy {
double discount(List<WoodBook> list);
}
//----------------------------代码片段六----------------------------
public class Main {
public static void main(String[] args) {
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.addBook(new WoodBook("红楼梦",50));
shoppingCart.addBook(new WoodBook("三国演义",40));
shoppingCart.addBook(new WoodBook("西游记",30));
shoppingCart.addBook(new WoodBook("水浒传",20));
shoppingCart.setDiscountStrategy(new SingleDiscountStrategy());
double total = shoppingCart.checkOut();
System.out.println("一切图书价格为:"+total);
}
}
成果:
3.4.4 考虑复盘
关于单一责任准则咱们有四个问题需求考虑
问题一: 怎样判断类的责任是否满意单一?
判断类的责任是否满意单一有五条规矩:
规矩一: 假设类中的代码行数、函数或特点过多,会影响代码的可读性和可保护性,那么咱们就需求考虑对类进行拆分;
规矩二: 假设类依靠的其他类过多,或许依靠类的其他类过多,不契合高内聚、低耦合的规划思维,那么咱们就需求考虑对类进行拆分;
规矩三: 假设私有办法过多,咱们就要考虑能否将私有办法独立到新的类中,那么咱们就设置为 public 办法,供更多的类运用,然后进步代码的复用性;
规矩四: 假设比较难给类起一个适宜姓名,很难用一个事务名词归纳,或许只能用一些笼统的Manager、Context 之类的词语来命名,那么这就阐明类的责任界说得或许不行明晰
规矩五: 假设类中许多的办法都是会集操作类中的某几个特点,比方: 在 UserInfo 比方中,假设一半的办法都是在操作 address 信息,那么能够考虑将这几个特点和对应的办法拆分出来
问题二: 类的责任是否规划得越单一越好?
类的责任单一性标准有四方面。
榜首方面,单一责任准则经过防止规划大而全的类,防止将不相关的功用耦合在一起,来进步类的内聚性。
第二方面,类责任单一,类依靠的和被依靠的其他类也会变少,削减了代码的耦合性,以此来完结代码的高内聚、低耦合。
第三方面,假设拆分得过细,实践上会适得其反,反倒会下降内聚性,也会影响代码的可保护性。
第四方面,依据不同的场景对某个类或模块单一责任的判断是不同的,不能为了拆分而拆分,形成过度规划,难以保护。
问题三: 单一责任准则为什么要这么规划?
那么单一责任准则为什么要这么规划?因为假设一个类承当的责任过多,即耦合性太高一个责任的改动或许会影响到其他的责任。
问题四: Hook违反了单一责任准则吗?
那么,Hook违反了单一责任准则吗?Hook突破了Java层OOP体系层规划理念,也就违反了单一责任准则。Hook虽好,不主张广泛运用,因为在开发进程中或许导致依靠不明晰、命名冲突、来源不明晰等问题。
3.5 接口阻隔准则
第五个准则是接口阻隔准则,接口阻隔准则指的是接口阻隔准则是指客户端不应该依靠于它不需求的接口。接口阻隔准则简称ISP,正如英文界说的那样interface-segregation principle,Clients should not be forced to depend upon interfaces that they do not use. 客户端不应该被强迫依靠它不需求的接口。其间的 “客户端”,能够了解为接口的调用者或许运用者。
接口阻隔准则是尽量将臃肿巨大的接口颗粒度拆得更细。和单一准则类似,一个接口,包括的责任完结的功用尽量简略单一,只跟接口自身想完结的功用相关,不能把别人干的活也包括进来,让完结者只关怀接口独立单元办法。
我在架构组规划对外的 API 或对外才能,接口干的责任,要十分清晰的,接口不能做与接口无关作业或躲藏逻辑,一个类对一个类依靠应树立在最小接口依靠根底之上。
提出问题
小木箱是一名AndroidDev也是一名DevopsDev,请用代码分类打印标记小木箱的技能树。
分析问题
首要将技能树悉数存放到技能清单IDev,然后让AndroidDev和DevopsDev别离完结技能清单IDev,终究在AndroidDev和DevopsDev匹配的技能树打印标记。
处理问题
能够参阅UML图例、Good Code、Bad Code和接口阻隔运用准则。
3.5.1 UML图例
3.5.2 Bad Code
比方小木箱做了AndroidDev和DevopsDev两份简历,而AndroidDev简历和DevopsDev简历所具有的技能栈又各不相同,但归档在小木箱同一份IDev技能树清单里边。
假设小木箱把AndroidDev简历和DevopsDev简历完结技能树清单接口,那么势必会导致AndroidDev简历既有Devops简历也有AndroidDev技能树,DevopsDev简历既有DevopsDev技能树也有AndroidDev技能树。
假设有一天小木箱技能树清单接口技能产生相应的改动,那么很简略给两份简历带来一些危险和改动。
//--------------------------------代码块一---------------------------------
public interface IDev {
void framework();
void ci2cd();
void jetpack();
void java();
}
//--------------------------------------代码块二---------------------------------------
public class AndroidDev implements IDev{
@Override
public void framework() {
System.out.println("CrazyCodingBoy is a Android developer and he knows framework");
}
@Override
public void ci2cd() {}
@Override
public void jetpack() {
System.out.println("CrazyCodingBoy is a Android developer and he knows jetpack");
}
@Override
public void java() {
System.out.println("CrazyCodingBoy is a Android developer and he knows java");
}
}
//--------------------------------------代码块三---------------------------------------
public class DevopsDev implements IDev {
@Override
public void framework() {}
@Override
public void ci2cd() {
System.out.println("CrazyCodingBoy is a Devops developer and he knows CI and CD");
}
@Override
public void jetpack() {}
@Override
public void java() {
System.out.println("CrazyCodingBoy is a Devops developer and he knows java");
}
}
//--------------------------------------代码块四---------------------------------------
public class Main {
public static void main(String[] args) {
AndroidDev androidDev = new AndroidDev();
DevopsDev devopsDev = new DevopsDev();
androidDev.framework();
androidDev.jetpack();
devopsDev.ci2cd();
androidDev.java();
devopsDev.java();
// TODO: delete 无效空完结
androidDev.ci2cd();
devopsDev.framework();
devopsDev.jetpack();
}
成果:
3.5.3 Good Code
接口阻隔准则是把臃肿巨大的IDev技能树清单接口,拆分红力度更小的ICi2cd、IFramework、IJetpack和IJava接口,进步整个体系和接口的一个灵敏性和可保护性,一起进步整个体系内聚性,削减对外交互。
ICi2cd只关怀小木箱CI/CD的研发才能,谁想持有这个才能就交给谁去完结,不同的技能树,交给不同的去完结自己的才能。
不然,IDev接口功用产生改动,就得去改AndroidDev和DevopsDev的逻辑。
假设代码臃肿,代码量大,那么简略手抖或改了不应改的,形成线上事故。
假设经过接口或模块阻隔办法完结,那么就能够下降修正本钱。
//--------------------------------代码块一---------------------------------
public interface ICi2cd {
void ci2cd();
}
//--------------------------------------代码块二---------------------------------------
public interface IFramework {
void framework();
}
//--------------------------------------代码块三---------------------------------------
public interface IJetpack {
void jetpack();
}
//--------------------------------------代码块四---------------------------------------
public interface IJava {
void java();
}
//--------------------------------------代码块五---------------------------------------
public class AndroidDev implements IFramework , IJetpack , IJava {
@Override
public void framework() {
System.out.println("CrazyCodingBoy is a Android developer and he knows framework");
}
@Override
public void jetpack() {
System.out.println("CrazyCodingBoy is a Android developer and he knows jetpack");
}
@Override
public void java() {
System.out.println("CrazyCodingBoy is a Android developer and he knows java");
}
}
//--------------------------------------代码块六---------------------------------------
public class DevopsDev implements ICi2cd , IJava {
@Override
public void ci2cd() {
System.out.println("CrazyCodingBoy is a Devops developer and he knows CI and CD");
}
@Override
public void java() {
System.out.println("CrazyCodingBoy is a Devops developer and he knows java");
}
}
//--------------------------------------代码块七---------------------------------------
public class Main {
public static void main(String[] args) {
AndroidDev androidDev = new AndroidDev();
DevopsDev devopsDev = new DevopsDev();
androidDev.framework();
androidDev.jetpack();
androidDev.java();
devopsDev.ci2cd();
devopsDev.java();
}
}
成果:
3.5.4 考虑复盘
接着咱们聊聊考虑复盘,考虑复盘分为两方面,榜首方面是接口阻隔准则和单一责任准则的差异?第二方面接口阻隔准则长处。
接口阻隔准则和单一责任准则的差异?
接口阻隔准则和单一责任准则的差异有两个,榜首,单一责任准则指的是类、接口和办法的责任是单一的,强调的是责任,也便是说在一个接口里,只要责任是单一的,有10个办法也是能够的。
第二,接口阻隔准则指的是在接口中的办法尽量越来越少,接口阻隔准则的条件必须先契合单一责任,在单一责任的条件下,接口尽量是单一接口。
接口阻隔准则长处
接口阻隔准则长处有三个。
榜首,躲藏完结细节
第二,下降耦合性
第三,进步代码的可读性
3.6 最小常识准则
第六个规划准则是最小常识准则,最小常识准则简称LOD,正如英文界说的那样Law of Demeter
,a module should not have knowledge of the inner details of the objects it manipulates 。不应有直接依靠联络的类,不要有依靠;
有依靠联络的类之间,尽量只依靠必要的接口。最小常识准则是期望削减类之间的耦合,让类越独立越好,每个类都应该少了解体系的其他部分,一旦产生改动,需求了解这一改动的类就会比较少。
最小常识准则和单一责任的意图都是完结高内聚低耦合,可是动身的视点不相同,单一责任是从自身供给的功用动身,最小常识准则是从联络动身。
提出问题
假设咱们把一个方针看作是一个人,那么要完结“一个人应该对其他人有最少的了解”,做到两点就满意了: 榜首点,只和直接的朋友沟通; 第二点,削减对朋友的了解。下面就详细说说怎样做到这两点。
最小常识准则还有一个英文解说是:talk only to your immediate friends(只和直接的朋友沟通)。
分析问题
什么是朋友呢?每个方针都必定会与其他的方针有耦合联络,两个方针之间的耦合就会成为朋友联络。
那么什么又是直接的朋友呢?呈现在成员变量、办法的输入输出参数中的类便是直接的朋友。最小常识准则要求只和直接的朋友通讯。
处理问题
能够参阅UML图例、Good Code、Bad Code和最小常识准则运用准则。
3.6.1 UML图例
3.6.2 Bad Code
很简略的比方:教师让班长清点全班同学的人数。这个比方中总共有三个类:教师Teacher、班长GroupLeader和学生Student。
在这个比方中,咱们的Teacher有几个朋友?两个,一个是GroupLeader,它是Teacher的command()办法的入参;另一个是Student,因为在Teacher的command()办法体中运用了Student。
那么Teacher有几个是直接的朋友?依照直接的朋友的界说
呈现在成员变量、办法的输入输出参数中的类便是直接的朋友
只要GroupLeader是Teacher的直接的朋友。
Teacher在command()办法中创立了Student的数组,和非直接的朋友Student产生了沟通,所以,上述比方违反了最小常识准则。
办法是类的一个行为,类居然不知道自己的行为与其他的类产生了依靠联络,这是不允许的,严峻违反了最小常识准则!
//--------------------------------------代码块一---------------------------------------
public interface IStudent {
}
//--------------------------------------代码块二---------------------------------------
public class Student implements IStudent {}
//--------------------------------------代码块三---------------------------------------
public interface IGroupLeader {
void count(List<Student> students);
}
//--------------------------------------代码块四---------------------------------------
public interface IGroupLeader {
void count(List<Student> students);
}
//--------------------------------------代码块五---------------------------------------
public class GroupLeader implements IGroupLeader{
@Override
public void count(List<Student> students) {
System.out.println("The number of students attending the class is: " + students.size());
}
}
//--------------------------------------代码块六---------------------------------------
public interface ITeacher {
void command(IGroupLeader groupLeader);
}
//--------------------------------------代码块七---------------------------------------
public class Teacher implements ITeacher{
@Override
public void command(IGroupLeader groupLeader) {
List<Student> allStudent = new ArrayList<>();
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
groupLeader.count(allStudent);
}
}
//--------------------------------------代码块八---------------------------------------
public class Main {
public static void main(String[] args) {
ITeacher teacher = new Teacher();
teacher.command(new GroupLeader());
}
}
成果:
3.6.3 Good Code
咱们打断学生和GroupLeader联络,直接的联络每个类都只和直接的朋友沟通,有效削减了类之间的耦合
//--------------------------------------代码块一---------------------------------------
public interface IStudent { }
//--------------------------------------代码块二---------------------------------------
public class Student implements IStudent {}
//--------------------------------------代码块三---------------------------------------
public interface IGroupLeader {
void count();
}
//--------------------------------------代码块四---------------------------------------
public class GroupLeader implements IGroupLeader {
private List<Student> students;
public GroupLeader(List<Student> students) {
this.students = students;
}
@Override
public void count() {
System.out.println("The number of students attending the class is: " + students.size());
}
}
//--------------------------------------代码块五---------------------------------------
public interface ITeacher {
void command(IGroupLeader groupLeader);
}
//--------------------------------------代码块六---------------------------------------
public class Teacher implements ITeacher {
@Override
public void command(IGroupLeader groupLeader) {
groupLeader.count();
}
}
//--------------------------------------代码块七---------------------------------------
public class Main {
public static void main(String[] args) {
ITeacher teacher = new Teacher();
List<Student> allStudent = new ArrayList(4);
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
allStudent.add(new Student());
teacher.command(new GroupLeader(allStudent));
}
}
成果:
3.6.4 运用准则
最少常识准则的运用准则有6个。
榜首,在类的区分上,应当创立弱耦合的类,类与类之间的耦合越弱,就越有利于完结可复用的方针。 第二,在类的结构规划上,每个类都应该下降成员的拜访权限。 第三,在类的规划上,只要有或许,一个类应当规划成不变的类。 第四,在对其他类的引证上,一个方针对其他类的方针的引证应该降到最低。 第五,尽量限制局部变量的有效范围,下降类的拜访权限。
第六,谨慎运用Serializable。
3.7 组成复用准则
终究一个准则是组成复用准则。组成复用准则简称CARP,正如英文界说的那样Composite/Aggregate Reuse Principle, try to use composite/aggregate *, *组成复用准则要求咱们在软件规划的进程中,尽量不要经过承继办法完结功用和类的一些组合。
因为在 Java 只支持单承继的, C 、 C ++支持多承继。所以规划形式在 Java 这一块的标准,它是不发起承继来处理问题的,所以更发起是组成复用,一个类持有别的一个方针,把才能交给别的的方针去完结。
因为承继损坏了会承继复用的和损坏类的一个封装性,子类和父类耦合度会比较大,因而引荐运用组成复用准则
最小常识准则,假设因为手抖,或许会不小心改了父类,最小常识准则限制复用灵敏性,组成复用准则能够维持类的封装性,下降类与类的耦合度,进步功用的灵敏性。
组成复用准则能够将已知的方针和成员变量归入新的方针和成员变量,办法里边去调用成员变量的详细的功用。就达成了一个组成复用准则。
3.7.1 UML图例
3.7.2 Bad Code
轿车从动力的视点来说,分为电动车ETCar和汽油车PCar。
电动车ETCar和汽油车PCar有许多色彩,如: 白色、红色。
假设后期新增黄色,那么需求电动车ETCar和汽油车PCar去承继Car,并让红色车RedPCar和白色车WhiteETCar去承继电动车ETCar和汽油车PCar。承继的办法能够完结类组合,但缺陷是色彩和车型组合越多,类组合会呈N 倍递,导致类爆破。
//--------------------------------------代码块一---------------------------------------
public abstract class Car {
public abstract void move();
}
//--------------------------------------代码块二---------------------------------------
public abstract class ETCar extends Car{
}
//--------------------------------------代码块三---------------------------------------
public abstract class PCar extends Car {
}
//--------------------------------------代码块四---------------------------------------
public class RedETCar extends Car{
@Override
public void move() {
System.out.println("Red ETCar is running!");
}
}
//--------------------------------------代码块五---------------------------------------
public class RedPCar extends PCar {
@Override
public void move() {
System.out.println("Red PCar is running!");
}
}
//--------------------------------------代码块六---------------------------------------
public class WhiteETCar extends ETCar {
@Override
public void move() {
System.out.println("White ETCar is running!");
}
}
//--------------------------------------代码块七---------------------------------------
public class WhitePCar extends PCar {
@Override
public void move() {
System.out.println("White PCar is running!");
}
}
//--------------------------------------代码块八---------------------------------------
public class Main {
public static void main(String[] args) {
new RedETCar().move();
new RedPCar().move();
new WhitePCar().move();
new WhiteETCar().move();
}
}
成果:
3.7.3 Good Code
正确的办法是: 界说一个笼统基类轿车Car。轿车Car分为两种,一种是油车PCar,一种是电动车ETCar。
因为笼统基类轿车Car组成复用了IColor接口方针,所以子类油车PCar和电动车ETCar能够持有笼统基类Car的IColor接口方针。
因为IColor方针一个接口,接口有多种色彩: 白色、黑色、黄色、绿色、棕等等。
假设每添加一种色彩,那么完结IColor接口即可,不需求像Bad Code经过承继办法进行类组合,不光处理了类爆破的问题,并且处理了承继带来的高耦合坏处。因而,在类组合问题上,咱们能够利用组成复用准则处理代码冗余问题。
//--------------------------------------代码块一---------------------------------------
public interface IColor {
String getName();
}
//--------------------------------------代码块二---------------------------------------
public class RedColor implements IColor {
@Override
public String getName() {
return "Red";
}
}
//--------------------------------------代码块三---------------------------------------
public class WhiteColor implements IColor {
@Override
public String getName() {
return "White";
}
}
//--------------------------------------代码块四---------------------------------------
public abstract class Car {
private IColor color;
public abstract void move();
public IColor getColor() {
return color;
}
public Car setColor(IColor color) {
this.color = color;
return this;
}
}
//--------------------------------------代码块五---------------------------------------
public class PCar extends Car {
@Override
public void move() {
System.out.println(getColor().getName() + " "+PCar.class.getSimpleName() +" is running!" );
}
}
//--------------------------------------代码块六---------------------------------------
public class ETCar extends Car {
@Override
public void move() {
System.out.println(getColor().getName() + " "+PCar.class.getSimpleName() +" is running!" );
}
}
//--------------------------------------代码块七---------------------------------------
public class Main {
public static void main(String[] args) {
PCar pCar = new PCar();
ETCar etCar = new ETCar();
RedColor redColor = new RedColor();
WhiteColor whiteColor = new WhiteColor();
pCar.setColor(redColor).move();
pCar.setColor(whiteColor).move();
etCar.setColor(redColor).move();
etCar.setColor(whiteColor).move();
}
}
成果:
3.7.4 考虑复盘
组合和聚合到底有什么差异呢?
聚合联络的类里有别的一个类作为参数。BirdGroup类被gc之后,bird类的引证依然建在。这便是聚合。
public class BirdGroup{
public Bird bird;
public BirdGroup(Bird bird){
this.bird = bird;
}
}
组合联络的类里有别的一个类的实例化,假设Bird这个类被GC了,内部的类的引证,随之消失了,这便是组合。
public class Bird{
public Wings wings;
public Bird(){
wings = new Wings () ;
}
}
组成复用准则的长处
使体系愈加灵敏,下降类与类之间的耦合度,一个类的改动对其他类形成的影响相对较小。
组成复用准则的缺陷
损坏了包装,一起包含的类的完结细节被躲藏。
好了,七大规划准则到现在现已说完了,咱们简略的总结一下:
假设咱们觉的上面表格比较杂乱,那么用七句话总结便是:
单一责任准则告知咱们完结类要责任单一;
里氏替换准则告知咱们不要损坏承继体系;
依靠倒置准则告知咱们要面向接口编程;
接口阻隔准则告知咱们在规划接口的时分要精简略一;
最小常识准则告知咱们要下降耦合;
组成复用准则告知咱们不要经过承继办法完结功用和类组合;
而开闭准则是总纲,告知咱们要对扩展开放,对修正封闭。
四、3大规划形式
说完七大规划准则,咱们再说说3大规划形式,规划形式一般分为三种,榜首种是创立型形式,第二种是结构型形式,第三种是行为型形式。
当咱们重视类的方针,比方怎样孵化出来类的方针?怎样创立类的方针?怎样new出来类的方针?怎样保护类的方针联络?咱们就需求运用到创立型形式。
当咱们重视类与类之间的联络,如 A 跟 B 类组合或生产联络的时分。咱们就需求运用到结构型形式。
当咱们重视类某一个办法功用的一个完结,咱们就需求运用到行为型形式。
创立型形式、结构型形式和行为型形式又分为23 种,因为篇幅有限,今日首要解说创立型形式的制作者规划形式,结构型形式的适配器规划形式,行为型形式的战略规划形式和模板办法规划形式。剩下19种规划形式,小木箱将在后续文章进行解说和整理。
4.1 创立型形式
创立型形式本质上是处理类的实例化,封装了详细类的信息和躲藏了类的实例化进程。今日首要解说制作者规划形式
4.1.1 制作者规划形式
4.1.1.1 界说
制作者形式所完结的内容便是经过将多个简略方针经过一步步的组装构建出一个杂乱方针的进程。
制作者规划形式满意了单一责任准则以及可复用的技能、制作者独立、易扩展、便于操控细节危险。
但一起当呈现特别多的物料以及许多的组合后,类的不断扩展也会形成难以保护的问题。
制作者规划形式能够把重复的内容笼统到数据库中,依照需求装备。这样就能够削减代码中许多的重复。
4.1.1.2 B站视频
《重学Java规划形式》第6章:制作者形式
4.1.1.3 Bad Code
这儿咱们模仿装饰公司关于规划出一些套餐装饰服务的场景。
许多装饰公司都会给出自家的套餐服务,一般有;欧式奢华、轻奢田园、现代精约等等,而这些套餐的后边是不同的产品的组合。例如;一级&二级吊顶、多乐士涂料、圣象地板、马可波罗地砖等等,依照不同的套餐的价格选取不同的品牌组合,终究再依照装饰面积给出一个全体的报价。
这儿咱们就模仿装饰公司想推出一些套餐装饰服务,依照不同的价格设定品牌挑选组合,以到达运用制作者形式的进程。
在模仿工程中供给了装饰中所需求的物料;ceilling(吊顶)
、coat(涂料)
、floor(地板)
、tile(地砖)
,这么四项内容。(实践的装饰物料要比这个多的多)
4.1.1.3.1 代码结构
-
物料接口: Matter
- 物料接口供给了根本的信息,以保证一切的装饰资料都能够依照一致标准进行获取。
public interface Matter {
String scene(); // 场景;地板、地砖、涂料、吊顶
String brand(); // 品牌
String model(); // 类型
BigDecimal price(); // 价格
String desc(); // 描绘
}
-
吊顶(ceiling)
- 一级顶: LevelOneCeiling
public class LevelOneCeiling implements Matter {
public String scene() {
return "吊顶";
}
public String brand() {
return "装饰公司自带";
}
public String model() {
return "一级顶";
}
public BigDecimal price() {
return new BigDecimal(260);
}
public String desc() {
return "造型只做低一级,只要一个层次的吊顶,一般离顶120-150mm";
}
}
- 二级顶: LevelTwoCeiling
public class LevelTwoCeiling implements Matter {
public String scene() {
return "吊顶";
}
public String brand() {
return "装饰公司自带";
}
public String model() {
return "二级顶";
}
public BigDecimal price() {
return new BigDecimal(850);
}
public String desc() {
return "两个层次的吊顶,二级吊顶高度一般就往下吊20cm,要是层高很高,也可添加每级的厚度";
}
}
-
涂料(coat)
- 多乐士: DuluxCoat
public class DuluxCoat implements Matter {
public String scene() {
return "涂料";
}
public String brand() {
return "多乐士(Dulux)";
}
public String model() {
return "第二代";
}
public BigDecimal price() {
return new BigDecimal(719);
}
public String desc() {
return "多乐士是阿克苏诺贝尔旗下的闻名修建装饰油漆品牌,产品热销于全球100个国家,每年全球有5000万户家庭运用多乐士油漆。";
}
}
- 立邦: LiBangCoat
public class LiBangCoat implements Matter {
public String scene() {
return "涂料";
}
public String brand() {
return "立邦";
}
public String model() {
return "默许等级";
}
public BigDecimal price() {
return new BigDecimal(650);
}
public String desc() {
return "立邦始终以开发绿色产品、重视高科技、高品质为方针,以技才能量不断推动科研和开发,满意消费者需求。";
}
}
-
地板(floor)
- 德尔
public class DerFloor implements Matter {
public String scene() {
return "地板";
}
public String brand() {
return "德尔(Der)";
}
public String model() {
return "A+";
}
public BigDecimal price() {
return new BigDecimal(119);
}
public String desc() {
return "DER德尔集团是全球领先的专业木地板制造商,北京2008年奥运会家装和公装地板供货商";
}
}
- 圣象
public class ShengXiangFloor implements Matter {
public String scene() {
return "地板";
}
public String brand() {
return "圣象";
}
public String model() {
return "一级";
}
public BigDecimal price() {
return new BigDecimal(318);
}
public String desc() {
return "圣象地板是我国地板行业闻名品牌。圣象地板具有我国驰名商标、我国名牌、国家免检、我国环境标志认证等多项荣誉。";
}
}
- 地砖(tile)
public class DongPengTile implements Matter {
public String scene() {
return "地砖";
}
public String brand() {
return "东鹏瓷砖";
}
public String model() {
return "10001";
}
public BigDecimal price() {
return new BigDecimal(102);
}
public String desc() {
return "东鹏瓷砖以品质铸就品牌,科技推动品牌,口碑传播品牌为宗旨,2014年品牌价值132.35亿元,位列建陶行业榜首。";
}
}
- 马可波罗
public class MarcoPoloTile implements Matter {
public String scene() {
return "地砖";
}
public String brand() {
return "马可波罗(MARCO POLO)";
}
public String model() {
return "缺省";
}
public BigDecimal price() {
return new BigDecimal(140);
}
public String desc() {
return "“马可波罗”品牌诞生于1996年,作为国内最早品牌化的建陶品牌,以“文化陶瓷”占领市场,享有“仿古砖至尊”的美誉。";
}
}
以上便是本次装饰公司所供给的装饰装备单
,接下咱们会经过事例去运用不同的物料组合出不同的套餐服务。
public class DecorationPackageController {
public String getMatterList(BigDecimal area, Integer level) {
List<Matter> list = new ArrayList<Matter>(); // 装饰清单
BigDecimal price = BigDecimal.ZERO; // 装饰价格
// 奢华欧式
if (1 == level) {
LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
DuluxCoat duluxCoat = new DuluxCoat(); // 涂料,多乐士
ShengXiangFloor shengXiangFloor = new ShengXiangFloor(); // 地板,圣象
list.add(levelTwoCeiling);
list.add(duluxCoat);
list.add(shengXiangFloor);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(duluxCoat.price()));
price = price.add(area.multiply(shengXiangFloor.price()));
}
// 轻奢田园
if (2 == level) {
LevelTwoCeiling levelTwoCeiling = new LevelTwoCeiling(); // 吊顶,二级顶
LiBangCoat liBangCoat = new LiBangCoat(); // 涂料,立邦
MarcoPoloTile marcoPoloTile = new MarcoPoloTile(); // 地砖,马可波罗
list.add(levelTwoCeiling);
list.add(liBangCoat);
list.add(marcoPoloTile);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelTwoCeiling.price()));
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
price = price.add(area.multiply(marcoPoloTile.price()));
}
// 现代精约
if (3 == level) {
LevelOneCeiling levelOneCeiling = new LevelOneCeiling(); // 吊顶,二级顶
LiBangCoat liBangCoat = new LiBangCoat(); // 涂料,立邦
DongPengTile dongPengTile = new DongPengTile(); // 地砖,东鹏
list.add(levelOneCeiling);
list.add(liBangCoat);
list.add(dongPengTile);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(levelOneCeiling.price()));
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(liBangCoat.price()));
price = price.add(area.multiply(dongPengTile.price()));
}
StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
"装饰清单" + "\r\n" +
"套餐等级:" + level + "\r\n" +
"套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
"房子面积:" + area.doubleValue() + " 平米\r\n" +
"资料清单:\r\n");
for (Matter matter: list) {
detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
}
return detail.toString();
}
}
- 测验入口: Main
public class Main {
public static void main(String[] args) {
DecorationPackageController decoration = new DecorationPackageController();
// 奢华欧式
System.out.println(decoration.getMatterList(new BigDecimal("132.52"),1));
// 轻奢田园
System.out.println(decoration.getMatterList(new BigDecimal("98.25"),2));
// 现代精约
System.out.println(decoration.getMatterList(new BigDecimal("85.43"),3));
}
}
总结:
- 首要这段代码所要处理的问题便是接收入参;装饰面积(area)、装饰等级(level),依据不同类型的装饰等级挑选不同的资料。
- 其次在完结进程中能够看到每一段
if
块里,都包含着不同的资料(吊顶,二级顶、涂料,立邦、地砖,马可波罗),终究生成装饰清单和装饰本钱。 - 终究供给获取装饰详细信息的办法,返回给调用方,用于知道装饰清单。
4.1.1.3.2 输出成果
-------------------------------------------------------
装饰清单
套餐等级:1
套餐价格:198064.39 元
房子面积:132.52 平米
资料清单:
吊顶:装饰公司自带、二级顶、平米价格:850 元。
涂料:多乐士(Dulux)、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。
-------------------------------------------------------
装饰清单
套餐等级:2
套餐价格:119865.00 元
房子面积:98.25 平米
资料清单:
吊顶:装饰公司自带、二级顶、平米价格:850 元。
涂料:立邦、默许等级、平米价格:650 元。
地砖:马可波罗(MARCO POLO)、缺省、平米价格:140 元。
-------------------------------------------------------
装饰清单
套餐等级:3
套餐价格:90897.52 元
房子面积:85.43 平米
资料清单:
吊顶:装饰公司自带、一级顶、平米价格:260 元。
涂料:立邦、默许等级、平米价格:650 元。
地砖:东鹏瓷砖、10001、平米价格:102 元。
4.1.1.4 Good Code
工程结构
├── Builder.java
├── DecorationPackageMenu.java
├── IMenu.java
├── Main.java
├── ceiling
│ ├── LevelOneCeiling.java
│ ├── LevelTwoCeiling.java
│ └── Matter.java
├── coat
│ ├── DuluxCoat.java
│ └── LiBangCoat.java
├── floor
│ ├── DerFloor.java
│ └── ShengXiangFloor.java
└── tile
├── DongPengTile.java
└── MarcoPoloTile.java
制作者模型结构
工程中有三个中心类和一个测验类,中心类是制作者形式的详细完结。与ifelse
完结办法相比,多出来了两个二外的类。详细功用如下;
-
Builder
,制作者类详细的各种组装由此类完结。 -
DecorationPackageMenu
,是IMenu
接口的完结类,首要是承载制作进程中的填充器。相当于这是一套承载物料和创立者中心联接的内容。
好,那么接下来会别离解说几个类的详细完结
界说装饰包接口
public interface IMenu {
IMenu appendCeiling(Matter matter); // 吊顶
IMenu appendCoat(Matter matter); // 涂料
IMenu appendFloor(Matter matter); // 地板
IMenu appendTile(Matter matter); // 地砖
String getDetail(); // 明细
}
- 接口类中界说了填充各项物料的办法;
吊顶
、涂料
、地板
、地砖
,以及终究供给获取悉数明细的办法。
装饰包完结
public class DecorationPackageMenu implements IMenu {
private List<Matter> list = new ArrayList<Matter>(); // 装饰清单
private BigDecimal price = BigDecimal.ZERO; // 装饰价格
private BigDecimal area; // 面积
private String grade; // 装饰等级;奢华欧式、轻奢田园、现代精约
private DecorationPackageMenu() {
}
public DecorationPackageMenu(Double area, String grade) {
this.area = new BigDecimal(area);
this.grade = grade;
}
public IMenu appendCeiling(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("0.2")).multiply(matter.price()));
return this;
}
public IMenu appendCoat(Matter matter) {
list.add(matter);
price = price.add(area.multiply(new BigDecimal("1.4")).multiply(matter.price()));
return this;
}
public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public IMenu appendTile(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
public String getDetail() {
StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
"装饰清单" + "\r\n" +
"套餐等级:" + grade + "\r\n" +
"套餐价格:" + price.setScale(2, BigDecimal.ROUND_HALF_UP) + " 元\r\n" +
"房子面积:" + area.doubleValue() + " 平米\r\n" +
"资料清单:\r\n");
for (Matter matter: list) {
detail.append(matter.scene()).append(":").append(matter.brand()).append("、").append(matter.model()).append("、平米价格:").append(matter.price()).append(" 元。\n");
}
return detail.toString();
}
}
- 装饰包的完结中每一个办法都会了
this
,也就能够十分方便的用于接连填充各项物料。 - 一起在填充时也会依据物料核算平米数下的报价,吊顶和涂料依照平米数适量乘以常数核算。
- 终究相同供给了一致的获取装饰清单的明细办法。
制作者办法
public class Builder {
public IMenu levelOne(Double area) {
return new DecorationPackageMenu(area, "奢华欧式")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new DuluxCoat()) // 涂料,多乐士
.appendFloor(new ShengXiangFloor()); // 地板,圣象
}
public IMenu levelTwo(Double area){
return new DecorationPackageMenu(area, "轻奢田园")
.appendCeiling(new LevelTwoCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new MarcoPoloTile()); // 地砖,马可波罗
}
public IMenu levelThree(Double area){
return new DecorationPackageMenu(area, "现代精约")
.appendCeiling(new LevelOneCeiling()) // 吊顶,二级顶
.appendCoat(new LiBangCoat()) // 涂料,立邦
.appendTile(new DongPengTile()); // 地砖,东鹏
}
}
测验办法:
@Test
public void test_Builder(){
Builder builder = new Builder();
// 奢华欧式
System.out.println(builder.levelOne(132.52D).getDetail());
// 轻奢田园
System.out.println(builder.levelTwo(98.25D).getDetail());
// 现代精约
System.out.println(builder.levelThree(85.43D).getDetail());
}
成果:
-------------------------------------------------------
装饰清单
套餐等级:奢华欧式
套餐价格:198064.39 元
房子面积:132.52 平米
资料清单:
吊顶:装饰公司自带、二级顶、平米价格:850 元。
涂料:多乐士(Dulux)、第二代、平米价格:719 元。
地板:圣象、一级、平米价格:318 元。
-------------------------------------------------------
装饰清单
套餐等级:轻奢田园
套餐价格:119865.00 元
房子面积:98.25 平米
资料清单:
吊顶:装饰公司自带、二级顶、平米价格:850 元。
涂料:立邦、默许等级、平米价格:650 元。
地砖:马可波罗(MARCO POLO)、缺省、平米价格:140 元。
-------------------------------------------------------
装饰清单
套餐等级:现代精约
套餐价格:90897.52 元
房子面积:85.43 平米
资料清单:
吊顶:装饰公司自带、一级顶、平米价格:260 元。
涂料:立邦、默许等级、平米价格:650 元。
地砖:东鹏瓷砖、10001、平米价格:102 元
- 测验成果是相同的,调用办法也根本类似。可是现在的代码结构却能够让你很方便的很有调理的进行扩展事务开发。而不是像以往相同把一切代码都写到
ifelse
里边。
4.1.1.5 Source Code
制作者不拘泥于形式,制作者形式用于创立一个杂乱方针。在android中,Dialog就用到了制作者形式,第三方库的okhttp、Retrofit等
public class Dialog {
String title;
boolean mCancelable = false;
Dialog(String title,boolean mCanclable){
this.title = title;
this.mCancelable = mCanclable;
}
public void show() {
System.out.print("show");
}
static class Builder{
String title;
boolean mCancelable = false;
public Builder setCancelable(boolean flag) {
mCancelable = flag;
return this;
}
public Builder setTitle(String title) {
this.title = title;
return this;
}
public Dialog build(){
return new Dialog(this.title,this.mCancelable);
}
}
}
4.1.1.6 注意事项
长处:
客户端不比知道产品内部细节,将产品自身与产品创立进程解耦,使得相同的创立进程能够创立不同的产品方针能够愈加精细地操控产品的创立进程,将杂乱方针分门别类抽出不同的类别来,使得开发者能够愈加方便地得到想要的产品
缺陷:
产品特点之间差异很大且特点没有默许值能够指定,这种状况是没法运用制作者形式的,咱们能够试想,一个方针20个特点,彼此之间毫无相关且每个都需求手动指定,那么很显然,即使运用了制作者形式也是毫无作用
4.2 结构型形式
创立型形式本质上是处理类或方针的组合,常见的结构模型有类结构型和方针结构型。今日首要解说适配器规划形式
4.2.1 适配器规划形式
4.1.1.1 界说
适配器形式把一个类的接口变换成客户端所等待的另一种接口,然后使原本因接口不匹配而无法在一起作业的两个类能够在一起作业,是作为两个不兼容的接口之间的桥梁。
这种类型的规划形式归于结构型形式,它结合了两个独立接口的功用,适配器分为类适配器和方针适配器.
首要处理在软件体系中,常常要将一些”现存的方针”放到新的环境中,而新环境要求的接口是现方针不能满意的;
4.1.1.2 UML图例
4.1.1.3 类适配器
类适配器是经过类的承继来完结的。Adpater直接承继了Target和Adaptee中的一切办法,并进行改写,然后完结了Target中的办法。
类适配器的缺陷便是必须完结Target和Adaptee中的办法,因为Java不支持多承继,所以通常将Target规划成接口,Adapter承继自Adaptee然后完结Target接口。运用类适配器的办法来完结一下上边的用雄蜂来冒充鸭子。
咱们能够看到下面的事例雄蜂(Drone)具有蜂鸣声(beep)、转子旋转(spin_rotors)和起飞(take_off)行为,鸭子Duck具有嘎嘎叫(quack)和飞(fly)行为
那么怎样找到一个适配器让雄蜂(Drone)的蜂鸣声beep和鸭子(Duck)的嘎嘎叫(quack)适配呢
又怎样找到一个适配器让鸭子(鸭子)飞(fly)和雄蜂(Drone)的转子旋转(spin_rotors)、起飞(take_off)适配呢?
很显然雄蜂适配器(DroneAdapter)嘎嘎叫(quack)能够适配雄蜂(Drone)蜂鸣声(beep)
雄蜂适配器(DroneAdapter)嘎嘎叫(fly)也能够适配雄蜂(Drone)转子旋转(spin_rotors)和起飞(take_off)
//--------------------------------------代码块一---------------------------------------
public interface Drone {
void beep();
void spin_rotors();
void take_off();
}
//--------------------------------------代码块二---------------------------------------
public class SuperDrone implements Drone {
public void beep() {
System.out.println("Beep beep beep");
}
public void spin_rotors() {
System.out.println("Rotors are spinning");
}
public void take_off() {
System.out.println("Taking off");
}
}
//--------------------------------------代码块三---------------------------------------
public interface Duck {
public void quack();
public void fly();
}
//--------------------------------------代码块四---------------------------------------
public class DroneAdapter implements Duck {
Drone drone;
public DroneAdapter(Drone drone) {
this.drone = drone;
}
public void quack() {
drone.beep();
}
public void fly() {
drone.spin_rotors();
drone.take_off();
}
}
//--------------------------------------代码块五---------------------------------------
public class DuckTestDrive {
public static void main(String[] args) {
Drone drone = new SuperDrone();
Duck droneAdapter = new DroneAdapter(drone);
droneAdapter.quack();
droneAdapter.fly();
}
}
成果:
4.1.1.4 方针适配器
方针适配器是运用组合的办法,在Adapter中会保留一个原方针(Adaptee)的引证,适配器的完结便是讲Target中的办法派遣给Adaptee方针来做,用Adaptee中的办法完结Target中的办法
方针适配器的好处便是,Adpater只需求完结Target中的办法就好啦。现在咱们经过一个用火鸡冒充鸭子的比方来看看怎样运用适配器形式。
火鸡(Turkey)具有火鸡叫(gobble)和飞(fly)行为,鸭子(Duck)具有嘎嘎叫(quack)和飞(fly)的行为,找一个火鸡适配器(TurkeyAdapter)让鸭子(Duck)的嘎嘎叫(quack)适配火鸡(Turkey)的火鸡叫(gobble).让鸭子(Duck)的飞(fly)适配火鸡(Turkey)的飞(fly),只要把火鸡(Turkey)的方针传给火鸡适配器(TurkeyAdapter)即可.不改动野火鸡(WildTurkey)火鸡叫(gobble)和飞(fly)的行为.一起,不改动绿头鸭(MallardDuck)的嘎嘎叫(quack) 和飞(fly)的行为.
//--------------------------------------代码块一---------------------------------------
public interface Duck {
public void quack();
public void fly();
}
//--------------------------------------代码块二---------------------------------------
public interface Turkey {
public void gobble();
public void fly();
}
//--------------------------------------代码块三---------------------------------------
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
turkey.fly();
}
}
//--------------------------------------代码块四---------------------------------------
public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
//--------------------------------------代码块五---------------------------------------
public class MallardDuck implements Duck {
public void quack() {
System.out.println("Quack");
}
public void fly() {
System.out.println("I'm flying");
}
}
//--------------------------------------代码块六---------------------------------------
public class DuckTestDrive {
public static void main(String[] args) {
Duck duck = new MallardDuck();
Turkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
鸭子和火鸡有相似之处,他们都会飞,尽管飞的不远,他们不太相同的当地便是叫声不太相同,现在咱们有一个火鸡的类,有鸭子的笼统类也便是接口。
咱们的适配器承继自鸭子类并且保留了火鸡的引证,重写鸭子的飞和叫的办法,可是是委托给火鸡的办法来完结的。在客户端中,咱们给适配器传递一个火鸡的方针,就能够把它当做鸭子来运用了。
成果:
4.1.1.5 Source Code
适配器形式能够用承继完结,这儿没有更高的笼统,当然也能够把Adapter的内容笼统出去,只是演示,ListView、GridView适配了Adapter类。
//界说适配器类
public class Adapter {
public void getView(int i){
System.out.println("给出View"+i);
}
}
//ListView 承继了Adapter
public class ListView extends Adapter{
public void show(){
System.out.print("循环显现View");
for(int i=0;i<3;i++){
getView(i);
}
}
}
//GridView承继了Adapter
public class GridView extends Adapter{
public void show(){
...
getView(i);
}
}
在android中,ListView、RecyclerView都是用了适配器形式,ListView适配了Adapter,ListView只管ItemView,不论详细怎样展现,Adapter只管展现。就像读卡器,读卡器作为内存和电脑之间的适配器。
4.1.1.6 注意事项
适配器形式的长处:
-
将方针类和适配者类解耦,经过引进一个适配器类来重用现有的适配者类,而无须修正原有代码。
-
添加了类的通明性和复用性,将详细的完结封装在适配者类中,关于客户端类来说是通明的,并且进步了适配者的复用性。
-
灵敏性和扩展性都十分好,经过运用装备文件,能够很方便地替换适配器,也能够在不修正原有代码的根底上添加新的适配器类,彻底契合“开闭准则”。
适配器形式的缺陷:
-
过多地运用适配器,会让体系十分零乱,不易全体进行把握。比方,分明看到调用的是 A 接口,其实内部被适配成了 B 接口的完结,一个体系假设太多呈现这种状况,无异于一场灾难。因而假设不是很有必要,能够不运用适配器,而是直接对体系进行重构。
-
因为 JAVA 至多承继一个类,所以至多只能适配一个适配者类,并且方针类必须是笼统类。
-
一次最多只能适配一个适配者类,不能一起适配多个适配者。
-
方针笼统类只能为接口,不能为类,其运用有一定的局限性;
适配器形式的运用机遇:
-
在实践的开发进程中,一个接口有许多的办法,可是对应的不同类只需求重视部分办法,其他无关的办法全都完结过于繁琐,尤其是涉及的完结类过多的状况。
-
想要树立一个能够重复运用的类,用于与一些彼此之间没有太大相关的一些类,包括一些或许在将来引进的类一起作业。
如: 现有一个需求的方针接口方针 Target,界说了许多相关的办法。可是在实践运用进程只需别离重视其间部分办法,而不是悉数完结。在此场景中:被依靠的方针方针TargetObj、适配器Adapter、客户端Client等
// 方针方针:界说了许多的相关办法
public interface TargetObj {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
// 适配器:将方针接口界说的办法悉数做默许完结
public abstract class Adapter implements TargetObj {
void operation1(){}
void operation2(){}
void operation3(){}
void operation4(){}
void operation5(){}
}
// 客户端:选用匿名内部类的办法完结需求的接口即可完结适配
public class Client {
public static void main(String[] args) {
Adapter adapter1 = new Adapter() {
@Override
public void operation3() {
// 只是完结需求重视的办法即可
System.out.println("operation3")
}
}
Adapter adapter2 = new Adapter() {
@Override
public void operation5() {
// 只是完结需求重视的办法即可
System.out.println("operation5")
}
}
adapter1.operation3();
adapter2.operation5();
}
}
4.3 行为型形式
4.3.1 战略规划形式
4.1.1.1 界说
战略形式界说是一系列封装起来的一种算法,让算法与算法之间能够彼此替换。战略形式把算法委托于运用者,战略形式能够独立改动。
比方咱们要去某个当地,会依据间隔的不同(或许是依据手头经济状况)来挑选不同的出行办法(共享单车、坐公交、滴滴打车等等),这些出行办法即不同的战略。
再比方活动促销,打 9 折、打 3 折、打 7 折仍是打 8 折?涉及详细的战略挑选时分,让运用者挑选,运用者只关怀对算法的封装,我怎样样去完结算法。运用者不需求管。下面咱们就用战略规划形式完结一个图书购买体系.
4.1.2.2 Code Case
在一个图书购买体系中,首要由一些几种不同的扣头:
扣头一(NoDiscountStrategy):对有些图书没有扣头。扣头算法方针返还0作为扣头值。
扣头二(FlatRateStrategy):对有些图书供给一个固定量值为1元的扣头。
扣头三(PercentageStrategy):对有些图书供给一个百分比的扣头,比方本书价格为 20元,扣头百分比为7%,那么扣头值便是207%=1.4(元)。
//--------------------------------------代码块一---------------------------------------
public class Book {
private String name;
private DiscountStrategy strategy;
public Book(String name, DiscountStrategy strategy) {
this.name = name;
this.strategy = strategy;
}
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public void getDiscount(){
System.out.println("book name:"+ name + " ,the discount algorithm is: "+ strategy.getClass().getSimpleName()+",the discounted price is: " + strategy.calcDiscount());
}
}
//--------------------------------------代码块二---------------------------------------
public abstract class DiscountStrategy {
private double price = 0;
private int copies;
public DiscountStrategy() {}
public DiscountStrategy(double price, int copies) {
this.price = price;
this.copies = copies;
}
abstract double calcDiscount();
public double getPrice() {
return price;
}
public int getCopies() {
return copies;
}
}
//--------------------------------------代码块三---------------------------------------
public class FlatRateStrategy extends DiscountStrategy{
private int discountPrice;
public FlatRateStrategy(double price, int copies) {
super(price,copies);
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
@Override
double calcDiscount() {
return discountPrice * getCopies();
}
}
//--------------------------------------代码块四---------------------------------------
public class NoDiscountStrategy extends DiscountStrategy{
@Override
double calcDiscount() {
return 0;
}
}
//--------------------------------------代码块五---------------------------------------
public class PercentageStrategy extends DiscountStrategy{
private double discountPercent;
public PercentageStrategy(double price, int copies) {
super(price, copies);
}
public void setDiscountPercent(double discountPercent) {
this.discountPercent = discountPercent;
}
@Override
double calcDiscount() {
return getCopies() * getPrice() * discountPercent;
}
}
//--------------------------------------代码块六---------------------------------------
public class Client {
public static void main(String[] args) {
Book book1 = new Book("java design pattern", new NoDiscountStrategy());
book1.getDiscount();
FlatRateStrategy rateStrategy = new FlatRateStrategy(23.0, 5);
rateStrategy.setDiscountPrice(1);
Book book2 = new Book("java design pattern",rateStrategy);
book2.getDiscount();
System.out.println("Revise《java design pattern》discount algorithm\n:");
PercentageStrategy percentageStrategy = new PercentageStrategy(23, 5);
percentageStrategy.setDiscountPercent(0.07);
book2.setStrategy(percentageStrategy);
book2.getDiscount();
}
}
成果:
4.1.2.3 Android Code
Android中RecyclerView的比方,咱们给RecyclerView挑选布局办法的时分,便是挑选的战略形式
//假设RecyclerView 这样写
public class RecyclerView {
private Layout layout;
public void setLayout(Layout layout) {
this.layout = layout;
if(layout == "横着"){
}else if(layout == "竖着"){
}else if(layout=="格子"){
}else{
}
this.layout.doLayout();
}
}
//这样写if就许多了
//摆放的办法
public interface Layout {
void doLayout();
}
//竖着摆放
public class LinearLayout implements Layout{
@Override
public void doLayout() {
System.out.println("LinearLayout");
}
}
//网格摆放
public class GridLayout implements Layout{
@Override
public void doLayout() {
System.out.println("GridLayout");
}
}
public class RecyclerView {
private Layout layout;
public void setLayout(Layout layout) {
this.layout = layout;
this.layout.doLayout();
}
}
当然Android的源码里边动画时刻插值器,用的也是战略规划形式,代码就不贴了,咱们能够结合源码和Android规划形式之战略形式在项目中的实践运用总结文章中的UML图进行学习.
4.1.2.4 注意事项
为什么要用战略规划形式?
比方咱们有微信付出,有付出宝付出,还有银联付出和招商付出。假设逻辑都经过 if else 完结,那么 if-else 块中的代码量比较大时分,后续代码的扩展和保护就会逐渐变得十分困难且简略犯错,就算运用Switch也相同违反了:
if (微信付出) {
// 逻辑1
} else if (付出宝付出) {
// 逻辑2
} else if (银联付出) {
// 逻辑3
} else if(招商付出){
// 逻辑4
}else{
// 逻辑5
}
单一责任准则(一个类应该只要一个产生改动的原因):因为之后修正任何一个逻辑,当时类都会被修正
开闭准则(对扩展开放,对修正封闭):假设此时需求添加(删去)某个逻辑,那么不可防止的要修正本来的代码
什么时分运用战略规划形式?
-
假设在一个体系里边有许多类,它们之间的差异仅在于它们的行为,那么运用战略形式能够动态地让一个方针在许多行为中挑选一种行为。 一个体系需求动态地在几种算法中挑选一种。
-
假设一个方针有许多的行为,假设不必恰当的形式,这些行为就只好运用多重的条件挑选句子来完结。
-
不期望客户端知道杂乱的、与算法相关的数据结构,在详细战略类中封装算法和相关的数据结构,进步算法的保密性与安全性。
战略形式的优缺陷是什么?
长处:
- 战略形式供给了对“开闭准则”的完美支持,用户能够在不修正原有体系的根底上挑选算法或行为,也能够灵敏地添加新的算法或行为。
- 战略形式供给了管理相关的算法族的办法。
- 战略形式供给了能够替换承继联络的办法。
- 运用战略形式能够防止运用多重条件转移句子。
缺陷:
-
客户端必须知道一切的战略类,并自行决定运用哪一个战略类。
-
战略形式将形成产生许多战略类,能够经过运用享元形式在一定程度上削减方针的数量。
4.3.2 模板办法规划形式
4.3.2.1 界说
模版形式是说对一个履行进程进行笼统分解,经过骨架和扩展办法完结一个标准的主体逻辑和扩展。咱们许多时分,做监控渠道也都是这样的:对进程进行标准化,对改动进行界说,形成一个渠道逻辑和事务扩展,完结一个产品模版。
4.3.2.2 UML 图例
经过以下AbstractClass模板类咱们能够看出来,PrivitiveOperation1()和PrivitiveOperation2()悉数封装在TemplateMethod()笼统办法里边,TemplateMethod()笼统办法父类操控履行次序,子类负责完结即可。经过封装不变部分,扩展可变部分和提取公共部分代码,便于保护和可拓宽性。
提出问题
小木箱预备煮茶和煮咖啡,煮茶的过程有烧水、泡茶、加柠檬、倒水四个过程,而煮咖啡的过程有烧水、过滤咖啡、倒水、加牛奶四个过程,请在操控台打印煮茶和煮咖啡的履行流程。
分析问题
煮茶和煮咖啡的过程中烧水和倒水动作是重复的,能不能抽取成模板办法呢?
处理问题
能够参阅UML图例、Good Code、Bad Code和模板办法规划形式源码分析。
4.3.2.3 Bad Code
错误的编码办法:将煮茶的过程烧水→泡茶→倒水→加柠檬按次序履行,煮咖啡的过程烧水→过滤咖啡→倒水→加牛奶也按次序履行,这样的缺陷是假设过程许多,那么代码显得比较臃肿,代码保护本钱也会越来越高。
//--------------------------------------代码块一---------------------------------------
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeaBag() {
System.out.println("Steeping the tea");
}
public void addLemon() {
System.out.println("Adding Lemon");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
//--------------------------------------代码块二---------------------------------------
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
//--------------------------------------代码块三---------------------------------------
public class Barista {
public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("Making tea...");
tea.prepareRecipe();
System.out.println("Making coffee...");
coffee.prepareRecipe();
}
}
成果:
4.3.2.4 Good Code
正确的编码办法:首要将煮茶和煮咖啡一起动作烧水和倒水抽取成模板办法,并在父类履行,然后煮茶的泡茶、加柠檬过程,煮咖啡的过滤咖啡、加牛奶过程别离差异化完结即可,终究要保证四个过程履行链准确性。
//--------------------------------------代码块一---------------------------------------
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
//--------------------------------------代码块二---------------------------------------
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
//--------------------------------------代码块三---------------------------------------
public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
//--------------------------------------代码块四---------------------------------------
public class Barista {
public static void main(String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("\nMaking tea...");
tea.prepareRecipe();
System.out.println("\nMaking coffee...");
coffee.prepareRecipe();
}
成果:
4.3.2.5 Source Code
当然Android的AsyncTask也能表现模板办法规划形式,咱们能够看到execute办法内部封装了onPreExecute, doInBackground, onPostExecute这个算法结构。
用户能够依据自己的需求来在覆写这几个办法,使得用户能够很方便的运用异步使命来完结耗时操作,又能够经过onPostExecute来完结更新UI线程的作业。
//--------------------------------------代码块一---------------------------------------
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
//............................................................................
mStatus = Status.RUNNING;
// TODO: 要害模板办法
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
//--------------------------------------代码块二---------------------------------------
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// TODO: 要害履行办法
return postResult(doInBackground(mParams));
}
};
}
//--------------------------------------代码块三---------------------------------------
private Result postResult(Result result) {
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
//--------------------------------------代码块四---------------------------------------
private static class InternalHandler extends Handler {
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
//--------------------------------------代码块五---------------------------------------
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
// TODO: 要害模板办法
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
4.3.2.6 注意事项
当然模板办法假设没有整理好办法与办法的调用链联络,那么模板办法会带来代码阅览的难度,会让人觉得难以了解。
五、总结与展望
《Android架构演进 规划形式 为什么主张你一定要学透规划形式》一文首要经过5W2H全方位的解说了规划形式对Android开发的价值,然后经过UML图例、BadCode、Good Code、运用准则和考虑复盘多维度分析了7大规划准则优劣势和中心思维,终究别离对创立型形式、行为型形式和结构型形式的事例分析了三大规划形式的完结细节。
因为假设功用简略,套用规划形式搭建,反而会添加了本钱和体系的杂乱度。因而,在作业中咱们既不要生搬硬套规划形式,也不要过度去规划。咱们要依据功用需求的杂乱性规划体系。
在了解规划形式思维的根底上,小木箱强烈主张咱们结合结构源码和项目源码对每一个规划形式和规划准则,进行深度了解和考虑,终究才能针对适宜的场景和问题正确的运用。
当然许多规划形式运用场景不是一种形式的仅有完结,或许是多种形式混合完结。因而,对Android同学发散思维和事务了解深度提出严苛的要求。有的时分架构才能是倒逼的,面对杂乱的事务频频的改动,咱们要勇于不断的应战!
这也是小木箱强烈主张咱们学透规划形式很重要的原因。期望经过这篇文章能够让你意识到学会规划形式的重要性。
下一章Android架构演进 规划形式 Android常见的4种创立型规划形式会从上而下带咱们揭秘常见创立型规划形式。我是 小木箱,咱们下一篇见~
参阅资料
- 规划形式六大准则(五)—-迪米特法则
- 规划形式(十)适配器形式
- 重学 Java 规划形式:实战制作者形式「各项装饰物料组合套餐选配场景」