版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、面向对象设计的5个原则 Robert Martin在敏捷软件开发:原则,模式和实践提到了面向对象设计的5个原则,每个原则的第一个英文字母合在一起名为“SOLID”,这五个原则分别是:单一职责的原则(Single responsibility Principle),开闭原则(Open Close Principle),Liskov替换原则(Liskov substitution principle),“接口分离原则(Interface Segregation Principle),依赖倒置(Dependency Inversion principle)。当然众多软件设计大师
2、们其实提炼过更多的原则,但是从“SOLID”起步肯定是一个不赖的选择。下面我们从一个简单的程序入手,通过逐步改进程序的方法,逐步体会这些原则在软件设计中作用。1. 起步如果我们要做如图1这样一个简化的绘图程序,程序界面的上边是一个工具栏,工具栏中包含了基本图形(圆形,矩形,三角形,椭圆形)的按钮,在工具栏下方是一个画布。用户使用这个绘图程序时,只要点击工具栏中的图形按钮,就可以在画布上绘制各种对应的图形。图1. 一个极简单的绘图软件我们可以建一个名字叫画布(Canvas)的类,在类中建立以绘制各种基本图形的方法,然后由一个User类模拟用户使用这个程序绘制各种图形,如图2所示。图2.
3、 画布类绘制各种基本图形现在把上述设想变成代码,因为所有的程序都写的一个文件里,其中User类是public类,并且包含main()函数,所以程序的名称User.java。我们的第一个程序如下:class Canvas public void drawCircle() /这里只打印了一个字符串模拟实现画圆的所有步骤 System.out.println(&quo
4、t;Drew a Circle"); public void drawTriangle() System.out.println("Drew a Triangle"); public void
5、0;drawRactangle() System.out.println("Drew a Ractangle"); public void drawEllipse() System.out.println("Drew a E
6、llipse"); public class User public static void main(String args) Canvas canvas=new Canvas(); /在这里模拟用户使用软件
7、canvas.drawCircle(); canvas.drawRactangle(); 当然这个程序做了极大的简化,通过打印字符串来模拟了画图形的所有复杂步骤。2.基于单一职责的第一次改进可以看出画布类Canvas实现了各种图形的绘制程序,这就给程序的维护带来了麻烦,因为画布类需要对所有的程序员开放,任何一个“基本图形”绘制程序的修改都需要修改画布类,即使是扩充一个新的图形,也需要修改画布类,造成这种状况的原因,是程序的设计违反了“单一职责的原则(Sin
8、gle responsibility Principle)”,单一职责原则要求每个类只负责一个职责,这样对这个类的修改,只会发生在这个类所负责的职责需要改变时,例如某个类负责绘制圆的职责,只有当圆的绘制方法出现问题或需要修改,才需要修改这个类,一个设计良好的程序应该避免在修改某个类的同时需要修改其他类。解决的方法是把画布类里的各种不同图形的绘制函数放到不同的类里,这样每个类的职责就单纯了。在这个例子中,分配职责后的图形类里面只有一个方法draw(),但是这并不表示单一职责原则要求一个类里只能有一个方法。单一职责原则要求,一个类里哪怕有10个方法,它们也是为同一职责服务的。现在画布类要做的只是调
9、用具体的图形类中的相应方法,这样,如果修改某个具体的图形类(例如Rectangle类)就和不会引起画布类和其他类的修改了,如图3所示。图3. 将绘制图形的职责分配到具体的类修改后的第二个程序如下:class Cirle public void draw() /这里只打印了一个字符串模拟实现画圆的所有步骤 System.out.print
10、ln("Drew a Circle"); class Triangle public void draw() System.out.println("Drew a Triangle"); class Ractangle p
11、ublic void draw() System.out.println("Drew a Ractangle"); class Ellipse public void draw() System.out.println("Drew a Ellipse&qu
12、ot;); class Canvas /画布类中只要简单地调用图形对象的方法就可以,不再需要实现复杂的绘制步骤 public void paint(Cirle c) c.draw(); /只需要简单的调用,不需要关心细节
13、160; public void paint(Triangle t) t.draw(); /只需要简单的调用,不需要关心细节 public void paint(Ellipse e) e
14、.draw(); public class User public static void main(String args) /在这里模拟用户使用软件 Canvas canvas=new Canvas();
15、 Cirle c=new Cirle(); canvas.paint(c); Triangle t=new Triangle(); canvas.paint(t);
16、0; Ellipse e=new Ellipse(); canvas.paint(e); 3.如何做到扩充时不需要修改但是画布类Canvas可能会碰到这样的麻烦:如果有新的图形加入系统,就需要不断修改,例如,如果要画“星”,那么就要添加一个Star类,然后Canvas类要修改代码,从而可以调用Star类的方法,也就是说Canvas需要不断修改才能适应系统的扩充,我们来看看程序修改
17、的过程。首先,加入Star类:class Star public void draw() System.out.println("Drew a star"); 然后,在画布类(Canvas)中加入新的方法: public void drawStar(Star s)
18、; s.draw(); 最后在User类中模拟用户调用: Star s=new Star(); canvas.drawStar(s);完整的代码:class Cirle public void draw() /
19、这里只打印了一个字符串模拟实现画圆的所有步骤 System.out.println("Drew a Circle"); class Triangle public void draw() System.out.println("Drew a Triangle");
20、; class Ractangle public void draw() System.out.println("Drew a Ractangle"); class Ellipse public void draw()
21、 System.out.println("Drew a Ellipse"); /新加class Star public void draw() System.out.println("Drew a star"); class Canvas
22、 /画布类中只要简单地调用图形对象的方法就可以,不再需要实现复杂的绘制步骤 public voidpaint(Cirle c) c.draw(); public voidpaint(Triangle t) &
23、#160;t.draw(); public voidpaint(Ellipse e) e.draw(); /新加 public voidpaint(Star
24、160;s) s.draw(); public class User public static void main(String args) /在这里模拟用户使用软件 Canvas
25、160;canvas=new Canvas(); Cirle c=new Cirle(); canvas.paint(c); Triangle t=new Triangle();
26、60;canvas.paint(t); Ellipse e=new Ellipse(); canvas.paint(e); /新加 Star s=new St
27、ar(); canvas.paint(s); 这样就不符合“开闭原则(Open Close Principle)”,开闭原则换句话就是“对软件可以扩充但是尽量不要修改"。这个例子中,在扩充了新的图形后,Canvas就需要修改,这是因为Canvas依赖的是具体的图形,而不是抽象的图形,这就涉及了面向对象设计的另一个原则“依赖倒置(Dependency Inversion principle)”,即不依赖具体而是依赖于抽象,本例中圆形、矩形、三角形、椭圆形、星
28、形都是具体的图形,当画布类依赖于具体的图形时,画任何一种图形,都需要知道是什么类型的图形,并需要建立一个专门的方法(在本例中用了重载,虽然方法名一样,但是参数的类型是不一样的),这样图形库扩充时,修改代码就是不得不做的事情。现在通过对“圆形、矩形、三角形、椭圆形、星形”的抽象获得一个新的概念“图形(Shape)”,所有具体的图形就成为这个概念的具体实现,如图4所示。图4. 从具体的图形抽象出概念通过对具体图形的抽象,得到Shape接口,这个接口有一个draw()虚方法,所有实现这个接口的具体图形的类都必须实现这个方法,代码如下:/Shape接口是抽象的概念interface
29、;Shape public void draw();/implements Shape表示实现Shape接口class Cirle implements Shape public void draw() System.out.println("Drew a Circle");
30、class Triangle implements Shape public void draw() System.out.println("Drew a Triangle"); class Ractangle implements Shape public void draw()
31、 System.out.println("Drew a Ractangle"); class Ellipse implements Shape public void draw() System.out.println("Drew a Ellipse");
32、; 然后让画布类不依赖于具体的图形而依赖于这个抽象的概念,如图5所示。图5. 画布类依赖于抽象的图形这样就可以大大的简化画布类的代码,而且Canvas类依赖于抽象的Shape而不是具体的图形,不管系统的图形类(Circle、Rectangle、Triangle等等这些类)如何扩充或修改,画布类的程序是不需要修改的:class Canvas public void paint(Shape s) &
33、#160;s.draw(); 用户使用绘图程序的模拟代码:public class User public static void main(String args) Canvas canvas=new Canvas(); Circle
34、c=new Circle(); canvas.paint(c); /注意这里的参数类型的转变,符合Liskov替换原则 Triangle t=new Triangle(); canvas.paint(t); /注意这里的参数类型的转变,符合Liskov替换原则
35、0; 完整的代码如下,已经变得简单而且易于维护:interface Shape public void draw();/implements Shape表示实现Shape接口class Cirle implements Shape public void draw() System.o
36、ut.println("Drew a Circle"); class Triangle implements Shape public void draw() System.out.println("Drew a Triangle"); class Ractangle implem
37、ents Shape public void draw() System.out.println("Drew a Ractangle"); class Ellipse implements Shape public void draw()
38、160;System.out.println("Drew a Ellipse"); class Canvas public void paint(Shape s) s.draw(); public class User pub
39、lic static void main(String args) Canvas canvas=new Canvas(); Cirle c=new Cirle(); canvas.paint(c); /注意这里的参数类型的转变,
40、符合Liskov替换原则 Triangle t=new Triangle(); canvas.paint(t); /注意这里的参数类型的转变,符合Liskov替换原则 4. 什么是Liskov替换注意上面代码中,不管是什么类型图形的对象都可以作为参数传递到画布类的paint方法中,在参数的传递过程当中
41、,具体的图形对象的类型会转变成Shape类型,这符合“Liskov替换原则(Liskov substitution principle)”,即子类可以成为基类的替身。在广泛使用的面向对象程序语言中,例如Java和C+,Liskov替换原则其实是程序设计语言本身所带的一个特性。在Java和C+程序设计语言中,在方法内部创建的“对象的引用”和“对象”是存在于不同的内存空间中的,对象的引用创建于栈内存,而对象创建于堆内存。在栈中创建的变量有时被称之为自动变量,因为当这个方法运行完毕,栈中的变量会被自动清除,但是在堆内存当中创建的对象,则需要手工删除(C+语言)或者用垃圾收集机制回收(Java语言)。
42、例如这句代码: Circle c=new Circle();其实表示了,如图6所示的事实:图6. 对象的引用和对象Liskov替换原则表述的是如果Circle是Shape的子类,那么Shape类型的引用可以指向Circle类型的对象,如图7所示。其实所谓“可以指向”是想表达这样一个特性:通过引用s可以访问Circle对象中,通过覆盖Shape的方法(有时是实现Shape的方法),而产生的方法,不是通过覆盖Shape而产生的方法当然是不能访问的。在本例中Circle对象中的draw()方法是覆盖Shape的draw()方法产生的,所以通过引
43、用s可以访问。这就是画布类中paint(Shape s)方法的由来: public void paint(Shape s) s.draw(); 图7. Shape类型的引用可以指向Circle类型的对象其实,上面的main()函数,还可以写成这样,同样就是Liskov替换: public static void main(String
44、0;args) Canvas canvas=new Canvas(); Shape s=new Cirle(); /注意这里的转型 canvas.paint(s); /注意这里的参数类型的转变,符合Liskov替换原则
45、160; s=new Triangle(); canvas.paint(s); /注意这里的参数类型的转变,符合Liskov替换原则 5.为不同的用户提供刚好够用的接口看到这里,你可能会觉得这个程序太过于简陋,以至于连图形的大小,颜色等等属性都无法设置,其实要解决这个问题比较容易,就是在每个图形类中增加一些属性,然后,在构造函数中初始化这些属性,以Circle类为例,如果要设置圆的尺寸,添加一个半径的属性,然后在构
46、造函数里初始化,代码如下:class Cirle implements Shape public double radius; Cirle(double r) radius=r; public void draw()
47、 System.out.printf("Drew a Circle radius:%fn",radius); 然后在创建对象时: Cirle c=new Cirle(3.5); /在构造函数中初始化 canvas.paint(c); 另外这个简陋的程序也不能够设置图形在画布上的位置,为了增加设置位置这个功能同时又不修改画布类以及图形类现在已经存在的代码,可以应用“接口分离原则(Interface
48、 Segregation Principle)”,因为画布类的paint()方法只关心图形的绘制,而图形绘制的位置,应该是图形对象的属性,这些属性会被draw()方法在绘制图像时调用,对画布类paint()方法来说现有的Shape接口已经刚好够用了,不需要了解如何设置位置。所以另外设置一个接口Position,在接口中加入setPosition(double x_position, double y_position)方法,但Canvas类只需要依赖于Shape接口而不需要管Position接口,如图8所示。 图8. Canvas类只依赖于Shape接口修改后的完整代码:/
49、画图的接口interface Shape public void draw();/设置显示位置的接口interface Position public void setPosition(double x_position, double y_position);/implements Shape,Position表示实现Shape,Position接口class Cirle implements Shape,Position publi
50、c double radius; public double x; public double y; Cirle(double r) radius=r; public void draw() S
51、ystem.out.printf("Drew a Circle radius:%f at position:%f,%fn",radius,x,y); public void setPosition(double x_position,double y_position) x=x_position;
52、160; y=y_position; class Ellipse implements Shape,Position public double x; public double y; public void draw() System.out.printf("
53、Drew a Ellipse at position:%f,%fn",x,y); public void setPosition(double x_position,double y_position) x=x_position; y=y_position; &
54、#160;class Canvas public void paint(Shape s) s.draw(); public class User public static void main(String args) Canvas canvas=new Canvas(); Cirle c=new Cirle(3.5); /初始化大小 c.setPosition(5, 6); /设置位置
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 微创手术机器人人机交互-洞察分析
- 微电网通信技术发展-洞察分析
- 旅游行业信息技术应用方案
- 科研机构配电室安全维保方案
- 统考版2025届高考地理二轮专项分层特训卷第一篇微考点增分专练微考点41荒漠化
- 文本挖掘中常量特征提取-洞察分析
- 心脏扩大发病机制探讨-洞察分析
- 油气资源数字化分析-洞察分析
- 探究医疗支付方式变革-洞察分析
- 污水处理项目风险评估方案
- 田间秸秆发酵技术方案
- 《中国二十四节气》教案
- 液压与气压传动课程设计-专用卧式铣床的液压系统
- 公交线路运营情况分析报告
- 《photoshop图形图像处理》项目2 制作网店商品图-图像基本编辑
- 职务犯罪课件
- 心理健康的心理健康
- 财务部工作总结及下一年工作计划
- 选择性必修二Unit1-选词填空(提高版)-第二辑
- 烟花爆竹行业事故应急救援处置培训
- 苏教版(2017)小学科学六年下册《电磁铁》说课(附反思、板书)课件
评论
0/150
提交评论