赣州新闻网

首页 / 科技

特斯拉电驱动系统终极拆解篇

2020-02-26 · 赣州新闻网 · 科技

前言

在面向对象的软件设计中,只有尽量降低各个模块之间的耦合度,才能提高代码的复用率,系统的可维护性、可扩展性才能提高。面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,如果把设计模式比作武功招式,那么设计原则就好比是内功心法。常用的设计原则有七个,下文将具体介绍i q s , I S M n 0。

设计原则简介

  • 单一职责原则:专注降低类的q N Y E ) P ; c复杂度,实现类要职责单一;
  • 开放关闭原则:所有面向对象原则的核心,设计要C f 1 R @ - K 7 B对扩展开发,对修改关闭;
  • 里式替换原则:实现开放关闭原O W N k X则的重要方式之一,设计不要破坏继承关系;
  • 依赖倒置原则:系统抽象化的具体实现,要求面向接口编程,是面向对象设计的主要实现机制之一;
  • 接口隔离原则:要求接口的方法尽量少,接口尽量细化;
  • 迪米特法则:降低系统的耦合度,使一个模块的修改尽量少的影响其他模块,扩展会相对容易;
  • 组合复用原则:在软件设计中,尽量使用组合/聚合而不是继承达到代码复用的目的。

这些设计原则并不说我们一定要遵循他们来r & O o e进行设计,而是根据我们的实际情况去怎么去选择使用他们,来让我们的程序做的更加的完善。

单一职责原则

解释:

就一个类而言,应该仅有一个引起它变化的原因,通俗的说,就是一个类只负责一项职责。

此原则的核心就是解耦和增强内聚性

那么为什么要使用单一职责原则:

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责| E E 1 9的能力。这种m D I Z O c r b v耦合会导致脆弱的) # 8 O n ^ S设计。

这也是他的优点,我们总结一下

优点:

(1)降低类的复杂度;

(2)提# 4 k s T C A高类的可读性,提高z q z Z系统的可维护_ e G性;

(3)降低变更引起的风险(降低对其他功能的影响)。

我们来举一些简? 7 d J R J / &单的例子来说M - A 4 | 0明一下这个单一职责原则

实例:

    //我们用动物生活来做测试
class Animal{
public void breathe(String animal)= k o p{
System.out.O B W 9 f ` println(animal+\"生活在陆地上\");
}
}
public cl~ K ` D aass Client{
publi+ , / I ~ /c sty ) - & ` 5 Y 5atic void main(String args){
Animal animal4 ) r S q = new Animal();
animal.breathe(\"羊\");
animal.breR r V 5 & v } =athe(\"牛\");
animal.breathe(\"猪\");
}
}

运行结果
羊生活在陆地上
牛生活在陆地上
猪生活在陆地上

但是问题来了,动物并不是都生活在陆地上l T 6 D I y ] -的,鱼就是生活在水中的,修改时如果遵循单一职责原则,需要将Animal类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:

class Terrestrial{
public void breathe(String@ P 2 J D k 6 animal){
System.out.println(animal+\"生活在陆地上\");
}
}
class Aquatic{
public void breathe(String animal){
System.out; + G.println(animal+\" # (生活在水里\");
}
}

public class Client{
public static void maN 2 V | Z X J %in(String args){
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe(\"羊\");
terrestrial.breathe(# # ]\"牛\");
terrestrial.breathe(\"猪\");

Aquatic aquatic = new Aquatic();
aquatic.breathe(\"鱼\");
}

运行结果:

羊生活在陆地上
牛生活在陆地上
猪生活在陆地上
鱼生活在水里

但是问题来了如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:


class Animal{
public void breathe(String animal){
if(\"鱼\".equals(ag x M S h 6 q F xnimal)){
Syst! Y Y $ x $ f N em.out.prS ] L uintln(animal+\"生活在水中\");
}else{
System.out.println(animal+U [ Y E\"生活在陆j c r r地上\");
}
}
}

public c 8 d 9lass Client{
public static void main(String args){
Animal animal = new Animal();
animal.breathe(\"羊\");
animal.breathe(\"牛\");
animal.breatha z J k D ye(\"猪\");
animal.breathe(\"鱼\");
}

可以看到,这种修改方式要简单的多。但是却存在着隐患:有一天需要将鱼分为生H 4 s H活在淡水中的鱼和生活在海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对W [ b _ B C Q 6 1调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛生活在水中”了。

这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。还有一种修改方式:


classw B n E 7 Animal{
public vob e ` F ? n 0 Uid breathe(String anim1 # + + B 6 dal){
System.out.println(animal+\"生活在陆地上\");[ v / # 7 z , O
}

public void breathe2(String animal){
System.out.pr1 - Zintln(animal+\"生活在水中\");
}
}

public class Client{
public static void main(String args){
Animal animal = new Animal();
animal.breatL k g L i P : 3he(\"牛\");
animal.breathe(\"羊\");
animal.breathe(\"猪\");
animal.breathe2(\"鱼\");
}, ) n z ` 6 F
}

可以看到,e ? 6 ) J ! ( ^ B这种修改方式没有改动原来的方法W . + e m {,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合] Y 0单一职责原则的,因为它并没有动原来方法的代码。

这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;

例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。 实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类v N A本身非常简单,否则还是遵循单一职责原. Y i a z则的好。

以上就是我所说的单一职责原则了,很多书中介绍的说它并4 # h不属于面向对象设计原则中的一种,但Q * Q u W是我认为它是,所以我就把他解释出来了。

开放* C P k E h关闭原则

开放5 , A关闭原则又称为开放封闭原则。

定义:

一个软件实体应Q u { f J a $该对扩展开放,对修改关闭,这个原则也是说,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下e p : n s : % j被扩展,换句话说,应当可以在不必修改源码R ( /的情况下改变这个模块的行为。

这句话其实刚开0 j v U F始看上去是有些矛盾的,接下来在我后边文章解释里面,我会把他解释清楚一点。

我们先用个比较好玩的例子来说一下。

西游记大家都看过,玉帝招安孙悟空的^ W % O I时候的桥段,大家还有没有印象?

当年大脑天宫的时候美猴王对玉皇大帝做了个挑战,美猴王说:皇帝轮流做,明年到我家,只叫他搬出去,将天宫让给我,对于这个挑战,太l r 3 7 ~ d B : P白金星给玉皇大帝提了个意见,我们把它招上来,给他个小官做,o p W v 4 , U他不就不闹事了?

换一句话说,不用兴师动众的,不破坏天庭的规矩这就是闭,但是收他为官,便是开,招安的方法便是符合开闭原则的,给他个‘弼马温’的官,便可以让这个系统正常不受威胁,是吧,何乐而不为? 我给大家画个图。

面向对象设计的原则一二

招安的方法的关键就是不允许更改现有的天庭秩序,但是允许将妖猴纳入现有的秩序中,从而8 s Q % . M | l L扩展了这个秩序,用面向对D R M象的语言来说:不允许更改的是系统的抽c / ! W象层,而允许扩展的是系统的实现层。

我们写一些简单的代码来进行一个完整的展示来进行一下理解。

实例:

    //书店卖书
ih y 8 N f } {nterface Books{
//书籍名称
public Sting getName();
//书籍价格
public int getPrice();
//书籍作者
public String getAuthor();
}

public class NovelBook implements Books {
private String name;

private int price;

private String author;2 d A w D , h

public NovelBook(Sp = l A J a ? ctring name, int prJ T =ic$ 4 2 ; * % 3 U :e, Sm b 6 3 Ntring author) {
this.name = nu l 0 ( C C l &ame;
this.price = price;
this.author =) V V Y , 2 $ 7 F author;
}

@Override
public String gn t ;etName(B ~ / h) {
return name;
}

@Override
public int getPrice() {
return price;
}

@Override
p5 6 ^ o o c D 1ublic SU { 9 ztring getAuthI O ~ E k 9or() {
return author;
}
}


以上的代码是数据的实现类和书籍的类别;

下面我们将要开始对书籍进行一个售卖活动:

public class BookStore {
private final static ArrayList<Books> sBookList = new ArrayList<Books>();

static {
sBookList.add(new NovelBook(\"天龙八部\", 4400, \"金庸\"));
sBookList.add(new NovelBook(\"射雕英雄传\", 7600, \"金庸\"));
sBookList.add(new NovO 7 u CelBook(\"钢铁是怎么炼成的\", 7500, \"保尔柯查金\"));
sBookList.addY 7 M A t |(new NovelBook(\"红楼梦\",{ ~ 7 , ` 3300, \"曹雪芹\1 U $ M k"));
}

public static void main(String args) throws IOException^ ) G {
NumberFormat format = NumberF u N * *Format.getCurrencyInstance();
format.setMaximumFractionDigits(2);
System.ouo L d J Tt.prin| * U . ; & ntlh D + m * k Yn(\"----书店卖出去的书籍记录如下---\");
for (Books book : sBookList) {
System.out.println(\"书籍名称:\" + book.getName()
+ \"\\t书籍作者:\" + book.getAuthor()
+ \"\\L Q O ?t书籍价格:\" + fM f Z O t Uormat.format(book.getPrice() / 100.00) + \"元\");
}
}
}

运- A p E行结果如下:

D:\\deve@ W C | 6 x elop\\JDK8\\jdk1.8.0_181X 2 C W { ( L 3\bin\\java.exe \"-p r }javaagent:D:\\develop\\IDEA\\IntelliJ IDEA 2018.2.4\\lib\\idea_rt.jar=62787:D:. Y z\\develop 3 8 : T\IDEA\\I+ Q u / ? O ]ntelliJ IDEA 2018.2.4\\bin\" -Dfile.encoding=UTF-8 -classpath D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\chs t R & _arsets.jar;D:\\develop\\JDK8\\jdk1.8.0_18L C k1\\jreN Z Y E w e\\lib\\deploy.jar;D:\\deve@ w A u a i K alop\\JDK8\\jdk o = g J l .1.8.0_18Q F ( % [ ; 6 E =1\\jre\H K S U\libm m w K q H ^ A\ext\\access-bridge-64.jar;D:\5 . g @ h V\develop\\J1  % t s ` zDK8\\jdk1.8.0_1O 3 $ W n ^ G 0 N81\\jre\\lib\\ext\\cldrdata.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\ext\\dnsns.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\ext\\jaccess.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\i 9 O d o\lib\\ext\\jfxr0 + J ` 2 ) yt.jar;j a t Z b H } 3D:\\develop\\JDK8\C G ) O Sjdk6 [ a [ * k1.8.0_181\\jre\\l~ C g  kib\\ext\\R X | S 7 H ; b hlocaledata.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\ext\\nashorn.jar;D:\\developM [ ; [ u E v } Z\\JDK8\\jdk1.8.0_181\\jre\\lib\\ext\\sunec.jar;D:\\devY 3 , n f ? o G ;elop\\JDK8\\jdk1.8.0_181\\jre\\lib\\ext\\sunjce_provider.jar;D:\\d& n M S g develoI ] M 3 D ~ z O Qp\\JDK8\\jdk1.8.0_181\\jre\\lib\\jfrm Q R e } V p 0.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\jfxswt.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\jsse.jar;D:\\develop\\JDK8\\jd{ 9 3k1.8.0_181\\jre\\lib\\management-agent.j^ Z e jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\z % A 2 u P\pl/ s Sugin.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\resources.jar;D:\\develop\\JDK8\\jdk1.8.0_181\\jre\\lib\\rt.jar;D:\; G O B I g q Adevelop\\IDEA_Workspace\\CloudCode\\out\\production\\Pattemj G 8 s IMoudle com.yldyyn.tw , ] nest.BookStore
----书店卖出去的书籍记` C h u录如下---
书籍名称:天龙八部 书籍作者:金庸 书籍价格:¥44.00元
书籍名称:射雕英雄传 书籍作者:金庸 书~ ? N 5 D籍价格:¥76.00元
书籍名称:钢铁, p n e x是怎么炼成的 书籍作者:保尔柯查金 书籍价格:¥75J z p.00元
书籍名称:红楼梦 书籍作者:曹雪芹 书籍价格:¥33.00元

Process finished with exit code 0

但是如果说现在书店0 j 8 | J q卖书的时候要求打折出售,40以上的我们要7折售卖,40一下的我们打8折。

方法有三种,第一个办法:修改接口。在Books上新增加一个方法getOnSalePrice(),专门进行打折,所有实现类实现这个方法。 但是这样修改的后果就是实现类NovelBook要修改,BookStore中的main方法也修改,同时Books作为接口应该; O l 8 & U 1 ;是稳定且可靠的,不应该经常发生变化,否则接口做为契约的作用就失去了效能,其他} 6 K a不想打折的书籍也会因为实现了书籍的接口必须打折,因此该方案被否定。

第二个办法:修改实现类。修改No7 q f s a Y +velBook 类中的方法,直接在getPrice()中实现打折处理,这个应该是大家在项I _ V . ? # * a 7目中经常使用的就是这样办法,通过cle ? j S & E o :ass文件替换的方式可以完成部分业务(或是缺陷修复)变化,但是该方法还是有缺陷的,例如采购书籍人p t ; 2 } 2 9 *员也是要看价格的,由于该方法已经实现了打折处理价格,因此采购人员看到的也是打折后的价格,这就产生了信息的蒙蔽效果,导致信息不对称/ ? A而出现决策失误的G u : R $情况。该方案也不是一个最优的方案。

第三个办法,通过扩展实现变化增加一个子类 OffNH z r x s v ZovelBook,覆写getPrice方法,高层次的模块(也就是static静态模块区)通过OE # r 3 y C @ffNovelBook类产生新的对象% R Q = S R 4 h,完成对业务变化开发任务。好办法,风险也小I s # $ M d。

public class OnSaleBook extends NovelBook {

public OnSaleBook(String name, int price, String author) {
super(name, price, author);
}

@Override
public String getName() {
rp ) 7eturn super.getName();
}

@Override
public int getPrice() {
int OnsalePrice = super.getPriS 5 gce();
int salePrce = 0;
ifd ~ I x G 7 (OnsalePrice >4000){Q , , X
salePrce = OnsalePrice *} ^ c l W C ! 70/100;
}else{
salePrce = Onsaly P 5 D 8 m C zePrice * 80/100;
}
return salePrce;
}

@OverrO H Wide
public String getAuthor() {
return super.getAuthor();
}
}

上面的代码是扩展出来的一个类,而不是在原来的类中进行的修改。

public class BookStore {
pri^ q Uvate fi- P G ,nal static ArrayList<Books> sBookList = new ArrayList<Books>();

static {
sBookList.add(new OnSaleBook(\"天龙八部\", 4400, k , 3 M d ~ ]\"金庸\"));
sBookList.add(new OnSalez k 2 ] 9Book(\"射雕英雄传\", 7600, b b n { z x\"金庸\"));
sBookList.add(new OnSaleBook(\"钢铁是怎么炼成的\", 7500, \"保尔柯查金\"));
sBookList.add(new OnSaleBook(\"红楼梦\", 3300, \"曹雪芹\"));
}

public static void ma? : n 7 ( @in(String args) throws IOExceps g f k R _ 3 P Ction {
NumberFormat fk h ] 3 i _ E Xo{ 4 vrmat = NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(2);2 K V ; !
System.out.println(8 % 8 `\"----书l ` ] ~ K 5 W s店卖出去的书籍记录如下---\");
for (Bookp } ] 7 e d xs book : sBookList) {
Sh E kystem.k / W x R ` Sout.println(\"书籍名称:\" + book.getName()
+ \"\\t书籍作者:\" + book.getAuthor()
+ \"\\t书籍价格:\" + format.format(book.getPrl . S & o | m G ice() / 100.00) + \"元\");
}
}
}

结果展n m v E 0 ^ J 4示:

----书店卖出去的书籍记录如下---
书Z 8 D籍名称:天龙八部 书籍作者:金庸 书籍价格:¥30.80元
书籍名称:射雕英雄传 书籍作者:金庸 书籍价格:¥53.20元
书籍名称:钢铁是怎么炼成的 书籍作者:保尔柯查金 书籍价格:¥52.50元
书& l y / k p籍名称:红楼梦 书籍作者:曹雪芹 书籍价格:¥26.40元

Process fini; % j 5 M $ f T xshed with exit code 0

在开闭原则中,抽象化是一个关键,解决问题的关键在于抽象化,在JAVA语言这种面向对象的语言中,可以给) u 9 , w u E 1 b系统定义出一个一劳永逸的,不再更改的抽象化的设计,此设计允许拥有无穷无尽的实现层被实现。

在JAVA语言中,可以给出U 7 ~一个或者多个抽象的JAVA类或者是JAVA接口,规定所有的具体类必须提供方法特征作为系统设计的抽象层,这个抽象层会遇见所有的可能出现的扩展,因此,在任何扩展情况下都不回, h y 4 V S去改变,这就让系统的抽象层不需要修改,从而满足开闭原则的第二条,对修改进行闭合。

同时,从抽象层里面导出一个或者多个新的具体类可以改变系= 1 H统的行为,这样就满足L C - W 7 Z R S了开闭原则的第一条。

尽管很多时候我们无法百分, . 9 j U百的做到开闭原则,但是如果向着这个方向去努力,就能够有部分的成功,这也是可以改善系统的结构的。

测试隔离细化生活文章瑞金驱动电驱系统